github.com/containerd/nerdctl@v1.7.7/pkg/mountutil/volumestore/volumestore.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package volumestore
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/containerd/containerd/identifiers"
    26  	"github.com/containerd/errdefs"
    27  	"github.com/containerd/nerdctl/pkg/inspecttypes/native"
    28  	"github.com/containerd/nerdctl/pkg/lockutil"
    29  	"github.com/containerd/nerdctl/pkg/strutil"
    30  )
    31  
    32  // Path returns a string like `/var/lib/nerdctl/1935db59/volumes/default`.
    33  func Path(dataStore, ns string) (string, error) {
    34  	if dataStore == "" || ns == "" {
    35  		return "", errdefs.ErrInvalidArgument
    36  	}
    37  	volStore := filepath.Join(dataStore, "volumes", ns)
    38  	return volStore, nil
    39  }
    40  
    41  // New returns a VolumeStore
    42  func New(dataStore, ns string) (VolumeStore, error) {
    43  	volStoreDir, err := Path(dataStore, ns)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	if err := os.MkdirAll(volStoreDir, 0700); err != nil {
    48  		return nil, err
    49  	}
    50  	vs := &volumeStore{
    51  		dir: volStoreDir,
    52  	}
    53  	return vs, nil
    54  }
    55  
    56  // DataDirName is "_data"
    57  const DataDirName = "_data"
    58  
    59  const volumeJSONFileName = "volume.json"
    60  
    61  type VolumeStore interface {
    62  	Dir() string
    63  	Create(name string, labels []string) (*native.Volume, error)
    64  	// Get may return ErrNotFound
    65  	Get(name string, size bool) (*native.Volume, error)
    66  	List(size bool) (map[string]native.Volume, error)
    67  	Remove(names []string) (removedNames []string, err error)
    68  }
    69  
    70  type volumeStore struct {
    71  	// dir is a string like `/var/lib/nerdctl/1935db59/volumes/default`.
    72  	// dir is guaranteed to exist.
    73  	dir string
    74  }
    75  
    76  func (vs *volumeStore) Dir() string {
    77  	return vs.dir
    78  }
    79  
    80  func (vs *volumeStore) Create(name string, labels []string) (*native.Volume, error) {
    81  	if err := identifiers.Validate(name); err != nil {
    82  		return nil, fmt.Errorf("malformed name %s: %w", name, err)
    83  	}
    84  	volPath := filepath.Join(vs.dir, name)
    85  	volDataPath := filepath.Join(volPath, DataDirName)
    86  	fn := func() error {
    87  		if err := os.Mkdir(volPath, 0700); err != nil {
    88  			return err
    89  		}
    90  		if err := os.Mkdir(volDataPath, 0755); err != nil {
    91  			return err
    92  		}
    93  
    94  		type volumeOpts struct {
    95  			Labels map[string]string `json:"labels"`
    96  		}
    97  
    98  		labelsMap := strutil.ConvertKVStringsToMap(labels)
    99  
   100  		volOpts := volumeOpts{
   101  			Labels: labelsMap,
   102  		}
   103  
   104  		labelsJSON, err := json.MarshalIndent(volOpts, "", "    ")
   105  		if err != nil {
   106  			return err
   107  		}
   108  
   109  		volFilePath := filepath.Join(volPath, volumeJSONFileName)
   110  		return os.WriteFile(volFilePath, labelsJSON, 0644)
   111  	}
   112  
   113  	if err := lockutil.WithDirLock(vs.dir, fn); err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	vol := &native.Volume{
   118  		Name:       name,
   119  		Mountpoint: volDataPath,
   120  	}
   121  	return vol, nil
   122  }
   123  
   124  func (vs *volumeStore) Get(name string, size bool) (*native.Volume, error) {
   125  	if err := identifiers.Validate(name); err != nil {
   126  		return nil, fmt.Errorf("malformed name %s: %w", name, err)
   127  	}
   128  	dataPath := filepath.Join(vs.dir, name, DataDirName)
   129  	if _, err := os.Stat(dataPath); err != nil {
   130  		if os.IsNotExist(err) {
   131  			return nil, fmt.Errorf("volume %q not found: %w", name, errdefs.ErrNotFound)
   132  		}
   133  		return nil, err
   134  	}
   135  
   136  	volFilePath := filepath.Join(vs.dir, name, volumeJSONFileName)
   137  	volumeDataBytes, err := os.ReadFile(volFilePath)
   138  	if err != nil {
   139  		if !os.IsNotExist(err) {
   140  			return nil, err
   141  		} // on else, volume.json does not exists should not be blocking for inspect operation
   142  	}
   143  
   144  	entry := native.Volume{
   145  		Name:       name,
   146  		Mountpoint: dataPath,
   147  		Labels:     Labels(volumeDataBytes),
   148  	}
   149  	if size {
   150  		entry.Size, err = Size(&entry)
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  	}
   155  	return &entry, nil
   156  }
   157  
   158  func (vs *volumeStore) List(size bool) (map[string]native.Volume, error) {
   159  	dEnts, err := os.ReadDir(vs.dir)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	res := make(map[string]native.Volume, len(dEnts))
   165  	for _, dEnt := range dEnts {
   166  		name := dEnt.Name()
   167  		vol, err := vs.Get(name, size)
   168  		if err != nil {
   169  			return res, err
   170  		}
   171  		res[name] = *vol
   172  	}
   173  	return res, nil
   174  }
   175  
   176  func (vs *volumeStore) Remove(names []string) ([]string, error) {
   177  	var removed []string
   178  	fn := func() error {
   179  		for _, name := range names {
   180  			if err := identifiers.Validate(name); err != nil {
   181  				return fmt.Errorf("malformed name %s: %w", name, err)
   182  			}
   183  			dir := filepath.Join(vs.dir, name)
   184  			if err := os.RemoveAll(dir); err != nil {
   185  				return err
   186  			}
   187  			removed = append(removed, name)
   188  		}
   189  		return nil
   190  	}
   191  	err := lockutil.WithDirLock(vs.dir, fn)
   192  	return removed, err
   193  }
   194  
   195  func Labels(b []byte) *map[string]string {
   196  	type volumeOpts struct {
   197  		Labels *map[string]string `json:"labels,omitempty"`
   198  	}
   199  	var vo volumeOpts
   200  	if err := json.Unmarshal(b, &vo); err != nil {
   201  		return nil
   202  	}
   203  	return vo.Labels
   204  }
   205  
   206  func Size(volume *native.Volume) (int64, error) {
   207  	var size int64
   208  	var walkFn = func(_ string, info os.FileInfo, err error) error {
   209  		if err != nil {
   210  			return err
   211  		}
   212  		if !info.IsDir() {
   213  			size += info.Size()
   214  		}
   215  		return err
   216  	}
   217  	var err = filepath.Walk(volume.Mountpoint, walkFn)
   218  	if err != nil {
   219  		return 0, err
   220  	}
   221  	return size, nil
   222  }