-
Notifications
You must be signed in to change notification settings - Fork 28
/
unit.go
145 lines (127 loc) · 4.12 KB
/
unit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package names
import (
"fmt"
"hash/crc32"
"regexp"
"strconv"
"strings"
"github.com/juju/errors"
)
const UnitTagKind = "unit"
// minShortenedLength defines minimum size of shortened unit tag, other things depend
// on that value so change it carefully.
const minShortenedLength = 21
// UnitSnippet defines the regexp for a valid Unit Id.
const UnitSnippet = "(" ApplicationSnippet ")/(" NumberSnippet ")"
var validUnit = regexp.MustCompile("^" UnitSnippet "$")
type UnitTag struct {
name string
}
func (t UnitTag) String() string { return t.Kind() "-" t.name }
func (t UnitTag) Kind() string { return UnitTagKind }
func (t UnitTag) Id() string { return unitTagSuffixToId(t.name) }
// Number returns the unit number from the tag, effectively the NumberSnippet from the
// validUnit regular expression.
func (t UnitTag) Number() int {
if i := strings.LastIndex(t.name, "-"); i > 0 {
num, _ := strconv.Atoi(t.name[i 1:])
return num
}
return 0
}
// NewUnitTag returns the tag for the unit with the given name.
// It will panic if the given unit name is not valid.
func NewUnitTag(unitName string) UnitTag {
tag, ok := tagFromUnitName(unitName)
if !ok {
panic(fmt.Sprintf("%q is not a valid unit name", unitName))
}
return tag
}
// ParseUnitTag parses a unit tag string.
func ParseUnitTag(unitTag string) (UnitTag, error) {
tag, err := ParseTag(unitTag)
if err != nil {
return UnitTag{}, err
}
ut, ok := tag.(UnitTag)
if !ok {
return UnitTag{}, invalidTagError(unitTag, UnitTagKind)
}
return ut, nil
}
// IsValidUnit returns whether name is a valid unit name.
func IsValidUnit(name string) bool {
return validUnit.MatchString(name)
}
// UnitApplication returns the name of the application that the unit is
// associated with. It returns an error if unitName is not a valid unit name.
func UnitApplication(unitName string) (string, error) {
s := validUnit.FindStringSubmatch(unitName)
if s == nil {
return "", fmt.Errorf("%q is not a valid unit name", unitName)
}
return s[1], nil
}
// UnitNumber returns the number of the unit within the
// application. It returns an error if unitName is not a valid unit
// name.
func UnitNumber(unitName string) (int, error) {
s := validUnit.FindStringSubmatch(unitName)
if s == nil {
return 0, fmt.Errorf("%q is not a valid unit name", unitName)
}
num, err := strconv.Atoi(s[2])
if err != nil {
// Shouldn't happen, the regexp checks for digits.
return 0, errors.Trace(err)
}
return num, nil
}
func tagFromUnitName(unitName string) (UnitTag, bool) {
// Replace only the last "/" with "-".
i := strings.LastIndex(unitName, "/")
if i <= 0 || !IsValidUnit(unitName) {
return UnitTag{}, false
}
unitName = unitName[:i] "-" unitName[i 1:]
return UnitTag{name: unitName}, true
}
func unitTagSuffixToId(s string) string {
// Replace only the last "-" with "/", as it is valid for application
// names to contain hyphens.
if i := strings.LastIndex(s, "-"); i > 0 {
s = s[:i] "/" s[i 1:]
}
return s
}
// ShortenedString returns the length-limited string for the tag.
// It can be used in places where there are strict length requirements, e.g. for
// a service name. It uses a hash so the resulting name should be unique.
// It will panic if maxLength is less than minShortenedLength.
func (t UnitTag) ShortenedString(maxLength int) (string, error) {
if maxLength < minShortenedLength {
return "", fmt.Errorf("max length must be at least %d, not %d", minShortenedLength, maxLength)
}
i := strings.LastIndex(t.name, "-")
if i <= 0 {
return "", fmt.Errorf("invalid tag %s", t.name)
}
// To keep unit 'name' the same on all units we reserve 4 chars for ID.
name, id := t.name[:i], t.name[i 1:]
idLen := len(id)
if idLen < 4 {
idLen = 4
}
var hashString string
// 8 for hash, 2 for two dashes
maxNameLength := maxLength - idLen - len(UnitTagKind) - 8 - 2
if len(name) > maxNameLength {
hash := crc32.Checksum([]byte(name), crc32.IEEETable)
hashString = fmt.Sprintf("%0.8x", hash)
name = name[:maxNameLength]
}
return "unit-" name hashString "-" id, nil
}