Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix "Access is Denied" error on windows (with lock file) #4725

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/context/store/metadatastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
const (
metadataDir = "meta"
metaFile = "meta.json"
lockFile = "lock"
)

type metadataStore struct {
Expand Down
119 changes: 103 additions & 16 deletions cli/context/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import (
"bytes"
_ "crypto/sha256" // ensure ids can be computed
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"strings"

"github.com/docker/docker/errdefs"
"github.com/gofrs/flock"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)

const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$"
Expand Down Expand Up @@ -98,6 +101,7 @@ type ContextTLSData struct {
// If the directory does not exist or is empty, initialize it
func New(dir string, cfg Config) *ContextStore {
metaRoot := filepath.Join(dir, metadataDir)
lockPath := filepath.Join(dir, lockFile)
tlsRoot := filepath.Join(dir, tlsDir)

return &ContextStore{
Expand All @@ -108,17 +112,44 @@ func New(dir string, cfg Config) *ContextStore {
tls: &tlsStore{
root: tlsRoot,
},
lockFile: flock.New(lockPath),
}
}

// ContextStore implements Store.
type ContextStore struct {
meta *metadataStore
tls *tlsStore
meta *metadataStore
tls *tlsStore
lockFile *flock.Flock
}

func (s *ContextStore) lock() error {
if err := os.MkdirAll(filepath.Dir(s.lockFile.Path()), 0o755); err != nil {
return fmt.Errorf("creating context store lock directory: %w", err)
}
if err := s.lockFile.Lock(); err != nil {
return fmt.Errorf("locking context store lock: %w", err)
}
return nil
}

func (s *ContextStore) unlock() error {
if err := s.lockFile.Unlock(); err != nil {
return fmt.Errorf("unlocking context store lock: %w", err)
}
return nil
}

// List return all contexts.
func (s *ContextStore) List() ([]Metadata, error) {
func (s *ContextStore) List() (_ []Metadata, errs error) {
if err := s.lock(); err != nil {
return nil, err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
return s.meta.list()
}

Expand All @@ -136,30 +167,62 @@ func Names(s Lister) ([]string, error) {
}

// CreateOrUpdate creates or updates metadata for the context.
func (s *ContextStore) CreateOrUpdate(meta Metadata) error {
func (s *ContextStore) CreateOrUpdate(meta Metadata) (errs error) {
if err := s.lock(); err != nil {
return err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
return s.meta.createOrUpdate(meta)
}

// Remove deletes the context with the given name, if found.
func (s *ContextStore) Remove(name string) error {
func (s *ContextStore) Remove(name string) (errs error) {
if err := s.lock(); err != nil {
return err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
if err := s.meta.remove(name); err != nil {
return errors.Wrapf(err, "failed to remove context %s", name)
return fmt.Errorf("failed to remove context %s: %w", name, err)
}
if err := s.tls.remove(name); err != nil {
return errors.Wrapf(err, "failed to remove context %s", name)
return fmt.Errorf("failed to remove context %s: %w", name, err)
}
return nil
}

// GetMetadata returns the metadata for the context with the given name.
// It returns an errdefs.ErrNotFound if the context was not found.
func (s *ContextStore) GetMetadata(name string) (Metadata, error) {
func (s *ContextStore) GetMetadata(name string) (_ Metadata, errs error) {
if err := s.lock(); err != nil {
return Metadata{}, err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
return s.meta.get(name)
}

// ResetTLSMaterial removes TLS data for all endpoints in the context and replaces
// it with the new data.
func (s *ContextStore) ResetTLSMaterial(name string, data *ContextTLSData) error {
func (s *ContextStore) ResetTLSMaterial(name string, data *ContextTLSData) (errs error) {
if err := s.lock(); err != nil {
return err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
if err := s.tls.remove(name); err != nil {
return err
}
Expand All @@ -178,7 +241,15 @@ func (s *ContextStore) ResetTLSMaterial(name string, data *ContextTLSData) error

// ResetEndpointTLSMaterial removes TLS data for the given context and endpoint,
// and replaces it with the new data.
func (s *ContextStore) ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error {
func (s *ContextStore) ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) (errs error) {
if err := s.lock(); err != nil {
return err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
if err := s.tls.removeEndpoint(contextName, endpointName); err != nil {
return err
}
Expand All @@ -195,13 +266,29 @@ func (s *ContextStore) ResetEndpointTLSMaterial(contextName string, endpointName

// ListTLSFiles returns the list of TLS files present for each endpoint in the
// context.
func (s *ContextStore) ListTLSFiles(name string) (map[string]EndpointFiles, error) {
func (s *ContextStore) ListTLSFiles(name string) (_ map[string]EndpointFiles, errs error) {
if err := s.lock(); err != nil {
return nil, err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
return s.tls.listContextData(name)
}

// GetTLSData reads, and returns the content of the given fileName for an endpoint.
// It returns an errdefs.ErrNotFound if the file was not found.
func (s *ContextStore) GetTLSData(contextName, endpointName, fileName string) ([]byte, error) {
func (s *ContextStore) GetTLSData(contextName, endpointName, fileName string) (_ []byte, errs error) {
if err := s.lock(); err != nil {
return nil, err
}
defer func() {
if err := s.unlock(); err != nil {
errs = errors.Join(errs, err)
}
}()
return s.tls.getData(contextName, endpointName, fileName)
}

Expand All @@ -223,7 +310,7 @@ func ValidateContextName(name string) error {
return errors.New(`"default" is a reserved context name`)
}
if !restrictedNameRegEx.MatchString(name) {
return errors.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern)
return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern)
}
return nil
}
Expand Down Expand Up @@ -371,7 +458,7 @@ func importTar(name string, s Writer, reader io.Reader) error {
continue
}
if err := isValidFilePath(hdr.Name); err != nil {
return errors.Wrap(err, hdr.Name)
return fmt.Errorf("%s: %w", hdr.Name, err)
}
if hdr.Name == metaFile {
data, err := io.ReadAll(tr)
Expand Down Expand Up @@ -423,7 +510,7 @@ func importZip(name string, s Writer, reader io.Reader) error {
continue
}
if err := isValidFilePath(zf.Name); err != nil {
return errors.Wrap(err, zf.Name)
return fmt.Errorf("%s: %w", zf.Name, err)
}
if zf.Name == metaFile {
f, err := zf.Open()
Expand Down
9 changes: 9 additions & 0 deletions cli/context/store/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ import (
is "gotest.tools/v3/assert/cmp"
)

func TestNew(t *testing.T) {
s := New(path.Join(t.TempDir(), "does", "not", "exist", "yet"), testCfg)
assert.Assert(t, s != nil)
// Check that the file lock works even when the directory does not exist yet.
all, err := s.List()
assert.NilError(t, err)
assert.Assert(t, len(all) == 0)
}

type endpoint struct {
Foo string `json:"a_very_recognizable_field_name"`
}
Expand Down
4 changes: 2 additions & 2 deletions scripts/vendor
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ init() {
cat > go.mod <<EOL
module github.com/docker/cli

go 1.19
go 1.20
EOL
}

update() {
(set -x ; go mod tidy -compat=1.19 -modfile=vendor.mod; go mod vendor -modfile=vendor.mod)
(set -x ; go mod tidy -compat=1.20 -modfile=vendor.mod; go mod vendor -modfile=vendor.mod)
}

validate() {
Expand Down
3 changes: 2 additions & 1 deletion vendor.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module github.com/docker/cli
// There is no 'go.mod' file, as that would imply opting in for all the rules
// around SemVer, which this repo cannot abide by as it uses CalVer.

go 1.19
go 1.20

require (
dario.cat/mergo v1.0.0
Expand All @@ -17,6 +17,7 @@ require (
github.com/docker/go-connections v0.4.1-0.20231110212414-fa09c952e3ea
github.com/docker/go-units v0.5.0
github.com/fvbommel/sortorder v1.0.2
github.com/gofrs/flock v0.8.1
github.com/gogo/protobuf v1.3.2
github.com/google/go-cmp v0.5.9
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
Expand Down
2 changes: 2 additions & 0 deletions vendor.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
Expand Down
24 changes: 24 additions & 0 deletions vendor/github.com/gofrs/flock/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions vendor/github.com/gofrs/flock/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions vendor/github.com/gofrs/flock/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions vendor/github.com/gofrs/flock/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading