buildpackage/verify-metadata

This change migrates the verify-buildpackage action to a
buildpackage/verify-metadata action.

Signed-off-by: Ben Hale <bhale@vmware.com>
This commit is contained in:
Ben Hale 2020-11-20 09:29:51 -08:00
parent f4b3a1168b
commit 5e1b3bf7a3
No known key found for this signature in database
GPG Key ID: A49396AF5C094C40
8 changed files with 293 additions and 233 deletions

View File

@ -0,0 +1,73 @@
name: Action buildpackage-verify-metadata
"on":
pull_request:
paths:
- buildpackage/verify-metadata/**
push:
branches:
- main
paths:
- buildpackage/verify-metadata/**
release:
types:
- published
jobs:
create-action:
name: Create Action
runs-on:
- ubuntu-latest
steps:
- if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }}
name: Docker login ghcr.io
uses: docker/login-action@v1
with:
password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
registry: ghcr.io
username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}
- uses: actions/checkout@v2
- id: version
name: Compute Version
run: |
#!/usr/bin/env bash
set -euo pipefail
if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then
VERSION=${BASH_REMATCH[1]}
elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then
VERSION=${BASH_REMATCH[1]}
else
VERSION=$(git rev-parse --short HEAD)
fi
echo "::set-output name=version::${VERSION}"
echo "Selected ${VERSION} from
* ref: ${GITHUB_REF}
* sha: ${GITHUB_SHA}
"
- name: Create Action
run: |
#!/usr/bin/env bash
set -euo pipefail
echo "::group::Building ${TARGET}:${VERSION}"
docker build \
--file Dockerfile \
--build-arg "SOURCE=${SOURCE}" \
--tag "${TARGET}:${VERSION}" \
.
echo "::endgroup::"
if [[ "${PUSH}" == "true" ]]; then
echo "::group::Pushing ${TARGET}:${VERSION}"
docker push "${TARGET}:${VERSION}"
echo "::endgroup::"
else
echo "Skipping push"
fi
env:
PUSH: ${{ github.event_name != 'pull_request' }}
SOURCE: buildpackage/verify-metadata/cmd
TARGET: ghcr.io/buildpacks/actions/buildpackage/verify-metadata
VERSION: ${{ steps.version.outputs.version }}

View File

@ -1,73 +0,0 @@
name: Create Action verify-buildpackage
"on":
pull_request:
paths:
- verify/**
push:
branches:
- main
paths:
- verify/**
release:
types:
- published
jobs:
create-action:
name: Create Action
runs-on:
- ubuntu-latest
steps:
- if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }}
name: Docker login ghcr.io
uses: docker/login-action@v1
with:
password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }}
registry: ghcr.io
username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}
- uses: actions/checkout@v2
- id: version
name: Compute Version
run: |
#!/usr/bin/env bash
set -euo pipefail
if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then
VERSION=${BASH_REMATCH[1]}
elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then
VERSION=${BASH_REMATCH[1]}
else
VERSION=$(git rev-parse --short HEAD)
fi
echo "::set-output name=version::${VERSION}"
echo "Selected ${VERSION} from
* ref: ${GITHUB_REF}
* sha: ${GITHUB_SHA}
"
- name: Create Action
run: |
#!/usr/bin/env bash
set -euo pipefail
echo "::group::Building ${TARGET}:${VERSION}"
docker build \
--file Dockerfile \
--build-arg "SOURCE=${SOURCE}" \
--tag "${TARGET}:${VERSION}" \
.
echo "::endgroup::"
if [[ "${PUSH}" == "true" ]]; then
echo "::group::Pushing ${TARGET}:${VERSION}"
docker push "${TARGET}:${VERSION}"
echo "::endgroup::"
else
echo "Skipping push"
fi
env:
PUSH: ${{ github.event_name != 'pull_request' }}
SOURCE: verify/cmd
TARGET: ghcr.io/buildpacks/actions/verify-buildpackage
VERSION: ${{ steps.version.outputs.version }}

View File

@ -8,8 +8,8 @@
- [Compute Mmetadata Action](#compute-mmetadata-action)
- [Inputs](#inputs)
- [Outputs](#outputs)
- [Registry Action](#registry-action)
- [Add](#add)
- [Buildpackage](#buildpackage)
- [Verify Metadata Action](#verify-metadata-action)
- [Inputs](#inputs-1)
- [Yank](#yank)
- [Inputs](#inputs-2)
@ -41,8 +41,30 @@ uses: docker://ghcr.io/buildpacks/actions/buildpack/compute-metadata
| `version` | The contents of `buildpack.version`
| `homepage` | The contents of `buildpack.homepage`
## Registry Action
The registry action adds and yanks buildpack releases in the [Buildpack Registry Index][bri].
## Buildpackage
### Verify Metadata Action
The `buildpackage/verify-metadata` action parses the metadata on a buildpackage and verifies that the `id` and `version` match expected values.
```yaml
uses: docker://ghcr.io/buildpacks/actions/buildpackage/verify-metadata
with:
id: test-buildpack
version: "1.0.0"
address: ghcr.io/example/test-buildpack@sha256:04ba2d17480910bd340f0305d846b007148dafd64bc6fc2626870c174b7c7de7
```
#### Inputs
| Parameter | Description
| :-------- | :----------
| `id` | The expected `id` for the buildpackage
| `version` | The expected `version` for the buildpackage
| `address` | The digest-style address of the buildpackage to verify
## Registry
### Add Entry Action
The `registry/add-entry` action adds an entry to the [Buildpack Registry Index][bri].
[bri]: https://github.com/buildpacks/registry-index
@ -102,24 +124,6 @@ uses: buildpacks/github-actions/setup-pack
| `pack-version` | Optional version of [`pack`][pack] to install. Defaults to latest release.
| `yj-version` | Optional version of [`yj`][yj] to install. Defaults to latest release.
## Verify Buildpackage Action
The verify-buildpackage action parses the metadata on a buildpackage and verifies that the `id` and `version` match expected values.
```yaml
uses: docker://ghcr.io/buildpacks/actions/verify-buildpackage
with:
id: test-buildpack
version: "1.0.0"
address: ghcr.io/example/test-buildpack@sha256:04ba2d17480910bd340f0305d846b007148dafd64bc6fc2626870c174b7c7de7
```
#### Inputs
| Parameter | Description
| :-------- | :----------
| `id` | The expected `id` for the buildpackage
| `version` | The expected `version` for the buildpackage
| `address` | The digest-style address of the buildpackage to verify
## License
This library is released under version 2.0 of the [Apache License][a].

View File

@ -22,32 +22,13 @@ import (
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/buildpacks/github-actions/verify"
"github.com/buildpacks/github-actions/buildpackage/verify-metadata"
"github.com/buildpacks/github-actions/toolkit"
)
func main() {
var (
v verify.Verifier
err error
ok bool
)
v.Image = remote.Image
if v.ID, ok = os.LookupEnv("INPUT_ID"); !ok {
panic(fmt.Errorf("id must be specified"))
}
if v.Version, ok = os.LookupEnv("INPUT_VERSION"); !ok {
panic(fmt.Errorf("version must be specified"))
}
if v.Address, ok = os.LookupEnv("INPUT_ADDRESS"); !ok {
panic(fmt.Errorf("address must be specified"))
}
if err = v.Verify(); err != nil {
panic(err)
if err := metadata.VerifyMetadata(&toolkit.DefaultToolkit{}, remote.Image); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@ -0,0 +1,46 @@
// Code generated by mockery v2.4.0-beta. DO NOT EDIT.
package mocks
import (
name "github.com/google/go-containerregistry/pkg/name"
remote "github.com/google/go-containerregistry/pkg/v1/remote"
mock "github.com/stretchr/testify/mock"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
// ImageFunction is an autogenerated mock type for the ImageFunction type
type ImageFunction struct {
mock.Mock
}
// Execute provides a mock function with given fields: _a0, _a1
func (_m *ImageFunction) Execute(_a0 name.Reference, _a1 ...remote.Option) (v1.Image, error) {
_va := make([]interface{}, len(_a1))
for _i := range _a1 {
_va[_i] = _a1[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 v1.Image
if rf, ok := ret.Get(0).(func(name.Reference, ...remote.Option) v1.Image); ok {
r0 = rf(_a0, _a1...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(v1.Image)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(name.Reference, ...remote.Option) error); ok {
r1 = rf(_a0, _a1...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package verify
package metadata
import (
"encoding/json"
@ -24,54 +24,67 @@ import (
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/buildpacks/github-actions/toolkit"
)
const MetadataLabel = "io.buildpacks.buildpackage.metadata"
type Verifier struct {
Image func(name.Reference, ...remote.Option) (v1.Image, error)
//go:generate mockery --name ImageFunction --case=underscore
ID string
Version string
Address string
}
type ImageFunction func(name.Reference, ...remote.Option) (v1.Image, error)
func (v Verifier) Verify() error {
ref, err := name.ParseReference(v.Address)
func VerifyMetadata(tk toolkit.Toolkit, imageFn ImageFunction) error {
id, ok := tk.GetInput("id")
if !ok {
return toolkit.FailedError("id must be specified")
}
version, ok := tk.GetInput("version")
if !ok {
return toolkit.FailedError("version must be specified")
}
address, ok := tk.GetInput("address")
if !ok {
return toolkit.FailedError("address must be specified")
}
ref, err := name.ParseReference(address)
if err != nil {
return fmt.Errorf("unable to parse address %s as image reference\n%w", v.Address, err)
return toolkit.FailedErrorf("unable to parse address %s as image reference", address)
}
if _, ok := ref.(name.Digest); !ok {
return fmt.Errorf("address %s must be in digest form <host>/<repository>@sh256:<digest>", v.Address)
return toolkit.FailedErrorf("address %s must be in digest form <host>/<repository>@sh256:<digest>", address)
}
image, err := v.Image(ref)
image, err := imageFn(ref)
if err != nil {
return fmt.Errorf("unable to retrieve image %s\n%w", v.Address, err)
return toolkit.FailedErrorf("unable to retrieve image %s", address)
}
configFile, err := image.ConfigFile()
if err != nil {
return fmt.Errorf("unable to retrieve config file\n%w", err)
return toolkit.FailedErrorf("unable to retrieve config file\n%w", err)
}
raw, ok := configFile.Config.Labels[MetadataLabel]
if !ok {
return fmt.Errorf("unable to retrieve %s label", MetadataLabel)
return toolkit.FailedErrorf("unable to retrieve %s label", MetadataLabel)
}
var m metadata
if err := json.Unmarshal([]byte(raw), &m); err != nil {
return fmt.Errorf("unable to unmarshal %s label", MetadataLabel)
return toolkit.FailedErrorf("unable to unmarshal %s label", MetadataLabel)
}
if v.ID != m.ID {
return fmt.Errorf("invalid id in buildpackage: expected '%s', found '%s'", v.ID, m.ID)
if id != m.ID {
return toolkit.FailedErrorf("invalid id in buildpackage: expected '%s', found '%s'", id, m.ID)
}
if v.Version != m.Version {
return fmt.Errorf("invalid version in buildpackage: expected '%s', found '%s'", v.Version, m.Version)
if version != m.Version {
return toolkit.FailedErrorf("invalid version in buildpackage: expected '%s', found '%s'", version, m.Version)
}
@ -85,7 +98,7 @@ func (v Verifier) Verify() error {
Version: %s
Homepage: %s
Stacks: %s
`, v.Address, m.ID, m.Version, m.Homepage, strings.Join(stacks, ", "))
`, address, m.ID, m.Version, m.Homepage, strings.Join(stacks, ", "))
return nil
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package metadata_test
import (
"testing"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake"
. "github.com/onsi/gomega"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/stretchr/testify/mock"
metadata "github.com/buildpacks/github-actions/buildpackage/verify-metadata"
mocks2 "github.com/buildpacks/github-actions/buildpackage/verify-metadata/mocks"
"github.com/buildpacks/github-actions/toolkit/mocks"
)
func TestVerifyMetadata(t *testing.T) {
spec.Run(t, "verify-metadata", func(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
f = &mocks2.ImageFunction{}
i = &fake.FakeImage{}
tk = &mocks.Toolkit{}
)
it.Before(func() {
tk.On("GetInput", "id").Return("test-id", true)
tk.On("GetInput", "version").Return("test-version", true)
})
it("fails if address is not a digest image reference", func() {
tk.On("GetInput", "address").Return("test-host/test-repository:test-version", true)
Expect(metadata.VerifyMetadata(tk, f.Execute)).
To(MatchError("::error ::address test-host/test-repository:test-version must be in digest form <host>/<repository>@sh256:<digest>"))
})
context("valid address", func() {
it.Before(func() {
tk.On("GetInput", "address").Return("host/repository@sha256:04ba2d17480910bd340f0305d846b007148dafd64bc6fc2626870c174b7c7de7", true)
f.On("Execute", mock.Anything).Return(i, nil)
})
it("fails if io.buildpacks.buildpackage.metadata is not on image", func() {
i.ConfigFileReturns(&v1.ConfigFile{
Config: v1.Config{
Labels: map[string]string{},
},
}, nil)
Expect(metadata.VerifyMetadata(tk, f.Execute)).
To(MatchError("::error ::unable to retrieve io.buildpacks.buildpackage.metadata label"))
})
it("fails if id does not match", func() {
i.ConfigFileReturns(&v1.ConfigFile{
Config: v1.Config{
Labels: map[string]string{metadata.MetadataLabel: `{ "id": "another-id", "version": "test-version" }`},
},
}, nil)
Expect(metadata.VerifyMetadata(tk, f.Execute)).
To(MatchError("::error ::invalid id in buildpackage: expected 'test-id', found 'another-id'"))
})
it("fails if version does not match", func() {
i.ConfigFileReturns(&v1.ConfigFile{
Config: v1.Config{
Labels: map[string]string{metadata.MetadataLabel: `{ "id": "test-id", "version": "another-version" }`},
},
}, nil)
Expect(metadata.VerifyMetadata(tk, f.Execute)).
To(MatchError("::error ::invalid version in buildpackage: expected 'test-version', found 'another-version'"))
})
it("passes if version and id match", func() {
i.ConfigFileReturns(&v1.ConfigFile{
Config: v1.Config{
Labels: map[string]string{metadata.MetadataLabel: `{ "id": "test-id", "version": "test-version" }`},
},
}, nil)
Expect(metadata.VerifyMetadata(tk, f.Execute)).To(Succeed())
})
})
}, spec.Report(report.Terminal{}))
}

View File

@ -1,93 +0,0 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package verify_test
import (
"testing"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake"
"github.com/google/go-containerregistry/pkg/v1/remote"
. "github.com/onsi/gomega"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/github-actions/verify"
)
func TestVerify(t *testing.T) {
spec.Run(t, "verify", func(t *testing.T, when spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
r = &TestRemote{Labels: make(map[string]string)}
v = verify.Verifier{
Image: r.Image,
ID: "test-id",
Version: "test-version",
Address: "host/repository@sha256:04ba2d17480910bd340f0305d846b007148dafd64bc6fc2626870c174b7c7de7",
}
)
it("fails if address is not a digest image reference", func() {
v.Address = "test-host/test-repository:test-version"
Expect(v.Verify()).To(MatchError("address test-host/test-repository:test-version must be in digest form <host>/<repository>@sh256:<digest>"))
})
it("fails if io.buildpacks.buildpackage.metadata is not on image", func() {
Expect(v.Verify()).To(MatchError("unable to retrieve io.buildpacks.buildpackage.metadata label"))
})
it("fails if id does not match", func() {
r.Labels[verify.MetadataLabel] = `{ "id": "another-id", "version": "test-version" }`
Expect(v.Verify()).To(MatchError("invalid id in buildpackage: expected 'test-id', found 'another-id'"))
})
it("fails if version does not match", func() {
r.Labels[verify.MetadataLabel] = `{ "id": "test-id", "version": "another-version" }`
Expect(v.Verify()).To(MatchError("invalid version in buildpackage: expected 'test-version', found 'another-version'"))
})
it("passes if version and id match", func() {
r.Labels[verify.MetadataLabel] = `{ "id": "test-id", "version": "test-version" }`
Expect(v.Verify()).To(Succeed())
})
}, spec.Report(report.Terminal{}))
}
type TestRemote struct {
Labels map[string]string
}
func (t *TestRemote) Image(_ name.Reference, _ ...remote.Option) (v1.Image, error) {
i := &fake.FakeImage{}
i.ConfigFileReturns(&v1.ConfigFile{
Config: v1.Config{
Labels: t.Labels,
},
}, nil)
return i, nil
}