github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/lxdprofile/name.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxdprofile
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  )
    14  
    15  // AppName here is used as the application prefix name. We can't use names.Juju
    16  // as that changes depending on platform.
    17  const AppName = "juju"
    18  
    19  // Prefix is used to prefix all the lxd profile programmable profiles. If a
    20  // profile doesn't have the prefix, then it will be removed when ensuring the
    21  // the validity of the names (see LXDProfileNames)
    22  var Prefix = fmt.Sprintf("%s-", AppName)
    23  
    24  // Name returns a serialisable name that we can use to identify profiles
    25  // juju-<model>-<application>-<charm-revision>
    26  func Name(modelName, appName string, revision int) string {
    27  	return fmt.Sprintf("%s%s-%s-%d", Prefix, modelName, appName, revision)
    28  }
    29  
    30  // LXDProfileNames ensures that the LXD profile names are unique yet preserve
    31  // the same order as the input. It removes certain profile names from the list,
    32  // for example "default" profile name will be removed.
    33  func LXDProfileNames(names []string) []string {
    34  	// ensure that the ones we have are unique
    35  	unique := make(map[string]int)
    36  	for k, v := range names {
    37  		if !IsValidName(v) {
    38  			continue
    39  		}
    40  		unique[v] = k
    41  	}
    42  	i := 0
    43  	unordered := make([]nameIndex, len(unique))
    44  	for k, v := range unique {
    45  		unordered[i] = nameIndex{
    46  			Name:  k,
    47  			Index: v,
    48  		}
    49  		i++
    50  	}
    51  	sort.Slice(unordered, func(i, j int) bool {
    52  		return unordered[i].Index < unordered[j].Index
    53  	})
    54  	ordered := make([]string, len(unordered))
    55  	for k, v := range unordered {
    56  		ordered[k] = v.Name
    57  	}
    58  	return ordered
    59  }
    60  
    61  // IsValidName returns if the name of the lxd profile looks valid.
    62  func IsValidName(name string) bool {
    63  	// doesn't contain the prefix
    64  	if !strings.HasPrefix(name, Prefix) {
    65  		return false
    66  	}
    67  	// it's required to have at least the following chars `x-x-0`
    68  	suffix := name[len(Prefix):]
    69  	if len(suffix) < 5 {
    70  		return false
    71  	}
    72  	// lastly check the last part is a number
    73  	lastHyphen := strings.LastIndex(suffix, "-")
    74  	revision := suffix[lastHyphen+1:]
    75  	_, err := strconv.Atoi(revision)
    76  	return err == nil
    77  }
    78  
    79  type nameIndex struct {
    80  	Name  string
    81  	Index int
    82  }
    83  
    84  // ProfileRevision returns an int which is the charm revision of the given
    85  // profile name.
    86  func ProfileRevision(profile string) (int, error) {
    87  	if !IsValidName(profile) {
    88  		return 0, errors.BadRequestf("not a juju profile name: %q", profile)
    89  	}
    90  	split := strings.Split(profile, "-")
    91  	rev := split[len(split)-1:]
    92  	return strconv.Atoi(rev[0])
    93  }
    94  
    95  // ProfileReplaceRevision replaces the old revision with a new revision
    96  // in the profile.
    97  func ProfileReplaceRevision(profile string, rev int) (string, error) {
    98  	if !IsValidName(profile) {
    99  		return "", errors.BadRequestf("not a juju profile name: %q", profile)
   100  	}
   101  	split := strings.Split(profile, "-")
   102  	notRev := split[:len(split)-1]
   103  	return strings.Join(append(notRev, strconv.Itoa(rev)), "-"), nil
   104  }
   105  
   106  // MatchProfileNameByApp returns the first profile which matches the provided
   107  // appName.  No match returns an empty string.
   108  // Assumes there is not more than one profile for the same application.
   109  func MatchProfileNameByAppName(names []string, appName string) (string, error) {
   110  	if appName == "" {
   111  		return "", errors.BadRequestf("no application name specified")
   112  	}
   113  	var foundProfile string
   114  	for _, p := range LXDProfileNames(names) {
   115  		rev, err := ProfileRevision(p)
   116  		if err != nil {
   117  			// "Shouldn't" happen since we used LXDProfileNames...
   118  			if errors.IsBadRequest(err) {
   119  				continue
   120  			}
   121  			return "", err
   122  		}
   123  		if strings.HasSuffix(p, fmt.Sprintf("-%s-%d", appName, rev)) {
   124  			foundProfile = p
   125  			break
   126  		}
   127  	}
   128  	return foundProfile, nil
   129  }