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 }