mirror of
https://github.com/openshift/image-registry.git
synced 2026-02-05 09:45:55 +01:00
Add v2 registry
This commit is contained in:
2
cmd/dockerregistry/doc.go
Normal file
2
cmd/dockerregistry/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package main contains the main executable for the integrated OpenShift Docker registry 2.0.
|
||||
package main
|
||||
33
cmd/dockerregistry/main.go
Normal file
33
cmd/dockerregistry/main.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/openshift/origin/pkg/cmd/dockerregistry"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// TODO convert to flags instead of a config file?
|
||||
configurationPath := ""
|
||||
if flag.NArg() > 0 {
|
||||
configurationPath = flag.Arg(0)
|
||||
} else if os.Getenv("REGISTRY_CONFIGURATION_PATH") != "" {
|
||||
configurationPath = os.Getenv("REGISTRY_CONFIGURATION_PATH")
|
||||
}
|
||||
if configurationPath == "" {
|
||||
fmt.Println("configuration path unspecified")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
configFile, err := os.Open(configurationPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to open configuration file: %s", err)
|
||||
}
|
||||
|
||||
dockerregistry.Execute(configFile)
|
||||
}
|
||||
303
pkg/dockerregistry/middleware/repository/openshift.go
Normal file
303
pkg/dockerregistry/middleware/repository/openshift.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
repomw "github.com/docker/distribution/registry/middleware/repository"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/openshift/origin/pkg/client"
|
||||
imageapi "github.com/openshift/origin/pkg/image/api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
repomw.Register("openshift", repomw.InitFunc(newRepository))
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
distribution.Repository
|
||||
|
||||
// TODO cache this at the app level
|
||||
registryClient *client.Client
|
||||
registryAddr string
|
||||
namespace string
|
||||
name string
|
||||
}
|
||||
|
||||
// newRepository returns a new repository middleware.
|
||||
func newRepository(repo distribution.Repository, options map[string]interface{}) (distribution.Repository, error) {
|
||||
openshiftAddr := os.Getenv("OPENSHIFT_MASTER")
|
||||
if len(openshiftAddr) == 0 {
|
||||
return nil, errors.New("OPENSHIFT_MASTER is required")
|
||||
}
|
||||
|
||||
registryAddr := os.Getenv("REGISTRY_URL")
|
||||
if len(registryAddr) == 0 {
|
||||
return nil, errors.New("REGISTRY_URL is required")
|
||||
}
|
||||
|
||||
insecure := len(os.Getenv("OPENSHIFT_INSECURE")) > 0
|
||||
var tlsClientConfig kclient.TLSClientConfig
|
||||
if !insecure {
|
||||
caData := os.Getenv("OPENSHIFT_CA_DATA")
|
||||
if len(caData) == 0 {
|
||||
return nil, errors.New("OPENSHIFT_CA_DATA is required")
|
||||
}
|
||||
certData := os.Getenv("OPENSHIFT_CERT_DATA")
|
||||
if len(certData) == 0 {
|
||||
return nil, errors.New("OPENSHIFT_CERT_DATA is required")
|
||||
}
|
||||
certKeyData := os.Getenv("OPENSHIFT_KEY_DATA")
|
||||
if len(certKeyData) == 0 {
|
||||
return nil, errors.New("OPENSHIFT_KEY_DATA is required")
|
||||
}
|
||||
tlsClientConfig = kclient.TLSClientConfig{
|
||||
CAData: []byte(caData),
|
||||
CertData: []byte(certData),
|
||||
KeyData: []byte(certKeyData),
|
||||
}
|
||||
}
|
||||
|
||||
registryClientConfig := kclient.Config{
|
||||
Host: openshiftAddr,
|
||||
TLSClientConfig: tlsClientConfig,
|
||||
Insecure: insecure,
|
||||
}
|
||||
registryClient, err := client.New(®istryClientConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating OpenShift client: %s", err)
|
||||
}
|
||||
|
||||
nameParts := strings.SplitN(repo.Name(), "/", 2)
|
||||
|
||||
return &repository{
|
||||
Repository: repo,
|
||||
registryClient: registryClient,
|
||||
registryAddr: registryAddr,
|
||||
namespace: nameParts[0],
|
||||
name: nameParts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Manifests returns r, which implements distribution.ManifestService.
|
||||
func (r *repository) Manifests() distribution.ManifestService {
|
||||
return r
|
||||
}
|
||||
|
||||
// Tags lists the tags under the named repository.
|
||||
func (r *repository) Tags() ([]string, error) {
|
||||
imageRepository, err := r.getImageRepository()
|
||||
if err != nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
tags := []string{}
|
||||
for tag := range imageRepository.Status.Tags {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// Exists returns true if the manifest specified by dgst exists.
|
||||
func (r *repository) Exists(dgst digest.Digest) (bool, error) {
|
||||
image, err := r.getImage(dgst)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return image != nil, nil
|
||||
}
|
||||
|
||||
// ExistsByTag returns true if the manifest with tag `tag` exists.
|
||||
func (r *repository) ExistsByTag(tag string) (bool, error) {
|
||||
imageRepository, err := r.getImageRepository()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, found := imageRepository.Status.Tags[tag]
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// Get retrieves the manifest with digest `dgst`.
|
||||
func (r *repository) Get(dgst digest.Digest) (*manifest.SignedManifest, error) {
|
||||
_, err := r.getImageStreamImage(dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
image, err := r.getImage(dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.manifestFromImage(image)
|
||||
}
|
||||
|
||||
// Get retrieves the named manifest, if it exists.
|
||||
func (r *repository) GetByTag(tag string) (*manifest.SignedManifest, error) {
|
||||
image, err := r.getImageRepositoryTag(tag)
|
||||
if err != nil {
|
||||
// TODO remove when docker 1.6 is out
|
||||
// Since docker 1.5 doesn't support pull by id, we're simulating pull by id
|
||||
// against the v2 registry by using a pull spec of the form
|
||||
// <repo>:<hex portion of digest>, so once we verify we got a 404 from
|
||||
// getImageRepositoryTag, we construct a digest and attempt to get the
|
||||
// imageStreamImage using that digest.
|
||||
|
||||
// TODO replace with kerrors.IsStatusError when it's rebased in
|
||||
if err, ok := err.(*kerrors.StatusError); !ok {
|
||||
log.Errorf("GetByTag: getImageRepositoryTag returned error: %s", err)
|
||||
return nil, err
|
||||
} else if err.ErrStatus.Code != http.StatusNotFound {
|
||||
log.Errorf("GetByTag: getImageRepositoryTag returned non-404: %s", err)
|
||||
}
|
||||
// let's try to get by id
|
||||
dgst, dgstErr := digest.ParseDigest("sha256:" + tag)
|
||||
if dgstErr != nil {
|
||||
log.Errorf("GetByTag: unable to parse digest: %s", dgstErr)
|
||||
return nil, err
|
||||
}
|
||||
image, err = r.getImageStreamImage(dgst)
|
||||
if err != nil {
|
||||
log.Errorf("GetByTag: getImageStreamImage returned error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dgst, err := digest.ParseDigest(image.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
image, err = r.getImage(dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.manifestFromImage(image)
|
||||
}
|
||||
|
||||
// Put creates or updates the named manifest.
|
||||
func (r *repository) Put(manifest *manifest.SignedManifest) error {
|
||||
// Resolve the payload in the manifest.
|
||||
payload, err := manifest.Payload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate digest
|
||||
dgst, err := digest.FromBytes(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upload to openshift
|
||||
irm := imageapi.ImageRepositoryMapping{
|
||||
TypeMeta: kapi.TypeMeta{
|
||||
APIVersion: "v1beta1",
|
||||
Kind: "ImageRepositoryMapping",
|
||||
},
|
||||
ObjectMeta: kapi.ObjectMeta{
|
||||
Namespace: r.namespace,
|
||||
Name: r.name,
|
||||
},
|
||||
Tag: manifest.Tag,
|
||||
Image: imageapi.Image{
|
||||
ObjectMeta: kapi.ObjectMeta{
|
||||
Name: dgst.String(),
|
||||
},
|
||||
DockerImageReference: fmt.Sprintf("%s/%s/%s@%s", r.registryAddr, r.namespace, r.name, dgst.String()),
|
||||
DockerImageManifest: string(payload),
|
||||
},
|
||||
}
|
||||
|
||||
if err := r.registryClient.ImageRepositoryMappings(r.namespace).Create(&irm); err != nil {
|
||||
log.Errorf("Error creating ImageRepositoryMapping: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Grab each json signature and store them.
|
||||
signatures, err := manifest.Signatures()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, signature := range signatures {
|
||||
if err := r.Signatures().Put(dgst, signature); err != nil {
|
||||
log.Errorf("Error storing signature: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the manifest with digest `dgst`.
|
||||
func (r *repository) Delete(dgst digest.Digest) error {
|
||||
return r.registryClient.Images().Delete(dgst.String())
|
||||
}
|
||||
|
||||
// getImageRepository retrieves the ImageRepository for r.
|
||||
func (r *repository) getImageRepository() (*imageapi.ImageRepository, error) {
|
||||
return r.registryClient.ImageRepositories(r.namespace).Get(r.name)
|
||||
}
|
||||
|
||||
// getImage retrieves the Image with digest `dgst`. This uses the registry's
|
||||
// credentials and should ONLY
|
||||
func (r *repository) getImage(dgst digest.Digest) (*imageapi.Image, error) {
|
||||
return r.registryClient.Images().Get(dgst.String())
|
||||
}
|
||||
|
||||
// getImageRepositoryTag retrieves the Image with tag `tag` for the ImageRepository
|
||||
// associated with r.
|
||||
func (r *repository) getImageRepositoryTag(tag string) (*imageapi.Image, error) {
|
||||
return r.registryClient.ImageRepositoryTags(r.namespace).Get(r.name, tag)
|
||||
}
|
||||
|
||||
// getImageStreamImage retrieves the Image with digest `dgst` for the ImageRepository
|
||||
// associated with r. This ensures the user has access to the image.
|
||||
func (r *repository) getImageStreamImage(dgst digest.Digest) (*imageapi.Image, error) {
|
||||
// TODO !!! use user credentials, not the registry's !!!
|
||||
return r.registryClient.ImageStreamImages(r.namespace).Get(r.name, dgst.String())
|
||||
}
|
||||
|
||||
// manifestFromImage converts an Image to a SignedManifest.
|
||||
func (r *repository) manifestFromImage(image *imageapi.Image) (*manifest.SignedManifest, error) {
|
||||
dgst, err := digest.ParseDigest(image.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch the signatures for the manifest
|
||||
signatures, err := r.Signatures().Get(dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jsig, err := libtrust.NewJSONSignature([]byte(image.DockerImageManifest), signatures...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract the pretty JWS
|
||||
raw, err := jsig.PrettySignature("signatures")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sm manifest.SignedManifest
|
||||
if err := json.Unmarshal(raw, &sm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sm, err
|
||||
}
|
||||
Reference in New Issue
Block a user