github.com/scaleway/scaleway-cli@v1.11.1/pkg/api/cache.go (about)

     1  // Copyright (C) 2015 Scaleway. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE.md file.
     4  
     5  package api
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"strings"
    15  	"sync"
    16  
    17  	"github.com/moul/anonuuid"
    18  	"github.com/renstrom/fuzzysearch/fuzzy"
    19  )
    20  
    21  const (
    22  	// CacheRegion permits to access at the region field
    23  	CacheRegion = iota
    24  	// CacheArch permits to access at the arch field
    25  	CacheArch
    26  	// CacheOwner permits to access at the owner field
    27  	CacheOwner
    28  	// CacheTitle permits to access at the title field
    29  	CacheTitle
    30  	// CacheMarketPlaceUUID is used to determine the UUID of local images
    31  	CacheMarketPlaceUUID
    32  	// CacheMaxfield is used to determine the size of array
    33  	CacheMaxfield
    34  )
    35  
    36  // ScalewayCache is used not to query the API to resolve full identifiers
    37  type ScalewayCache struct {
    38  	// Images contains names of Scaleway images indexed by identifier
    39  	Images map[string][CacheMaxfield]string `json:"images"`
    40  
    41  	// Snapshots contains names of Scaleway snapshots indexed by identifier
    42  	Snapshots map[string][CacheMaxfield]string `json:"snapshots"`
    43  
    44  	// Volumes contains names of Scaleway volumes indexed by identifier
    45  	Volumes map[string][CacheMaxfield]string `json:"volumes"`
    46  
    47  	// Bootscripts contains names of Scaleway bootscripts indexed by identifier
    48  	Bootscripts map[string][CacheMaxfield]string `json:"bootscripts"`
    49  
    50  	// Servers contains names of Scaleway servers indexed by identifier
    51  	Servers map[string][CacheMaxfield]string `json:"servers"`
    52  
    53  	// Path is the path to the cache file
    54  	Path string `json:"-"`
    55  
    56  	// Modified tells if the cache needs to be overwritten or not
    57  	Modified bool `json:"-"`
    58  
    59  	// Lock allows ScalewayCache to be used concurrently
    60  	Lock sync.Mutex `json:"-"`
    61  
    62  	hookSave func()
    63  }
    64  
    65  const (
    66  	// IdentifierUnknown is used when we don't know explicitly the type key of the object (used for nil comparison)
    67  	IdentifierUnknown = 1 << iota
    68  	// IdentifierServer is the type key of cached server objects
    69  	IdentifierServer
    70  	// IdentifierImage is the type key of cached image objects
    71  	IdentifierImage
    72  	// IdentifierSnapshot is the type key of cached snapshot objects
    73  	IdentifierSnapshot
    74  	// IdentifierBootscript is the type key of cached bootscript objects
    75  	IdentifierBootscript
    76  	// IdentifierVolume is the type key of cached volume objects
    77  	IdentifierVolume
    78  )
    79  
    80  // ScalewayResolverResult is a structure containing human-readable information
    81  // about resolver results. This structure is used to display the user choices.
    82  type ScalewayResolverResult struct {
    83  	Identifier string
    84  	Type       int
    85  	Name       string
    86  	Arch       string
    87  	Needle     string
    88  	RankMatch  int
    89  	Region     string
    90  }
    91  
    92  // ScalewayResolverResults is a list of `ScalewayResolverResult`
    93  type ScalewayResolverResults []ScalewayResolverResult
    94  
    95  // NewScalewayResolverResult returns a new ScalewayResolverResult
    96  func NewScalewayResolverResult(Identifier, Name, Arch, Region string, Type int) (ScalewayResolverResult, error) {
    97  	if err := anonuuid.IsUUID(Identifier); err != nil {
    98  		return ScalewayResolverResult{}, err
    99  	}
   100  	return ScalewayResolverResult{
   101  		Identifier: Identifier,
   102  		Type:       Type,
   103  		Name:       Name,
   104  		Arch:       Arch,
   105  		Region:     Region,
   106  	}, nil
   107  }
   108  
   109  func (s ScalewayResolverResults) Len() int {
   110  	return len(s)
   111  }
   112  
   113  func (s ScalewayResolverResults) Swap(i, j int) {
   114  	s[i], s[j] = s[j], s[i]
   115  }
   116  
   117  func (s ScalewayResolverResults) Less(i, j int) bool {
   118  	return s[i].RankMatch < s[j].RankMatch
   119  }
   120  
   121  // TruncIdentifier returns first 8 characters of an Identifier (UUID)
   122  func (s *ScalewayResolverResult) TruncIdentifier() string {
   123  	return s.Identifier[:8]
   124  }
   125  
   126  func identifierTypeName(kind int) string {
   127  	switch kind {
   128  	case IdentifierServer:
   129  		return "Server"
   130  	case IdentifierImage:
   131  		return "Image"
   132  	case IdentifierSnapshot:
   133  		return "Snapshot"
   134  	case IdentifierVolume:
   135  		return "Volume"
   136  	case IdentifierBootscript:
   137  		return "Bootscript"
   138  	}
   139  	return ""
   140  }
   141  
   142  // CodeName returns a full resource name with typed prefix
   143  func (s *ScalewayResolverResult) CodeName() string {
   144  	name := strings.ToLower(s.Name)
   145  	name = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(name, "-")
   146  	name = regexp.MustCompile(`--+`).ReplaceAllString(name, "-")
   147  	name = strings.Trim(name, "-")
   148  
   149  	return fmt.Sprintf("%s:%s", strings.ToLower(identifierTypeName(s.Type)), name)
   150  }
   151  
   152  // FilterByArch deletes the elements which not match with arch
   153  func (s *ScalewayResolverResults) FilterByArch(arch string) {
   154  REDO:
   155  	for i := range *s {
   156  		if (*s)[i].Arch != arch {
   157  			(*s)[i] = (*s)[len(*s)-1]
   158  			*s = (*s)[:len(*s)-1]
   159  			goto REDO
   160  		}
   161  	}
   162  }
   163  
   164  // NewScalewayCache loads a per-user cache
   165  func NewScalewayCache(hookSave func()) (*ScalewayCache, error) {
   166  	var cache ScalewayCache
   167  
   168  	cache.hookSave = hookSave
   169  	homeDir := os.Getenv("HOME") // *nix
   170  	if homeDir == "" {           // Windows
   171  		homeDir = os.Getenv("USERPROFILE")
   172  	}
   173  	if homeDir == "" {
   174  		homeDir = "/tmp"
   175  	}
   176  	cachePath := filepath.Join(homeDir, ".scw-cache.db")
   177  	cache.Path = cachePath
   178  	_, err := os.Stat(cachePath)
   179  	if os.IsNotExist(err) {
   180  		cache.Clear()
   181  		return &cache, nil
   182  	} else if err != nil {
   183  		return nil, err
   184  	}
   185  	file, err := ioutil.ReadFile(cachePath)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	err = json.Unmarshal(file, &cache)
   190  	if err != nil {
   191  		// fix compatibility with older version
   192  		if err = os.Remove(cachePath); err != nil {
   193  			return nil, err
   194  		}
   195  		cache.Clear()
   196  		return &cache, nil
   197  	}
   198  	if cache.Images == nil {
   199  		cache.Images = make(map[string][CacheMaxfield]string)
   200  	}
   201  	if cache.Snapshots == nil {
   202  		cache.Snapshots = make(map[string][CacheMaxfield]string)
   203  	}
   204  	if cache.Volumes == nil {
   205  		cache.Volumes = make(map[string][CacheMaxfield]string)
   206  	}
   207  	if cache.Servers == nil {
   208  		cache.Servers = make(map[string][CacheMaxfield]string)
   209  	}
   210  	if cache.Bootscripts == nil {
   211  		cache.Bootscripts = make(map[string][CacheMaxfield]string)
   212  	}
   213  	return &cache, nil
   214  }
   215  
   216  // Clear removes all information from the cache
   217  func (c *ScalewayCache) Clear() {
   218  	c.Images = make(map[string][CacheMaxfield]string)
   219  	c.Snapshots = make(map[string][CacheMaxfield]string)
   220  	c.Volumes = make(map[string][CacheMaxfield]string)
   221  	c.Bootscripts = make(map[string][CacheMaxfield]string)
   222  	c.Servers = make(map[string][CacheMaxfield]string)
   223  	c.Modified = true
   224  }
   225  
   226  // Flush flushes the cache database
   227  func (c *ScalewayCache) Flush() error {
   228  	return os.Remove(c.Path)
   229  }
   230  
   231  // Save atomically overwrites the current cache database
   232  func (c *ScalewayCache) Save() error {
   233  	c.Lock.Lock()
   234  	defer c.Lock.Unlock()
   235  
   236  	c.hookSave()
   237  	if c.Modified {
   238  		file, err := ioutil.TempFile(filepath.Dir(c.Path), filepath.Base(c.Path))
   239  		if err != nil {
   240  			return err
   241  		}
   242  		defer file.Close()
   243  		if err := json.NewEncoder(file).Encode(c); err != nil {
   244  			os.Remove(file.Name())
   245  			return err
   246  		}
   247  
   248  		if err := os.Rename(file.Name(), c.Path); err != nil {
   249  			os.Remove(file.Name())
   250  			return err
   251  		}
   252  	}
   253  	return nil
   254  }
   255  
   256  // ComputeRankMatch fills `ScalewayResolverResult.RankMatch` with its `fuzzy` score
   257  func (s *ScalewayResolverResult) ComputeRankMatch(needle string) {
   258  	s.Needle = needle
   259  	s.RankMatch = fuzzy.RankMatch(needle, s.Name)
   260  }
   261  
   262  // LookUpImages attempts to return identifiers matching a pattern
   263  func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
   264  	c.Lock.Lock()
   265  	defer c.Lock.Unlock()
   266  
   267  	var res ScalewayResolverResults
   268  	var exactMatches ScalewayResolverResults
   269  
   270  	if acceptUUID && anonuuid.IsUUID(needle) == nil {
   271  		if fields, ok := c.Images[needle]; ok {
   272  			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
   273  			if err != nil {
   274  				return ScalewayResolverResults{}, err
   275  			}
   276  			entry.ComputeRankMatch(needle)
   277  			res = append(res, entry)
   278  		}
   279  	}
   280  
   281  	needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
   282  	// FIXME: if 'user/' is in needle, only watch for a user image
   283  	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
   284  	for identifier, fields := range c.Images {
   285  		if fields[CacheTitle] == needle {
   286  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
   287  			if err != nil {
   288  				return ScalewayResolverResults{}, err
   289  			}
   290  			entry.ComputeRankMatch(needle)
   291  			exactMatches = append(exactMatches, entry)
   292  		}
   293  		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
   294  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
   295  			if err != nil {
   296  				return ScalewayResolverResults{}, err
   297  			}
   298  			entry.ComputeRankMatch(needle)
   299  			res = append(res, entry)
   300  		} else if strings.HasPrefix(fields[CacheMarketPlaceUUID], needle) || nameRegex.MatchString(fields[CacheMarketPlaceUUID]) {
   301  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
   302  			if err != nil {
   303  				return ScalewayResolverResults{}, err
   304  			}
   305  			entry.ComputeRankMatch(needle)
   306  			res = append(res, entry)
   307  		}
   308  	}
   309  
   310  	if len(exactMatches) == 1 {
   311  		return exactMatches, nil
   312  	}
   313  
   314  	return removeDuplicatesResults(res), nil
   315  }
   316  
   317  // LookUpSnapshots attempts to return identifiers matching a pattern
   318  func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
   319  	c.Lock.Lock()
   320  	defer c.Lock.Unlock()
   321  
   322  	var res ScalewayResolverResults
   323  	var exactMatches ScalewayResolverResults
   324  
   325  	if acceptUUID && anonuuid.IsUUID(needle) == nil {
   326  		if fields, ok := c.Snapshots[needle]; ok {
   327  			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
   328  			if err != nil {
   329  				return ScalewayResolverResults{}, err
   330  			}
   331  			entry.ComputeRankMatch(needle)
   332  			res = append(res, entry)
   333  		}
   334  	}
   335  
   336  	needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
   337  	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
   338  	for identifier, fields := range c.Snapshots {
   339  		if fields[CacheTitle] == needle {
   340  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
   341  			if err != nil {
   342  				return ScalewayResolverResults{}, err
   343  			}
   344  			entry.ComputeRankMatch(needle)
   345  			exactMatches = append(exactMatches, entry)
   346  		}
   347  		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
   348  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
   349  			if err != nil {
   350  				return ScalewayResolverResults{}, err
   351  			}
   352  			entry.ComputeRankMatch(needle)
   353  			res = append(res, entry)
   354  		}
   355  	}
   356  
   357  	if len(exactMatches) == 1 {
   358  		return exactMatches, nil
   359  	}
   360  
   361  	return removeDuplicatesResults(res), nil
   362  }
   363  
   364  // LookUpVolumes attempts to return identifiers matching a pattern
   365  func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
   366  	c.Lock.Lock()
   367  	defer c.Lock.Unlock()
   368  
   369  	var res ScalewayResolverResults
   370  	var exactMatches ScalewayResolverResults
   371  
   372  	if acceptUUID && anonuuid.IsUUID(needle) == nil {
   373  		if fields, ok := c.Volumes[needle]; ok {
   374  			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
   375  			if err != nil {
   376  				return ScalewayResolverResults{}, err
   377  			}
   378  			entry.ComputeRankMatch(needle)
   379  			res = append(res, entry)
   380  		}
   381  	}
   382  
   383  	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
   384  	for identifier, fields := range c.Volumes {
   385  		if fields[CacheTitle] == needle {
   386  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
   387  			if err != nil {
   388  				return ScalewayResolverResults{}, err
   389  			}
   390  			entry.ComputeRankMatch(needle)
   391  			exactMatches = append(exactMatches, entry)
   392  		}
   393  		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
   394  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
   395  			if err != nil {
   396  				return ScalewayResolverResults{}, err
   397  			}
   398  			entry.ComputeRankMatch(needle)
   399  			res = append(res, entry)
   400  		}
   401  	}
   402  
   403  	if len(exactMatches) == 1 {
   404  		return exactMatches, nil
   405  	}
   406  
   407  	return removeDuplicatesResults(res), nil
   408  }
   409  
   410  // LookUpBootscripts attempts to return identifiers matching a pattern
   411  func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
   412  	c.Lock.Lock()
   413  	defer c.Lock.Unlock()
   414  
   415  	var res ScalewayResolverResults
   416  	var exactMatches ScalewayResolverResults
   417  
   418  	if acceptUUID && anonuuid.IsUUID(needle) == nil {
   419  		if fields, ok := c.Bootscripts[needle]; ok {
   420  			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
   421  			if err != nil {
   422  				return ScalewayResolverResults{}, err
   423  			}
   424  			entry.ComputeRankMatch(needle)
   425  			res = append(res, entry)
   426  		}
   427  	}
   428  
   429  	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
   430  	for identifier, fields := range c.Bootscripts {
   431  		if fields[CacheTitle] == needle {
   432  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
   433  			if err != nil {
   434  				return ScalewayResolverResults{}, err
   435  			}
   436  			entry.ComputeRankMatch(needle)
   437  			exactMatches = append(exactMatches, entry)
   438  		}
   439  		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
   440  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
   441  			if err != nil {
   442  				return ScalewayResolverResults{}, err
   443  			}
   444  			entry.ComputeRankMatch(needle)
   445  			res = append(res, entry)
   446  		}
   447  	}
   448  
   449  	if len(exactMatches) == 1 {
   450  		return exactMatches, nil
   451  	}
   452  
   453  	return removeDuplicatesResults(res), nil
   454  }
   455  
   456  // LookUpServers attempts to return identifiers matching a pattern
   457  func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
   458  	c.Lock.Lock()
   459  	defer c.Lock.Unlock()
   460  
   461  	var res ScalewayResolverResults
   462  	var exactMatches ScalewayResolverResults
   463  
   464  	if acceptUUID && anonuuid.IsUUID(needle) == nil {
   465  		if fields, ok := c.Servers[needle]; ok {
   466  			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
   467  			if err != nil {
   468  				return ScalewayResolverResults{}, err
   469  			}
   470  			entry.ComputeRankMatch(needle)
   471  			res = append(res, entry)
   472  		}
   473  	}
   474  
   475  	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
   476  	for identifier, fields := range c.Servers {
   477  		if fields[CacheTitle] == needle {
   478  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
   479  			if err != nil {
   480  				return ScalewayResolverResults{}, err
   481  			}
   482  			entry.ComputeRankMatch(needle)
   483  			exactMatches = append(exactMatches, entry)
   484  		}
   485  		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
   486  			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
   487  			if err != nil {
   488  				return ScalewayResolverResults{}, err
   489  			}
   490  			entry.ComputeRankMatch(needle)
   491  			res = append(res, entry)
   492  		}
   493  	}
   494  
   495  	if len(exactMatches) == 1 {
   496  		return exactMatches, nil
   497  	}
   498  
   499  	return removeDuplicatesResults(res), nil
   500  }
   501  
   502  // removeDuplicatesResults transforms an array into a unique array
   503  func removeDuplicatesResults(elements ScalewayResolverResults) ScalewayResolverResults {
   504  	encountered := map[string]ScalewayResolverResult{}
   505  
   506  	// Create a map of all unique elements.
   507  	for v := range elements {
   508  		encountered[elements[v].Identifier] = elements[v]
   509  	}
   510  
   511  	// Place all keys from the map into a slice.
   512  	results := ScalewayResolverResults{}
   513  	for _, result := range encountered {
   514  		results = append(results, result)
   515  	}
   516  	return results
   517  }
   518  
   519  // parseNeedle parses a user needle and try to extract a forced object type
   520  // i.e:
   521  //   - server:blah-blah -> kind=server, needle=blah-blah
   522  //   - blah-blah -> kind="", needle=blah-blah
   523  //   - not-existing-type:blah-blah
   524  func parseNeedle(input string) (identifierType int, needle string) {
   525  	parts := strings.Split(input, ":")
   526  	if len(parts) == 2 {
   527  		switch parts[0] {
   528  		case "server":
   529  			return IdentifierServer, parts[1]
   530  		case "image":
   531  			return IdentifierImage, parts[1]
   532  		case "snapshot":
   533  			return IdentifierSnapshot, parts[1]
   534  		case "bootscript":
   535  			return IdentifierBootscript, parts[1]
   536  		case "volume":
   537  			return IdentifierVolume, parts[1]
   538  		}
   539  	}
   540  	return IdentifierUnknown, input
   541  }
   542  
   543  // LookUpIdentifiers attempts to return identifiers matching a pattern
   544  func (c *ScalewayCache) LookUpIdentifiers(needle string) (ScalewayResolverResults, error) {
   545  	results := ScalewayResolverResults{}
   546  
   547  	identifierType, needle := parseNeedle(needle)
   548  
   549  	if identifierType&(IdentifierUnknown|IdentifierServer) > 0 {
   550  		servers, err := c.LookUpServers(needle, false)
   551  		if err != nil {
   552  			return ScalewayResolverResults{}, err
   553  		}
   554  		for _, result := range servers {
   555  			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierServer)
   556  			if err != nil {
   557  				return ScalewayResolverResults{}, err
   558  			}
   559  			entry.ComputeRankMatch(needle)
   560  			results = append(results, entry)
   561  		}
   562  	}
   563  
   564  	if identifierType&(IdentifierUnknown|IdentifierImage) > 0 {
   565  		images, err := c.LookUpImages(needle, false)
   566  		if err != nil {
   567  			return ScalewayResolverResults{}, err
   568  		}
   569  		for _, result := range images {
   570  			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierImage)
   571  			if err != nil {
   572  				return ScalewayResolverResults{}, err
   573  			}
   574  			entry.ComputeRankMatch(needle)
   575  			results = append(results, entry)
   576  		}
   577  	}
   578  
   579  	if identifierType&(IdentifierUnknown|IdentifierSnapshot) > 0 {
   580  		snapshots, err := c.LookUpSnapshots(needle, false)
   581  		if err != nil {
   582  			return ScalewayResolverResults{}, err
   583  		}
   584  		for _, result := range snapshots {
   585  			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierSnapshot)
   586  			if err != nil {
   587  				return ScalewayResolverResults{}, err
   588  			}
   589  			entry.ComputeRankMatch(needle)
   590  			results = append(results, entry)
   591  		}
   592  	}
   593  
   594  	if identifierType&(IdentifierUnknown|IdentifierVolume) > 0 {
   595  		volumes, err := c.LookUpVolumes(needle, false)
   596  		if err != nil {
   597  			return ScalewayResolverResults{}, err
   598  		}
   599  		for _, result := range volumes {
   600  			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierVolume)
   601  			if err != nil {
   602  				return ScalewayResolverResults{}, err
   603  			}
   604  			entry.ComputeRankMatch(needle)
   605  			results = append(results, entry)
   606  		}
   607  	}
   608  
   609  	if identifierType&(IdentifierUnknown|IdentifierBootscript) > 0 {
   610  		bootscripts, err := c.LookUpBootscripts(needle, false)
   611  		if err != nil {
   612  			return ScalewayResolverResults{}, err
   613  		}
   614  		for _, result := range bootscripts {
   615  			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierBootscript)
   616  			if err != nil {
   617  				return ScalewayResolverResults{}, err
   618  			}
   619  			entry.ComputeRankMatch(needle)
   620  			results = append(results, entry)
   621  		}
   622  	}
   623  	return results, nil
   624  }
   625  
   626  // InsertServer registers a server in the cache
   627  func (c *ScalewayCache) InsertServer(identifier, region, arch, owner, name string) {
   628  	c.Lock.Lock()
   629  	defer c.Lock.Unlock()
   630  
   631  	fields, exists := c.Servers[identifier]
   632  	if !exists || fields[CacheTitle] != name {
   633  		c.Servers[identifier] = [CacheMaxfield]string{region, arch, owner, name}
   634  		c.Modified = true
   635  	}
   636  }
   637  
   638  // RemoveServer removes a server from the cache
   639  func (c *ScalewayCache) RemoveServer(identifier string) {
   640  	c.Lock.Lock()
   641  	defer c.Lock.Unlock()
   642  
   643  	delete(c.Servers, identifier)
   644  	c.Modified = true
   645  }
   646  
   647  // ClearServers removes all servers from the cache
   648  func (c *ScalewayCache) ClearServers() {
   649  	c.Lock.Lock()
   650  	defer c.Lock.Unlock()
   651  
   652  	c.Servers = make(map[string][CacheMaxfield]string)
   653  	c.Modified = true
   654  }
   655  
   656  // InsertImage registers an image in the cache
   657  func (c *ScalewayCache) InsertImage(identifier, region, arch, owner, name, marketPlaceUUID string) {
   658  	c.Lock.Lock()
   659  	defer c.Lock.Unlock()
   660  
   661  	fields, exists := c.Images[identifier]
   662  	if !exists || fields[CacheTitle] != name {
   663  		c.Images[identifier] = [CacheMaxfield]string{region, arch, owner, name, marketPlaceUUID}
   664  		c.Modified = true
   665  	}
   666  }
   667  
   668  // RemoveImage removes a server from the cache
   669  func (c *ScalewayCache) RemoveImage(identifier string) {
   670  	c.Lock.Lock()
   671  	defer c.Lock.Unlock()
   672  
   673  	delete(c.Images, identifier)
   674  	c.Modified = true
   675  }
   676  
   677  // ClearImages removes all images from the cache
   678  func (c *ScalewayCache) ClearImages() {
   679  	c.Lock.Lock()
   680  	defer c.Lock.Unlock()
   681  
   682  	c.Images = make(map[string][CacheMaxfield]string)
   683  	c.Modified = true
   684  }
   685  
   686  // InsertSnapshot registers an snapshot in the cache
   687  func (c *ScalewayCache) InsertSnapshot(identifier, region, arch, owner, name string) {
   688  	c.Lock.Lock()
   689  	defer c.Lock.Unlock()
   690  
   691  	fields, exists := c.Snapshots[identifier]
   692  	if !exists || fields[CacheTitle] != name {
   693  		c.Snapshots[identifier] = [CacheMaxfield]string{region, arch, owner, name}
   694  		c.Modified = true
   695  	}
   696  }
   697  
   698  // RemoveSnapshot removes a server from the cache
   699  func (c *ScalewayCache) RemoveSnapshot(identifier string) {
   700  	c.Lock.Lock()
   701  	defer c.Lock.Unlock()
   702  
   703  	delete(c.Snapshots, identifier)
   704  	c.Modified = true
   705  }
   706  
   707  // ClearSnapshots removes all snapshots from the cache
   708  func (c *ScalewayCache) ClearSnapshots() {
   709  	c.Lock.Lock()
   710  	defer c.Lock.Unlock()
   711  
   712  	c.Snapshots = make(map[string][CacheMaxfield]string)
   713  	c.Modified = true
   714  }
   715  
   716  // InsertVolume registers an volume in the cache
   717  func (c *ScalewayCache) InsertVolume(identifier, region, arch, owner, name string) {
   718  	c.Lock.Lock()
   719  	defer c.Lock.Unlock()
   720  
   721  	fields, exists := c.Volumes[identifier]
   722  	if !exists || fields[CacheTitle] != name {
   723  		c.Volumes[identifier] = [CacheMaxfield]string{region, arch, owner, name}
   724  		c.Modified = true
   725  	}
   726  }
   727  
   728  // RemoveVolume removes a server from the cache
   729  func (c *ScalewayCache) RemoveVolume(identifier string) {
   730  	c.Lock.Lock()
   731  	defer c.Lock.Unlock()
   732  
   733  	delete(c.Volumes, identifier)
   734  	c.Modified = true
   735  }
   736  
   737  // ClearVolumes removes all volumes from the cache
   738  func (c *ScalewayCache) ClearVolumes() {
   739  	c.Lock.Lock()
   740  	defer c.Lock.Unlock()
   741  
   742  	c.Volumes = make(map[string][CacheMaxfield]string)
   743  	c.Modified = true
   744  }
   745  
   746  // InsertBootscript registers an bootscript in the cache
   747  func (c *ScalewayCache) InsertBootscript(identifier, region, arch, owner, name string) {
   748  	c.Lock.Lock()
   749  	defer c.Lock.Unlock()
   750  
   751  	fields, exists := c.Bootscripts[identifier]
   752  	if !exists || fields[CacheTitle] != name {
   753  		c.Bootscripts[identifier] = [CacheMaxfield]string{region, arch, owner, name}
   754  		c.Modified = true
   755  	}
   756  }
   757  
   758  // RemoveBootscript removes a bootscript from the cache
   759  func (c *ScalewayCache) RemoveBootscript(identifier string) {
   760  	c.Lock.Lock()
   761  	defer c.Lock.Unlock()
   762  
   763  	delete(c.Bootscripts, identifier)
   764  	c.Modified = true
   765  }
   766  
   767  // ClearBootscripts removes all bootscripts from the cache
   768  func (c *ScalewayCache) ClearBootscripts() {
   769  	c.Lock.Lock()
   770  	defer c.Lock.Unlock()
   771  
   772  	c.Bootscripts = make(map[string][CacheMaxfield]string)
   773  	c.Modified = true
   774  }
   775  
   776  // GetNbServers returns the number of servers in the cache
   777  func (c *ScalewayCache) GetNbServers() int {
   778  	c.Lock.Lock()
   779  	defer c.Lock.Unlock()
   780  
   781  	return len(c.Servers)
   782  }
   783  
   784  // GetNbImages returns the number of images in the cache
   785  func (c *ScalewayCache) GetNbImages() int {
   786  	c.Lock.Lock()
   787  	defer c.Lock.Unlock()
   788  
   789  	return len(c.Images)
   790  }
   791  
   792  // GetNbSnapshots returns the number of snapshots in the cache
   793  func (c *ScalewayCache) GetNbSnapshots() int {
   794  	c.Lock.Lock()
   795  	defer c.Lock.Unlock()
   796  
   797  	return len(c.Snapshots)
   798  }
   799  
   800  // GetNbVolumes returns the number of volumes in the cache
   801  func (c *ScalewayCache) GetNbVolumes() int {
   802  	c.Lock.Lock()
   803  	defer c.Lock.Unlock()
   804  
   805  	return len(c.Volumes)
   806  }
   807  
   808  // GetNbBootscripts returns the number of bootscripts in the cache
   809  func (c *ScalewayCache) GetNbBootscripts() int {
   810  	c.Lock.Lock()
   811  	defer c.Lock.Unlock()
   812  
   813  	return len(c.Bootscripts)
   814  }