diff --git a/pkg/repocli/authbas.go b/pkg/repocli/attic/authbas.go similarity index 81% rename from pkg/repocli/authbas.go rename to pkg/repocli/attic/authbas.go index 1b555eb..3bba0a0 100644 --- a/pkg/repocli/authbas.go +++ b/pkg/repocli/attic/authbas.go @@ -4,13 +4,13 @@ import ( "encoding/base64" ) -type Authenticator interface { +type xxxAuthenticator interface { MakeHeader(user, pass string) (key, value string, err error) } type BasicAuthenticator struct{} -func NewBasicAuthenticator() *BasicAuthenticator { +func xxxNewBasicAuthenticator() *BasicAuthenticator { return &BasicAuthenticator{} } diff --git a/pkg/repocli/client_test.go b/pkg/repocli/client_test.go index b651a02..efcb220 100644 --- a/pkg/repocli/client_test.go +++ b/pkg/repocli/client_test.go @@ -12,7 +12,7 @@ import ( "time" ) -func TestClientGetManifest(t *testing.T) { +func xxxTestClientGetManifest(t *testing.T) { rawrepo := "mirror.gcr.io/alpine" tags := []string{ "3.20.0", diff --git a/pkg/repocli/digest.go b/pkg/repocli/digest.go index 2eab175..5ec317c 100644 --- a/pkg/repocli/digest.go +++ b/pkg/repocli/digest.go @@ -3,7 +3,10 @@ package repocli import ( "crypto/sha256" "crypto/sha512" + "hash" + "encoding/hex" + "errors" "strings" ) @@ -21,15 +24,17 @@ func SHA512Digest(src []byte) string { return "sha512:" + hex.EncodeToString(sum) } +type Algorithm int + const ( - Undefined int = iota + Undefined Algorithm = iota SHA256 SHA512 ) -func DigestType(digest string) int { +func DigestType(digest string) Algorithm { var err error - var typ int + var typ Algorithm digest = strings.ToLower(digest) digest = strings.TrimPrefix(digest, "sha256:") digest = strings.TrimPrefix(digest, "sha512:") @@ -47,3 +52,83 @@ func DigestType(digest string) int { } return typ } + +type Digest struct { + algorithm Algorithm + decoded []byte +} + +func NewDigestFromBytes(algorithm Algorithm, payload []byte) *Digest { + var sum []byte + var hasher hash.Hash + switch algorithm { + case SHA512: + hasher = sha512.New() + default: + hasher = sha256.New() + } + hasher.Write(payload) + sum = hasher.Sum(nil) + digest := &Digest{ + algorithm: algorithm, + decoded: sum, + } + return digest +} + +func ParseDigest(str string) (*Digest, error) { + var err error + digest := &Digest{} + + str = strings.ToLower(str) + str = strings.TrimPrefix(str, "sha256:") + str = strings.TrimPrefix(str, "sha512:") + + decoded, err := hex.DecodeString(str) + if err != nil { + err := errors.New("Can't decode digest") + return digest, err + } + digest.decoded = decoded + switch len(decoded) { + case 32: + digest.algorithm = SHA256 + case 64: + digest.algorithm = SHA512 + default: + err = errors.New("Unknown digest type") + return digest, err + } + return digest, err +} + +func (dig *Digest) Hex() string { + return hex.EncodeToString(dig.decoded) +} + +func (dig *Digest) Algorithm() Algorithm { + return dig.algorithm +} + +func (dig *Digest) Prefix() string { + var prefix string + switch dig.algorithm { + case SHA256: + prefix = "sha256" + case SHA512: + prefix = "sha512" + } + return prefix +} + +func (dig *Digest) Encoded() string { + var prefix string + switch dig.algorithm { + case SHA256: + prefix = "sha256" + case SHA512: + prefix = "sha512" + } + hexx := hex.EncodeToString(dig.decoded) + return prefix + ":" + hexx +} diff --git a/pkg/repocli/getupload.go b/pkg/repocli/getupload.go index e4c3cd6..3caf8e3 100644 --- a/pkg/repocli/getupload.go +++ b/pkg/repocli/getupload.go @@ -28,8 +28,6 @@ func (cli *Client) GetUpload(ctx context.Context, rawrepo string) (string, error } defer resp.Body.Close() - fmt.Printf("=== %++v\n", resp.Header) - if resp.StatusCode != http.StatusAccepted { err := fmt.Errorf("Unxected response code %s", resp.Status) return loc, err diff --git a/pkg/repocli/imager.go b/pkg/repocli/imager.go new file mode 100644 index 0000000..e338516 --- /dev/null +++ b/pkg/repocli/imager.go @@ -0,0 +1,123 @@ +package repocli + +import ( + "bytes" + "context" + "errors" + "io" + "os" + "path/filepath" + + ocidigest "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +type Imageer interface { + WriteManifest(mime string, manifest []byte) error + ReadManifest(digest string) (bool, string, []byte, error) + WriteBlob(digest string, reader io.Reader) error + ReadBlob(digest string, writer io.Writer) error +} + +type Imager struct { + index ocispec.Index + place string +} + +func NewImager(place string) *Imager { + imager := &Imager{ + index: ocispec.Index{ + MediaType: MediaTypeOIIv1, + Manifests: make([]ocispec.Descriptor, 0), + }, + place: place, + } + //imager.SchemaVersion = 2 + return imager +} + +func (ima *Imager) WriteManifest(ctx context.Context, digest, mime string, payload []byte) error { + var err error + digestobj, err := ocidigest.Parse(digest) + if err != nil { + return err + } + var subdir string + switch digestobj.Algorithm() { + case ocidigest.SHA256: + subdir = "sha256" + case ocidigest.SHA384: + subdir = "sha384" + case ocidigest.SHA512: + subdir = "sha512" + default: + err := errors.New("Unknown digest type") + return err + } + dir := filepath.Join(ima.place, subdir) + err = os.MkdirAll(dir, 0750) + if err != nil { + return err + } + fpath := filepath.Join(dir, digestobj.Encoded()) + file, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0640) + if err != nil { + return err + } + reader := bytes.NewReader(payload) + size, err := io.Copy(file, reader) + if size != int64(len(payload)) { + err = errors.New("Mismatch sizes") + return err + } + descr := ocispec.Descriptor{ + MediaType: mime, + Digest: digestobj, + Size: size, + } + ima.index.Manifests = append(ima.index.Manifests, descr) + // TODO: flush index + return err +} + +func (ima *Imager) WriteLayer(ctx context.Context, digest string, size int64, reader io.Reader) error { + var err error + digestobj, err := ocidigest.Parse(digest) + if err != nil { + return err + } + var subdir string + switch digestobj.Algorithm() { + case ocidigest.SHA256: + subdir = "sha256" + case ocidigest.SHA384: + subdir = "sha384" + case ocidigest.SHA512: + subdir = "sha512" + default: + err := errors.New("Unknown digest type") + return err + } + dir := filepath.Join(ima.place, subdir) + err = os.MkdirAll(dir, 0750) + if err != nil { + return err + } + fpath := filepath.Join(dir, digestobj.Encoded()) + file, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0640) + if err != nil { + return err + } + verifier := digestobj.Verifier() + mw := io.MultiWriter(verifier, file) + recsize, err := io.Copy(mw, reader) + if size != recsize { + err = errors.New("Mismatch sizes") + return err + } + if !verifier.Verified() { + err = errors.New("Mismatch digests") + return err + } + return err +} diff --git a/pkg/repocli/imager_test.go b/pkg/repocli/imager_test.go new file mode 100644 index 0000000..605e785 --- /dev/null +++ b/pkg/repocli/imager_test.go @@ -0,0 +1,35 @@ +package repocli + +import ( + "github.com/stretchr/testify/require" + + ocidigest "github.com/opencontainers/go-digest" + + "bytes" + "context" + "fmt" + "testing" + "time" + //"io" +) + +func TestDigest(t *testing.T) { + payload := []byte("Hello, World") + digestobj := ocidigest.FromBytes(payload) + require.NotNil(t, digestobj) + enc := digestobj.Encoded() + fmt.Printf("Digest: %s\n", enc) + fmt.Printf("Alg: %s\n", digestobj.Algorithm()) + + ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) + tmpdir := t.TempDir() + imager := NewImager(tmpdir) + require.NotNil(t, imager) + digest := fmt.Sprintf("%s:%s", digestobj.Algorithm(), digestobj.Encoded()) + size := int64(len(payload)) + reader := bytes.NewReader(payload) + + err := imager.WriteLayer(ctx, digest, size, reader) + require.NoError(t, err) + +} diff --git a/pkg/repocli/pullimage_test.go b/pkg/repocli/pullimage_test.go index 41aeb1f..948fcc3 100644 --- a/pkg/repocli/pullimage_test.go +++ b/pkg/repocli/pullimage_test.go @@ -9,7 +9,7 @@ import ( "time" ) -func TestPullImage(t *testing.T) { +func xxxTestPullImage(t *testing.T) { ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) cli := NewClientWithTransport(nil, nil)