buildr/modules/packaging/ociimg/registry_publisher.go
Peter e60726ef9e
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
feat: implement new and man for plugin modules
- use extracted shared libraries
2023-08-23 22:06:26 +02:00

145 lines
3.3 KiB
Go

package ociimg
import (
"context"
"fmt"
"log/slog"
"net/http"
"github.com/google/go-containerregistry/pkg/authn"
"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/google/go-containerregistry/pkg/v1/types"
"code.icb4dc0.de/buildr/buildr/internal/maps"
)
type registryPublisherOption func(p *registryPublisher)
func withTags(tags ...string) registryPublisherOption {
return func(p *registryPublisher) {
tagsSet := make(map[string]bool)
for i := range p.tags {
tagsSet[p.tags[i]] = true
}
for i := range tags {
tagsSet[tags[i]] = true
}
p.tags = maps.Keys(tagsSet)
}
}
func withKeyChain(kc authn.Keychain) registryPublisherOption {
return func(p *registryPublisher) {
p.keychain = kc
}
}
func newRegistryPublisher(
imageName string,
logger *slog.Logger,
opts ...registryPublisherOption,
) registryPublisher {
p := registryPublisher{
imageName: imageName,
logger: logger,
tags: []string{latestTag},
t: http.DefaultTransport,
keychain: authn.DefaultKeychain,
}
for i := range opts {
opts[i](&p)
}
return p
}
type registryPublisher struct {
t http.RoundTripper
keychain authn.Keychain
logger *slog.Logger
imageName string
tags []string
insecure bool
}
func (p registryPublisher) Publish(ctx context.Context, result imageOrIndex) (name.Reference, error) {
ro := []remote.Option{
remote.WithAuthFromKeychain(p.keychain),
remote.WithTransport(p.t),
remote.WithContext(ctx),
remote.WithUserAgent(userAgent),
}
var no []name.Option
if p.insecure {
no = append(no, name.Insecure)
}
for i, tagName := range p.tags {
tag, err := name.NewTag(fmt.Sprintf("%s:%s", p.imageName, tagName), no...)
if err != nil {
return nil, err
}
if i == 0 {
p.logger.Debug("Publishing", slog.String("tag", tag.String()))
if err := pushResult(tag, result, ro); err != nil {
return nil, err
}
} else {
p.logger.Debug("Tagging", slog.String("tag", tag.String()))
if err := remote.Tag(tag, result, ro...); err != nil {
return nil, err
}
}
}
h, err := result.Digest()
if err != nil {
return nil, err
}
ref := fmt.Sprintf("%s@%s", p.imageName, h)
if len(p.tags) == 1 && p.tags[0] != latestTag {
// If a single tag is explicitly set (not latest), then this
// is probably a release, so include the tag in the reference.
ref = fmt.Sprintf("%s:%s@%s", p.imageName, p.tags[0], h)
}
dig, err := name.NewDigest(ref)
if err != nil {
return nil, err
}
p.logger.Debug("Published", slog.String("digest", dig.String()))
return &dig, nil
}
func pushResult(tag name.Tag, br imageOrIndex, opt []remote.Option) error {
mt, err := br.MediaType()
if err != nil {
return err
}
//nolint:exhaustive // not necessary
switch mt {
case types.OCIImageIndex, types.DockerManifestList:
idx, ok := br.(v1.ImageIndex)
if !ok {
return fmt.Errorf("failed to interpret result as index: %v", br)
}
return remote.WriteIndex(tag, idx, opt...)
case types.OCIManifestSchema1, types.DockerManifestSchema2:
img, ok := br.(v1.Image)
if !ok {
return fmt.Errorf("failed to interpret result as image: %v", br)
}
return remote.Write(tag, img, opt...)
default:
return fmt.Errorf("result image media type: %s", mt)
}
}