commit
78e88560e1
|
@ -5,6 +5,8 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
|
@ -141,7 +143,9 @@ func (ce *exporter) Finalize(ctx context.Context) (map[string]string, error) {
|
|||
return nil, layerDone(err)
|
||||
}
|
||||
if err := ce.cache.Save(ctx, key, ra); err != nil {
|
||||
return nil, layerDone(errors.Wrap(err, "error writing layer blob"))
|
||||
if !errors.Is(err, os.ErrExist) {
|
||||
return nil, layerDone(errors.Wrap(err, "error writing layer blob"))
|
||||
}
|
||||
}
|
||||
layerDone(nil)
|
||||
}
|
||||
|
@ -305,18 +309,47 @@ func (ci *importer) Resolve(ctx context.Context, _ ocispecs.Descriptor, id strin
|
|||
}
|
||||
|
||||
type ciProvider struct {
|
||||
desc ocispecs.Descriptor
|
||||
ci *importer
|
||||
ci *importer
|
||||
desc ocispecs.Descriptor
|
||||
mu sync.Mutex
|
||||
entries map[digest.Digest]*actionscache.Entry
|
||||
}
|
||||
|
||||
func (p *ciProvider) ReaderAt(ctx context.Context, desc ocispecs.Descriptor) (content.ReaderAt, error) {
|
||||
func (p *ciProvider) CheckDescriptor(ctx context.Context, desc ocispecs.Descriptor) error {
|
||||
if desc.Digest != p.desc.Digest {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := p.loadEntry(ctx, desc)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *ciProvider) loadEntry(ctx context.Context, desc ocispecs.Descriptor) (*actionscache.Entry, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if ce, ok := p.entries[desc.Digest]; ok {
|
||||
return ce, nil
|
||||
}
|
||||
key := "buildkit-blob-" + version + "-" + desc.Digest.String()
|
||||
ce, err := p.ci.cache.Load(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ce == nil {
|
||||
return nil, errors.Errorf("blob not found")
|
||||
return nil, errors.Errorf("blob %s not found", desc.Digest)
|
||||
}
|
||||
if p.entries == nil {
|
||||
p.entries = make(map[digest.Digest]*actionscache.Entry)
|
||||
}
|
||||
p.entries[desc.Digest] = ce
|
||||
return ce, nil
|
||||
}
|
||||
|
||||
func (p *ciProvider) ReaderAt(ctx context.Context, desc ocispecs.Descriptor) (content.ReaderAt, error) {
|
||||
ce, err := p.loadEntry(ctx, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rac := ce.Download(context.TODO())
|
||||
return &readerAt{ReaderAtCloser: rac, desc: desc}, nil
|
||||
|
|
3
go.mod
3
go.mod
|
@ -50,7 +50,7 @@ require (
|
|||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028
|
||||
github.com/tonistiigi/go-actions-cache v0.0.0-20210714033416-b93d7f1b2e70
|
||||
github.com/tonistiigi/go-actions-cache v0.0.0-20211002214948-4d48f2ff622a
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f
|
||||
github.com/urfave/cli v1.22.4
|
||||
|
@ -88,6 +88,7 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -325,6 +325,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
|||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
|
@ -1034,8 +1036,8 @@ github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dS
|
|||
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 h1:uEkkUFMCPtzz1HVOa42u15OHems1ugiRt172tSRTWSk=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028/go.mod h1:E6osHKls9ix67jofYQ61RQKwlJhqJOZM2hintp+49iI=
|
||||
github.com/tonistiigi/go-actions-cache v0.0.0-20210714033416-b93d7f1b2e70 h1:+ZlFs3Tl5qYZJvX2PxfZxGlVXz847LsOJGyNVU5pCHo=
|
||||
github.com/tonistiigi/go-actions-cache v0.0.0-20210714033416-b93d7f1b2e70/go.mod h1:dNS+PPTqGnSl80x3wEyWWCHeON5xiBGtcM0uD6CgHNU=
|
||||
github.com/tonistiigi/go-actions-cache v0.0.0-20211002214948-4d48f2ff622a h1:TkwT/jFyObWQRFSUdLPEUIBXXlbqkGzStfOFgu/okCE=
|
||||
github.com/tonistiigi/go-actions-cache v0.0.0-20211002214948-4d48f2ff622a/go.mod h1:YiIBjH5gP7mao3t0dBrNNBGuKYdeJmcAJjYLXr43k6A=
|
||||
github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939 h1:s6wDMZYNyWt8KvkjhrMpOthFPgI3JB8ipJS+eCV/psg=
|
||||
github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939/go.mod h1:Vm5u/mtkj1OMhtao0v+BGo2LUoLCgHYXvRmj0jWITlE=
|
||||
github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939 h1:ZZ1KHKvs97BcRoblbm6RhrDzs/OejFv7miYSIcZI7Ds=
|
||||
|
|
|
@ -946,6 +946,8 @@ func notifyCompleted(ctx context.Context, v *client.Vertex, err error, cached bo
|
|||
v.Cached = cached
|
||||
if err != nil {
|
||||
v.Error = err.Error()
|
||||
} else {
|
||||
v.Error = ""
|
||||
}
|
||||
pw.Write(v.Digest.String(), *v)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,26 @@ type MultiProvider struct {
|
|||
sub map[digest.Digest]content.Provider
|
||||
}
|
||||
|
||||
func (mp *MultiProvider) CheckDescriptor(ctx context.Context, desc ocispecs.Descriptor) error {
|
||||
type checkDescriptor interface {
|
||||
CheckDescriptor(context.Context, ocispecs.Descriptor) error
|
||||
}
|
||||
|
||||
mp.mu.RLock()
|
||||
if p, ok := mp.sub[desc.Digest]; ok {
|
||||
mp.mu.RUnlock()
|
||||
if cd, ok := p.(checkDescriptor); ok {
|
||||
return cd.CheckDescriptor(ctx, desc)
|
||||
}
|
||||
} else {
|
||||
mp.mu.RUnlock()
|
||||
}
|
||||
if cd, ok := mp.base.(checkDescriptor); ok {
|
||||
return cd.CheckDescriptor(ctx, desc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReaderAt returns a content.ReaderAt
|
||||
func (mp *MultiProvider) ReaderAt(ctx context.Context, desc ocispecs.Descriptor) (content.ReaderAt, error) {
|
||||
mp.mu.RLock()
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.o
|
||||
*.a
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.prof
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# Gogland
|
||||
.idea/
|
|
@ -0,0 +1,29 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
|
||||
env:
|
||||
global:
|
||||
- GO111MODULE=on
|
||||
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go get golang.org/x/lint/golint
|
||||
script:
|
||||
- gofiles=$(find ./ -name '*.go') && [ -z "$gofiles" ] || unformatted=$(goimports -l $gofiles) && [ -z "$unformatted" ] || (echo >&2 "Go files must be formatted with gofmt. Following files has problem:\n $unformatted" && false)
|
||||
- golint ./... # This won't break the build, just show warnings
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (c) 2018-2020, Dmitrij Koniajev (dimchansky@gmail.com)
|
||||
|
||||
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
|
||||
|
||||
http://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.
|
|
@ -0,0 +1,66 @@
|
|||
# utfbom [![Godoc](https://godoc.org/github.com/dimchansky/utfbom?status.png)](https://godoc.org/github.com/dimchansky/utfbom) [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Build Status](https://travis-ci.org/dimchansky/utfbom.svg?branch=master)](https://travis-ci.org/dimchansky/utfbom) [![Go Report Card](https://goreportcard.com/badge/github.com/dimchansky/utfbom)](https://goreportcard.com/report/github.com/dimchansky/utfbom) [![Coverage Status](https://coveralls.io/repos/github/dimchansky/utfbom/badge.svg?branch=master)](https://coveralls.io/github/dimchansky/utfbom?branch=master)
|
||||
|
||||
The package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary. It can also return the encoding detected by the BOM.
|
||||
|
||||
## Installation
|
||||
|
||||
go get -u github.com/dimchansky/utfbom
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/dimchansky/utfbom"
|
||||
)
|
||||
|
||||
func main() {
|
||||
trySkip([]byte("\xEF\xBB\xBFhello"))
|
||||
trySkip([]byte("hello"))
|
||||
}
|
||||
|
||||
func trySkip(byteData []byte) {
|
||||
fmt.Println("Input:", byteData)
|
||||
|
||||
// just skip BOM
|
||||
output, err := ioutil.ReadAll(utfbom.SkipOnly(bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("ReadAll with BOM skipping", output)
|
||||
|
||||
// skip BOM and detect encoding
|
||||
sr, enc := utfbom.Skip(bytes.NewReader(byteData))
|
||||
fmt.Printf("Detected encoding: %s\n", enc)
|
||||
output, err = ioutil.ReadAll(sr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("ReadAll with BOM detection and skipping", output)
|
||||
fmt.Println()
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
$ go run main.go
|
||||
Input: [239 187 191 104 101 108 108 111]
|
||||
ReadAll with BOM skipping [104 101 108 108 111]
|
||||
Detected encoding: UTF8
|
||||
ReadAll with BOM detection and skipping [104 101 108 108 111]
|
||||
|
||||
Input: [104 101 108 108 111]
|
||||
ReadAll with BOM skipping [104 101 108 108 111]
|
||||
Detected encoding: Unknown
|
||||
ReadAll with BOM detection and skipping [104 101 108 108 111]
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
// Package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary.
|
||||
// It wraps an io.Reader object, creating another object (Reader) that also implements the io.Reader
|
||||
// interface but provides automatic BOM checking and removing as necessary.
|
||||
package utfbom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Encoding is type alias for detected UTF encoding.
|
||||
type Encoding int
|
||||
|
||||
// Constants to identify detected UTF encodings.
|
||||
const (
|
||||
// Unknown encoding, returned when no BOM was detected
|
||||
Unknown Encoding = iota
|
||||
|
||||
// UTF8, BOM bytes: EF BB BF
|
||||
UTF8
|
||||
|
||||
// UTF-16, big-endian, BOM bytes: FE FF
|
||||
UTF16BigEndian
|
||||
|
||||
// UTF-16, little-endian, BOM bytes: FF FE
|
||||
UTF16LittleEndian
|
||||
|
||||
// UTF-32, big-endian, BOM bytes: 00 00 FE FF
|
||||
UTF32BigEndian
|
||||
|
||||
// UTF-32, little-endian, BOM bytes: FF FE 00 00
|
||||
UTF32LittleEndian
|
||||
)
|
||||
|
||||
// String returns a user-friendly string representation of the encoding. Satisfies fmt.Stringer interface.
|
||||
func (e Encoding) String() string {
|
||||
switch e {
|
||||
case UTF8:
|
||||
return "UTF8"
|
||||
case UTF16BigEndian:
|
||||
return "UTF16BigEndian"
|
||||
case UTF16LittleEndian:
|
||||
return "UTF16LittleEndian"
|
||||
case UTF32BigEndian:
|
||||
return "UTF32BigEndian"
|
||||
case UTF32LittleEndian:
|
||||
return "UTF32LittleEndian"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
const maxConsecutiveEmptyReads = 100
|
||||
|
||||
// Skip creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||
// It also returns the encoding detected by the BOM.
|
||||
// If the detected encoding is not needed, you can call the SkipOnly function.
|
||||
func Skip(rd io.Reader) (*Reader, Encoding) {
|
||||
// Is it already a Reader?
|
||||
b, ok := rd.(*Reader)
|
||||
if ok {
|
||||
return b, Unknown
|
||||
}
|
||||
|
||||
enc, left, err := detectUtf(rd)
|
||||
return &Reader{
|
||||
rd: rd,
|
||||
buf: left,
|
||||
err: err,
|
||||
}, enc
|
||||
}
|
||||
|
||||
// SkipOnly creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||
func SkipOnly(rd io.Reader) *Reader {
|
||||
r, _ := Skip(rd)
|
||||
return r
|
||||
}
|
||||
|
||||
// Reader implements automatic BOM (Unicode Byte Order Mark) checking and
|
||||
// removing as necessary for an io.Reader object.
|
||||
type Reader struct {
|
||||
rd io.Reader // reader provided by the client
|
||||
buf []byte // buffered data
|
||||
err error // last error
|
||||
}
|
||||
|
||||
// Read is an implementation of io.Reader interface.
|
||||
// The bytes are taken from the underlying Reader, but it checks for BOMs, removing them as necessary.
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if r.buf == nil {
|
||||
if r.err != nil {
|
||||
return 0, r.readErr()
|
||||
}
|
||||
|
||||
return r.rd.Read(p)
|
||||
}
|
||||
|
||||
// copy as much as we can
|
||||
n = copy(p, r.buf)
|
||||
r.buf = nilIfEmpty(r.buf[n:])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readErr() error {
|
||||
err := r.err
|
||||
r.err = nil
|
||||
return err
|
||||
}
|
||||
|
||||
var errNegativeRead = errors.New("utfbom: reader returned negative count from Read")
|
||||
|
||||
func detectUtf(rd io.Reader) (enc Encoding, buf []byte, err error) {
|
||||
buf, err = readBOM(rd)
|
||||
|
||||
if len(buf) >= 4 {
|
||||
if isUTF32BigEndianBOM4(buf) {
|
||||
return UTF32BigEndian, nilIfEmpty(buf[4:]), err
|
||||
}
|
||||
if isUTF32LittleEndianBOM4(buf) {
|
||||
return UTF32LittleEndian, nilIfEmpty(buf[4:]), err
|
||||
}
|
||||
}
|
||||
|
||||
if len(buf) > 2 && isUTF8BOM3(buf) {
|
||||
return UTF8, nilIfEmpty(buf[3:]), err
|
||||
}
|
||||
|
||||
if (err != nil && err != io.EOF) || (len(buf) < 2) {
|
||||
return Unknown, nilIfEmpty(buf), err
|
||||
}
|
||||
|
||||
if isUTF16BigEndianBOM2(buf) {
|
||||
return UTF16BigEndian, nilIfEmpty(buf[2:]), err
|
||||
}
|
||||
if isUTF16LittleEndianBOM2(buf) {
|
||||
return UTF16LittleEndian, nilIfEmpty(buf[2:]), err
|
||||
}
|
||||
|
||||
return Unknown, nilIfEmpty(buf), err
|
||||
}
|
||||
|
||||
func readBOM(rd io.Reader) (buf []byte, err error) {
|
||||
const maxBOMSize = 4
|
||||
var bom [maxBOMSize]byte // used to read BOM
|
||||
|
||||
// read as many bytes as possible
|
||||
for nEmpty, n := 0, 0; err == nil && len(buf) < maxBOMSize; buf = bom[:len(buf)+n] {
|
||||
if n, err = rd.Read(bom[len(buf):]); n < 0 {
|
||||
panic(errNegativeRead)
|
||||
}
|
||||
if n > 0 {
|
||||
nEmpty = 0
|
||||
} else {
|
||||
nEmpty++
|
||||
if nEmpty >= maxConsecutiveEmptyReads {
|
||||
err = io.ErrNoProgress
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isUTF32BigEndianBOM4(buf []byte) bool {
|
||||
return buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF
|
||||
}
|
||||
|
||||
func isUTF32LittleEndianBOM4(buf []byte) bool {
|
||||
return buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00
|
||||
}
|
||||
|
||||
func isUTF8BOM3(buf []byte) bool {
|
||||
return buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF
|
||||
}
|
||||
|
||||
func isUTF16BigEndianBOM2(buf []byte) bool {
|
||||
return buf[0] == 0xFE && buf[1] == 0xFF
|
||||
}
|
||||
|
||||
func isUTF16LittleEndianBOM2(buf []byte) bool {
|
||||
return buf[0] == 0xFF && buf[1] == 0xFE
|
||||
}
|
||||
|
||||
func nilIfEmpty(buf []byte) (res []byte) {
|
||||
if len(buf) > 0 {
|
||||
res = buf
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Tõnis Tiigi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -18,6 +18,7 @@ import (
|
|||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/dimchansky/utfbom"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
@ -79,7 +80,9 @@ func TryEnv(opt Opt) (*Cache, error) {
|
|||
}
|
||||
|
||||
type Opt struct {
|
||||
Client *http.Client
|
||||
Client *http.Client
|
||||
Timeout time.Duration
|
||||
BackoffPool *BackoffPool
|
||||
}
|
||||
|
||||
func New(token, url string, opt Opt) (*Cache, error) {
|
||||
|
@ -137,6 +140,13 @@ func New(token, url string, opt Opt) (*Cache, error) {
|
|||
if opt.Client == nil {
|
||||
opt.Client = http.DefaultClient
|
||||
}
|
||||
if opt.Timeout == 0 {
|
||||
opt.Timeout = 5 * time.Minute
|
||||
}
|
||||
|
||||
if opt.BackoffPool == nil {
|
||||
opt.BackoffPool = defaultBackoffPool
|
||||
}
|
||||
|
||||
return &Cache{
|
||||
opt: opt,
|
||||
|
@ -198,15 +208,11 @@ func (c *Cache) Load(ctx context.Context, keys ...string) (*Entry, error) {
|
|||
q.Set("keys", strings.Join(keys, ","))
|
||||
q.Set("version", version(keys[0]))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
req = req.WithContext(ctx)
|
||||
Log("load cache %s", req.URL.String())
|
||||
resp, err := c.opt.Client.Do(req)
|
||||
resp, err := c.doWithRetries(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if err := checkResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ce Entry
|
||||
dt, err := ioutil.ReadAll(io.LimitReader(resp.Body, 32*1024))
|
||||
if err != nil {
|
||||
|
@ -237,15 +243,11 @@ func (c *Cache) reserve(ctx context.Context, key string) (int, error) {
|
|||
c.auth(req)
|
||||
c.accept(req)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req = req.WithContext(ctx)
|
||||
Log("save cache req %s body=%s", req.URL.String(), dt)
|
||||
resp, err := c.opt.Client.Do(req)
|
||||
resp, err := c.doWithRetries(ctx, req)
|
||||
if err != nil {
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
if err := checkResponse(resp); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
dt, err = ioutil.ReadAll(io.LimitReader(resp.Body, 32*1024))
|
||||
if err != nil {
|
||||
|
@ -275,13 +277,10 @@ func (c *Cache) commit(ctx context.Context, id int, size int64) error {
|
|||
c.accept(req)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
Log("commit cache %s, size %d", req.URL.String(), size)
|
||||
resp, err := c.opt.Client.Do(req)
|
||||
resp, err := c.doWithRetries(ctx, req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error committing cache %d", id)
|
||||
}
|
||||
if err := checkResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
dt, err = ioutil.ReadAll(io.LimitReader(resp.Body, 32*1024))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -412,13 +411,10 @@ func (c *Cache) uploadChunk(ctx context.Context, id int, ra io.ReaderAt, off, n
|
|||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/*", off, off+n-1))
|
||||
|
||||
Log("upload cache chunk %s, range %d-%d", req.URL.String(), off, off+n-1)
|
||||
resp, err := c.opt.Client.Do(req)
|
||||
resp, err := c.doWithRetries(ctx, req)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := checkResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
dt, err := ioutil.ReadAll(io.LimitReader(resp.Body, 32*1024))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
|
@ -429,6 +425,38 @@ func (c *Cache) uploadChunk(ctx context.Context, id int, ra io.ReaderAt, off, n
|
|||
return resp.Body.Close()
|
||||
}
|
||||
|
||||
func (c *Cache) doWithRetries(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
req = req.WithContext(ctx)
|
||||
var err error
|
||||
max := time.Now().Add(c.opt.Timeout)
|
||||
for {
|
||||
if err1 := c.opt.BackoffPool.Wait(ctx, time.Until(max)); err1 != nil {
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "%v", err1)
|
||||
}
|
||||
return nil, err1
|
||||
}
|
||||
var resp *http.Response
|
||||
resp, err = c.opt.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if err := checkResponse(resp); err != nil {
|
||||
var he HTTPError
|
||||
if errors.As(err, &he) {
|
||||
if he.StatusCode == http.StatusTooManyRequests {
|
||||
c.opt.BackoffPool.Delay()
|
||||
continue
|
||||
}
|
||||
}
|
||||
c.opt.BackoffPool.Reset()
|
||||
return nil, err
|
||||
}
|
||||
c.opt.BackoffPool.Reset()
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) auth(r *http.Request) {
|
||||
r.Header.Add("Authorization", "Bearer "+c.Token.Raw)
|
||||
}
|
||||
|
@ -535,29 +563,47 @@ func (e GithubAPIError) Is(err error) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
type HTTPError struct {
|
||||
StatusCode int
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e HTTPError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return nil
|
||||
}
|
||||
dt, err := ioutil.ReadAll(io.LimitReader(resp.Body, 32*1024))
|
||||
dt, err := ioutil.ReadAll(utfbom.SkipOnly(io.LimitReader(resp.Body, 32*1024)))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
var gae GithubAPIError
|
||||
if err := json.Unmarshal(dt, &gae); err != nil {
|
||||
return errors.Wrapf(err, "failed to parse error response %d: %s", resp.StatusCode, dt)
|
||||
if err1 := json.Unmarshal(dt, &gae); err1 != nil {
|
||||
err = errors.Wrapf(err1, "failed to parse error response %d: %s", resp.StatusCode, dt)
|
||||
} else if gae.Message != "" {
|
||||
err = errors.WithStack(gae)
|
||||
} else {
|
||||
err = errors.Errorf("unknown error %s: %s", resp.Status, dt)
|
||||
}
|
||||
if gae.Message != "" {
|
||||
return errors.WithStack(gae)
|
||||
|
||||
return HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Err: err,
|
||||
}
|
||||
return errors.Errorf("unknown error %d: %s", resp.StatusCode, dt)
|
||||
}
|
||||
|
||||
func decryptToken(enc, pass string) (string, string, error) {
|
||||
// openssl key derivation uses some non-standard algorithm so exec instead of using go libraries
|
||||
// this is only used on testing anyway
|
||||
cmd := exec.Command("openssl", "enc", "-d", "-aes-256-cbc", "-a", "-A", "-salt", "-md", "sha256", "-pass", "env:GOCACHE_TOKEN_PW")
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("GOCACHE_TOKEN_PW=%s", pass))
|
||||
cmd := exec.Command("openssl", "enc", "-d", "-aes-256-cbc", "-a", "-A", "-salt", "-md", "sha256", "-pass", "env:GHCACHE_TOKEN_PW")
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("GHCACHE_TOKEN_PW=%s", pass))
|
||||
cmd.Stdin = bytes.NewReader([]byte(enc))
|
||||
buf := &bytes.Buffer{}
|
||||
cmd.Stdout = buf
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package actionscache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const maxBackoff = time.Second * 90
|
||||
const minBackoff = time.Second * 1
|
||||
|
||||
var defaultBackoffPool = &BackoffPool{}
|
||||
|
||||
type BackoffPool struct {
|
||||
mu sync.Mutex
|
||||
queue []chan struct{}
|
||||
timer *time.Timer
|
||||
backoff time.Duration
|
||||
target time.Time
|
||||
}
|
||||
|
||||
func (b *BackoffPool) Wait(ctx context.Context, timeout time.Duration) error {
|
||||
b.mu.Lock()
|
||||
if b.timer == nil {
|
||||
b.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
b.queue = append(b.queue, done)
|
||||
|
||||
b.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
case <-time.After(timeout):
|
||||
return errors.Errorf("maximum timeout reached")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BackoffPool) Reset() {
|
||||
b.mu.Lock()
|
||||
b.reset()
|
||||
b.backoff = 0
|
||||
b.mu.Unlock()
|
||||
}
|
||||
func (b *BackoffPool) reset() {
|
||||
for _, done := range b.queue {
|
||||
close(done)
|
||||
}
|
||||
b.queue = nil
|
||||
if b.timer != nil {
|
||||
b.timer.Stop()
|
||||
b.timer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BackoffPool) trigger(t *time.Timer) {
|
||||
b.mu.Lock()
|
||||
if b.timer != t {
|
||||
// this timer is not the current one
|
||||
b.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
b.reset()
|
||||
b.backoff = b.backoff * 2
|
||||
if b.backoff > maxBackoff {
|
||||
b.backoff = maxBackoff
|
||||
}
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
func (b *BackoffPool) Delay() {
|
||||
b.mu.Lock()
|
||||
if b.timer != nil {
|
||||
minTime := time.Now().Add(minBackoff)
|
||||
if b.target.Before(minTime) {
|
||||
b.target = minTime
|
||||
b.timer.Stop()
|
||||
b.setupTimer()
|
||||
}
|
||||
b.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if b.backoff == 0 {
|
||||
b.backoff = minBackoff
|
||||
}
|
||||
|
||||
b.target = time.Now().Add(b.backoff)
|
||||
b.setupTimer()
|
||||
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
func (b *BackoffPool) setupTimer() {
|
||||
var t *time.Timer
|
||||
b.timer = time.AfterFunc(time.Until(b.target), func() {
|
||||
b.trigger(t)
|
||||
})
|
||||
t = b.timer
|
||||
}
|
|
@ -201,6 +201,9 @@ github.com/davecgh/go-spew/spew
|
|||
# github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
## explicit
|
||||
github.com/dgrijalva/jwt-go
|
||||
# github.com/dimchansky/utfbom v1.1.1
|
||||
## explicit
|
||||
github.com/dimchansky/utfbom
|
||||
# github.com/docker/cli v20.10.8+incompatible
|
||||
## explicit
|
||||
github.com/docker/cli/cli/config
|
||||
|
@ -450,7 +453,7 @@ github.com/tonistiigi/fsutil
|
|||
github.com/tonistiigi/fsutil/copy
|
||||
github.com/tonistiigi/fsutil/prefix
|
||||
github.com/tonistiigi/fsutil/types
|
||||
# github.com/tonistiigi/go-actions-cache v0.0.0-20210714033416-b93d7f1b2e70
|
||||
# github.com/tonistiigi/go-actions-cache v0.0.0-20211002214948-4d48f2ff622a
|
||||
## explicit; go 1.16
|
||||
github.com/tonistiigi/go-actions-cache
|
||||
# github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
|
||||
|
|
|
@ -48,6 +48,7 @@ import (
|
|||
digest "github.com/opencontainers/go-digest"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
|
@ -346,6 +347,24 @@ func (w *Worker) Exporter(name string, sm *session.Manager) (exporter.Exporter,
|
|||
}
|
||||
|
||||
func (w *Worker) FromRemote(ctx context.Context, remote *solver.Remote) (ref cache.ImmutableRef, err error) {
|
||||
if cd, ok := remote.Provider.(interface {
|
||||
CheckDescriptor(context.Context, ocispecs.Descriptor) error
|
||||
}); ok && len(remote.Descriptors) > 0 {
|
||||
var eg errgroup.Group
|
||||
for _, desc := range remote.Descriptors {
|
||||
desc := desc
|
||||
eg.Go(func() error {
|
||||
if err := cd.CheckDescriptor(ctx, desc); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
descHandler := &cache.DescHandler{
|
||||
Provider: func(session.Group) content.Provider { return remote.Provider },
|
||||
Progress: &controller.Controller{
|
||||
|
|
Loading…
Reference in New Issue