Codebase list golang-github-appc-spec / c363301
Imported Upstream version 0.7.1+dfsg Dmitry Smirnov 8 years ago
68 changed file(s) with 2682 addition(s) and 230 deletion(s). Raw diff Collapse all Expand all
0 bin/
1 gopath/
2 *.sw[ponm]
11
22 language: go
33
4 go:
5 - 1.3
6 - 1.4
7
8 install:
9 - go get golang.org/x/tools/cmd/cover
10 - go get golang.org/x/tools/cmd/vet
4 matrix:
5 include:
6 - go: 1.3.3
7 install:
8 - go get golang.org/x/tools/cmd/cover
9 - go get golang.org/x/tools/cmd/vet
10 - go: 1.4.2
11 install:
12 - go get golang.org/x/tools/cmd/cover
13 - go get golang.org/x/tools/cmd/vet
14 - go: 1.5.1
1115
1216 script:
1317 - ./test
0 ### v0.7.1
1
2 Minor release of the spec with one critical bug/consistency fix and a few tooling enhancements.
3
4 0.7.0 introduced a field to the app section of ImageManifests to allow users to specify supplementary group IDs. Unfortunately, this was implemented with inconsistent naming: `supplementaryGids` in the text of the spec itself, but `supplementaryGroups` in the schema code.
5
6 In this release we standardise both the spec and the schema to `supplementaryGIDs`. See #516 for more information.
7
8 Other changes in this release:
9 - Added a callback to BuildWalker in the aci package to allow users to modify tar entries while building an ACI (#509)
10 - Added an `--owner-root` flag to acbuild to adjust the uid/gid of all files in an ACI to 0:0 (#509)
11 - Added a `--supplementary-gids` flag to actool's patch-manifest subcommand to adjust the supplementary group IDs of an ACI (#506, #516)
12 - Added the ability to extract labels in the `lastditch` package (#508)
13 - Changed the behaviour of the `NewAppFromString` parser in the `discovery` package to URL-encode label values before parsing them (#514)
14
15 ### v0.7.0
16
17 Next major release of the spec, with a lot of tooling improvements, wording clarifications, and one breaking schema change from the previous release.
18
19 Spec changes since v0.6.1:
20 - The `mount` objects in pod manifests now refer directly to a path rather than referencing an app's mountPoint. This makes it easier for implementations to provide supplementary, arbitrary mounts in pods without needing to modify app sections in the pod manifest. This is a breaking schema change; however, implementations are suggested to preserve similar behaviour to the previous three-level mount mappings by continuing to use mountPoints name fields to generate mount<->volume mappings (#364, #495)
21 - The resource/cpu unit has changed to cpu cores, instead of millicores (#468)
22 - The wording around unpacking of ACI dependencies was reworked to clarify the expected order of unpacking (#425, #431, #494)
23 - The wording around mounting volumes was expanded to advise much more explicit behaviour and protect various attack vectors for hosts (#431, #494)
24 - App sections now support a `supplementaryGroups` field, which allows users to specify a list of additional GIDs that the processes of the app should run with (#339)
25 - A new "Dependency Matching" section better explains the label matching process during dependency resolution (#469)
26 - Clarified wording around the "empty" volume type (#449)
27
28 Tooling changes and features:
29 - Added an `IsValidOSArch` function implementations can use
30 - When patching ACIs that do not have an `app` section in their manifest, actool will now automatically inject an App iff the user specifies an exec statement (#473, #489)
31 - actool now warns if a manifest's ACVersion is too old (#322)
32 - The NewCompressedReader function in the aci package now returns an io.ReadCloser instead of an io.Reader to facilitate closing the reader in the case of xz compression. (#462)
33 - Added a new last-ditch parser of pod and image manifests to facilitate retrieving some debugging information for badly-formed manifests (#477)
34
35 Schema and tooling bugfixes:
36 - actool now validates layouts before opening output file so that it does not create empty files or truncate existing ones if a layout is invalid (#322)
37 - Fixed a panic when `actool patch-manifest` is supplied with an ACI with no app section in its ImageManifest (#473)
38 - ACE validator's name is ACName compatible (#448)
39 - ACE validator's mountpoint checking is now more robust on Linux and works on FreeBSD (#467)
40 - `build_aci` now works if NO_SIGNATURE is set
41 - `build_aci` properly generates an armored signature (#460)
42 - Fixed a typo in the ACE validator (uid should be uuid) (#485)
43
44 Other changes:
45 - Rewrote the kubernetes import path in Godeps (#471)
46 - pkg/device is now buildable on OS X (#486)
47 - schema code is now tested against Go 1.5
48 - Added explicit reference to RFC2119 regarding wording
49
050 ### v0.6.1
151
252 Minor release of the spec; the most important change is adjusting the type for
4444
4545 While the specification prescribes that the Meta Discovery process occurs over HTTP(S), it is intentionally agnostic with respect to the transport over which the discovered resource can be retrieved.
4646 In the simplest cases (and as for example in Simple Discovery), the ACI payload can simply be retrieved over HTTPS itself.
47 However, in more advanced implementations - particulary in highly distributed environments - alternative protocols like [HDFS](hdfs) or [BitTorrent](bittorrent) could be used instead.
47 However, in more advanced implementations - particularly in highly distributed environments - alternative protocols like [HDFS](hdfs) or [BitTorrent](bittorrent) could be used instead.
4848
4949 [hdfs]: http://hadoop.apache.org/docs/r1.2.1/hdfs_design.html
5050 [bittorrent]: http://en.wikipedia.org/wiki/BitTorrent
0 {
1 "ImportPath": "github.com/appc/spec",
2 "GoVersion": "go1.4.1",
3 "Packages": [
4 "./..."
5 ],
6 "Deps": [
7 {
8 "ImportPath": "github.com/coreos/go-semver/semver",
9 "Rev": "6fe83ccda8fb9b7549c9ab4ba47f47858bc950aa"
10 },
11 {
12 "ImportPath": "github.com/spf13/pflag",
13 "Rev": "94e98a55fb412fcbcfc302555cb990f5e1590627"
14 },
15 {
16 "ImportPath": "golang.org/x/net/html",
17 "Rev": "ccfcd82c7124abd517842acbacc3b8c1e390c73d"
18 },
19 {
20 "ImportPath": "k8s.io/kubernetes/pkg/api/resource",
21 "Comment": "v1.0.2",
22 "Rev": "e310e619fc1ac4f3238bf5ebe9e7033bf5d47ee2"
23 },
24 {
25 "ImportPath": "speter.net/go/exp/math/dec/inf",
26 "Rev": "42ca6cd68aa922bc3f32f1e056e61b65945d9ad7"
27 }
28 ]
29 }
0 This directory tree is generated automatically by godep.
1
2 Please do not edit.
3
4 See https://github.com/tools/godep for more information.
0 /*
1 Copyright 2014 The Kubernetes Authors All rights reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 */
15
16 package resource
17
18 import (
19 "errors"
20 "fmt"
21 "math/big"
22 "regexp"
23 "strings"
24
25 flag "github.com/appc/spec/Godeps/_workspace/src/github.com/spf13/pflag"
26 "github.com/appc/spec/Godeps/_workspace/src/speter.net/go/exp/math/dec/inf"
27 )
28
29 // Quantity is a fixed-point representation of a number.
30 // It provides convenient marshaling/unmarshaling in JSON and YAML,
31 // in addition to String() and Int64() accessors.
32 //
33 // The serialization format is:
34 //
35 // <quantity> ::= <signedNumber><suffix>
36 // (Note that <suffix> may be empty, from the "" case in <decimalSI>.)
37 // <digit> ::= 0 | 1 | ... | 9
38 // <digits> ::= <digit> | <digit><digits>
39 // <number> ::= <digits> | <digits>.<digits> | <digits>. | .<digits>
40 // <sign> ::= "+" | "-"
41 // <signedNumber> ::= <number> | <sign><number>
42 // <suffix> ::= <binarySI> | <decimalExponent> | <decimalSI>
43 // <binarySI> ::= Ki | Mi | Gi | Ti | Pi | Ei
44 // (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)
45 // <decimalSI> ::= m | "" | k | M | G | T | P | E
46 // (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)
47 // <decimalExponent> ::= "e" <signedNumber> | "E" <signedNumber>
48 //
49 // No matter which of the three exponent forms is used, no quantity may represent
50 // a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal
51 // places. Numbers larger or more precise will be capped or rounded up.
52 // (E.g.: 0.1m will rounded up to 1m.)
53 // This may be extended in the future if we require larger or smaller quantities.
54 //
55 // When a Quantity is parsed from a string, it will remember the type of suffix
56 // it had, and will use the same type again when it is serialized.
57 //
58 // Before serializing, Quantity will be put in "canonical form".
59 // This means that Exponent/suffix will be adjusted up or down (with a
60 // corresponding increase or decrease in Mantissa) such that:
61 // a. No precision is lost
62 // b. No fractional digits will be emitted
63 // c. The exponent (or suffix) is as large as possible.
64 // The sign will be omitted unless the number is negative.
65 //
66 // Examples:
67 // 1.5 will be serialized as "1500m"
68 // 1.5Gi will be serialized as "1536Mi"
69 //
70 // NOTE: We reserve the right to amend this canonical format, perhaps to
71 // allow 1.5 to be canonical.
72 // TODO: Remove above disclaimer after all bikeshedding about format is over,
73 // or after March 2015.
74 //
75 // Note that the quantity will NEVER be internally represented by a
76 // floating point number. That is the whole point of this exercise.
77 //
78 // Non-canonical values will still parse as long as they are well formed,
79 // but will be re-emitted in their canonical form. (So always use canonical
80 // form, or don't diff.)
81 //
82 // This format is intended to make it difficult to use these numbers without
83 // writing some sort of special handling code in the hopes that that will
84 // cause implementors to also use a fixed point implementation.
85 type Quantity struct {
86 // Amount is public, so you can manipulate it if the accessor
87 // functions are not sufficient.
88 Amount *inf.Dec
89
90 // Change Format at will. See the comment for Canonicalize for
91 // more details.
92 Format
93 }
94
95 // Format lists the three possible formattings of a quantity.
96 type Format string
97
98 const (
99 DecimalExponent = Format("DecimalExponent") // e.g., 12e6
100 BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20)
101 DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6)
102 )
103
104 // MustParse turns the given string into a quantity or panics; for tests
105 // or others cases where you know the string is valid.
106 func MustParse(str string) Quantity {
107 q, err := ParseQuantity(str)
108 if err != nil {
109 panic(fmt.Errorf("cannot parse '%v': %v", str, err))
110 }
111 return *q
112 }
113
114 const (
115 // splitREString is used to separate a number from its suffix; as such,
116 // this is overly permissive, but that's OK-- it will be checked later.
117 splitREString = "^([+-]?[0-9.]+)([eEimkKMGTP]*[-+]?[0-9]*)$"
118 )
119
120 var (
121 // splitRE is used to get the various parts of a number.
122 splitRE = regexp.MustCompile(splitREString)
123
124 // Errors that could happen while parsing a string.
125 ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'")
126 ErrNumeric = errors.New("unable to parse numeric part of quantity")
127 ErrSuffix = errors.New("unable to parse quantity's suffix")
128
129 // Commonly needed big.Int values-- treat as read only!
130 bigTen = big.NewInt(10)
131 bigZero = big.NewInt(0)
132 bigOne = big.NewInt(1)
133 bigThousand = big.NewInt(1000)
134 big1024 = big.NewInt(1024)
135
136 // Commonly needed inf.Dec values-- treat as read only!
137 decZero = inf.NewDec(0, 0)
138 decOne = inf.NewDec(1, 0)
139 decMinusOne = inf.NewDec(-1, 0)
140 decThousand = inf.NewDec(1000, 0)
141 dec1024 = inf.NewDec(1024, 0)
142 decMinus1024 = inf.NewDec(-1024, 0)
143
144 // Largest (in magnitude) number allowed.
145 maxAllowed = inf.NewDec((1<<63)-1, 0) // == max int64
146
147 // The maximum value we can represent milli-units for.
148 // Compare with the return value of Quantity.Value() to
149 // see if it's safe to use Quantity.MilliValue().
150 MaxMilliValue = int64(((1 << 63) - 1) / 1000)
151 )
152
153 // ParseQuantity turns str into a Quantity, or returns an error.
154 func ParseQuantity(str string) (*Quantity, error) {
155 parts := splitRE.FindStringSubmatch(strings.TrimSpace(str))
156 // regexp returns are entire match, followed by an entry for each () section.
157 if len(parts) != 3 {
158 return nil, ErrFormatWrong
159 }
160
161 amount := new(inf.Dec)
162 if _, ok := amount.SetString(parts[1]); !ok {
163 return nil, ErrNumeric
164 }
165
166 base, exponent, format, ok := quantitySuffixer.interpret(suffix(parts[2]))
167 if !ok {
168 return nil, ErrSuffix
169 }
170
171 // So that no one but us has to think about suffixes, remove it.
172 if base == 10 {
173 amount.SetScale(amount.Scale() + inf.Scale(-exponent))
174 } else if base == 2 {
175 // numericSuffix = 2 ** exponent
176 numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent))
177 ub := amount.UnscaledBig()
178 amount.SetUnscaledBig(ub.Mul(ub, numericSuffix))
179 }
180
181 // Cap at min/max bounds.
182 sign := amount.Sign()
183 if sign == -1 {
184 amount.Neg(amount)
185 }
186 // This rounds non-zero values up to the minimum representable
187 // value, under the theory that if you want some resources, you
188 // should get some resources, even if you asked for way too small
189 // of an amount.
190 // Arguably, this should be inf.RoundHalfUp (normal rounding), but
191 // that would have the side effect of rounding values < .5m to zero.
192 if v, ok := amount.Unscaled(); v != int64(0) || !ok {
193 amount.Round(amount, 3, inf.RoundUp)
194 }
195
196 // The max is just a simple cap.
197 if amount.Cmp(maxAllowed) > 0 {
198 amount.Set(maxAllowed)
199 }
200 if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 {
201 // This avoids rounding and hopefully confusion, too.
202 format = DecimalSI
203 }
204 if sign == -1 {
205 amount.Neg(amount)
206 }
207
208 return &Quantity{amount, format}, nil
209 }
210
211 // removeFactors divides in a loop; the return values have the property that
212 // d == result * factor ^ times
213 // d may be modified in place.
214 // If d == 0, then the return values will be (0, 0)
215 func removeFactors(d, factor *big.Int) (result *big.Int, times int) {
216 q := big.NewInt(0)
217 m := big.NewInt(0)
218 for d.Cmp(bigZero) != 0 {
219 q.DivMod(d, factor, m)
220 if m.Cmp(bigZero) != 0 {
221 break
222 }
223 times++
224 d, q = q, d
225 }
226 return d, times
227 }
228
229 // Canonicalize returns the canonical form of q and its suffix (see comment on Quantity).
230 //
231 // Note about BinarySI:
232 // * If q.Format is set to BinarySI and q.Amount represents a non-zero value between
233 // -1 and +1, it will be emitted as if q.Format were DecimalSI.
234 // * Otherwise, if q.Format is set to BinarySI, frational parts of q.Amount will be
235 // rounded up. (1.1i becomes 2i.)
236 func (q *Quantity) Canonicalize() (string, suffix) {
237 if q.Amount == nil {
238 return "0", ""
239 }
240
241 // zero is zero always
242 if q.Amount.Cmp(&inf.Dec{}) == 0 {
243 return "0", ""
244 }
245
246 format := q.Format
247 switch format {
248 case DecimalExponent, DecimalSI:
249 case BinarySI:
250 if q.Amount.Cmp(decMinus1024) > 0 && q.Amount.Cmp(dec1024) < 0 {
251 // This avoids rounding and hopefully confusion, too.
252 format = DecimalSI
253 } else {
254 tmp := &inf.Dec{}
255 tmp.Round(q.Amount, 0, inf.RoundUp)
256 if tmp.Cmp(q.Amount) != 0 {
257 // Don't lose precision-- show as DecimalSI
258 format = DecimalSI
259 }
260 }
261 default:
262 format = DecimalExponent
263 }
264
265 // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to
266 // one of the other formats.
267 switch format {
268 case DecimalExponent, DecimalSI:
269 mantissa := q.Amount.UnscaledBig()
270 exponent := int(-q.Amount.Scale())
271 amount := big.NewInt(0).Set(mantissa)
272 // move all factors of 10 into the exponent for easy reasoning
273 amount, times := removeFactors(amount, bigTen)
274 exponent += times
275
276 // make sure exponent is a multiple of 3
277 for exponent%3 != 0 {
278 amount.Mul(amount, bigTen)
279 exponent--
280 }
281
282 suffix, _ := quantitySuffixer.construct(10, exponent, format)
283 number := amount.String()
284 return number, suffix
285 case BinarySI:
286 tmp := &inf.Dec{}
287 tmp.Round(q.Amount, 0, inf.RoundUp)
288
289 amount, exponent := removeFactors(tmp.UnscaledBig(), big1024)
290 suffix, _ := quantitySuffixer.construct(2, exponent*10, format)
291 number := amount.String()
292 return number, suffix
293 }
294 return "0", ""
295 }
296
297 // String formats the Quantity as a string.
298 func (q *Quantity) String() string {
299 number, suffix := q.Canonicalize()
300 return number + string(suffix)
301 }
302
303 // MarshalJSON implements the json.Marshaller interface.
304 func (q Quantity) MarshalJSON() ([]byte, error) {
305 return []byte(`"` + q.String() + `"`), nil
306 }
307
308 // UnmarshalJSON implements the json.Unmarshaller interface.
309 func (q *Quantity) UnmarshalJSON(value []byte) error {
310 str := string(value)
311 parsed, err := ParseQuantity(strings.Trim(str, `"`))
312 if err != nil {
313 return err
314 }
315 // This copy is safe because parsed will not be referred to again.
316 *q = *parsed
317 return nil
318 }
319
320 // NewQuantity returns a new Quantity representing the given
321 // value in the given format.
322 func NewQuantity(value int64, format Format) *Quantity {
323 return &Quantity{
324 Amount: inf.NewDec(value, 0),
325 Format: format,
326 }
327 }
328
329 // NewMilliQuantity returns a new Quantity representing the given
330 // value * 1/1000 in the given format. Note that BinarySI formatting
331 // will round fractional values, and will be changed to DecimalSI for
332 // values x where (-1 < x < 1) && (x != 0).
333 func NewMilliQuantity(value int64, format Format) *Quantity {
334 return &Quantity{
335 Amount: inf.NewDec(value, 3),
336 Format: format,
337 }
338 }
339
340 // Value returns the value of q; any fractional part will be lost.
341 func (q *Quantity) Value() int64 {
342 if q.Amount == nil {
343 return 0
344 }
345 tmp := &inf.Dec{}
346 return tmp.Round(q.Amount, 0, inf.RoundUp).UnscaledBig().Int64()
347 }
348
349 // MilliValue returns the value of q * 1000; this could overflow an int64;
350 // if that's a concern, call Value() first to verify the number is small enough.
351 func (q *Quantity) MilliValue() int64 {
352 if q.Amount == nil {
353 return 0
354 }
355 tmp := &inf.Dec{}
356 return tmp.Round(tmp.Mul(q.Amount, decThousand), 0, inf.RoundUp).UnscaledBig().Int64()
357 }
358
359 // Set sets q's value to be value.
360 func (q *Quantity) Set(value int64) {
361 if q.Amount == nil {
362 q.Amount = &inf.Dec{}
363 }
364 q.Amount.SetUnscaled(value)
365 q.Amount.SetScale(0)
366 }
367
368 // SetMilli sets q's value to be value * 1/1000.
369 func (q *Quantity) SetMilli(value int64) {
370 if q.Amount == nil {
371 q.Amount = &inf.Dec{}
372 }
373 q.Amount.SetUnscaled(value)
374 q.Amount.SetScale(3)
375 }
376
377 // Copy is a convenience function that makes a deep copy for you. Non-deep
378 // copies of quantities share pointers and you will regret that.
379 func (q *Quantity) Copy() *Quantity {
380 if q.Amount == nil {
381 return NewQuantity(0, q.Format)
382 }
383 tmp := &inf.Dec{}
384 return &Quantity{
385 Amount: tmp.Set(q.Amount),
386 Format: q.Format,
387 }
388 }
389
390 // qFlag is a helper type for the Flag function
391 type qFlag struct {
392 dest *Quantity
393 }
394
395 // Sets the value of the internal Quantity. (used by flag & pflag)
396 func (qf qFlag) Set(val string) error {
397 q, err := ParseQuantity(val)
398 if err != nil {
399 return err
400 }
401 // This copy is OK because q will not be referenced again.
402 *qf.dest = *q
403 return nil
404 }
405
406 // Converts the value of the internal Quantity to a string. (used by flag & pflag)
407 func (qf qFlag) String() string {
408 return qf.dest.String()
409 }
410
411 // States the type of flag this is (Quantity). (used by pflag)
412 func (qf qFlag) Type() string {
413 return "quantity"
414 }
415
416 // QuantityFlag is a helper that makes a quantity flag (using standard flag package).
417 // Will panic if defaultValue is not a valid quantity.
418 func QuantityFlag(flagName, defaultValue, description string) *Quantity {
419 q := MustParse(defaultValue)
420 flag.Var(NewQuantityFlagValue(&q), flagName, description)
421 return &q
422 }
423
424 // NewQuantityFlagValue returns an object that can be used to back a flag,
425 // pointing at the given Quantity variable.
426 func NewQuantityFlagValue(q *Quantity) flag.Value {
427 return qFlag{q}
428 }
0 /*
1 Copyright 2014 The Kubernetes Authors All rights reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 */
15
16 package resource_test
17
18 import (
19 "fmt"
20
21 "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
22 )
23
24 func ExampleFormat() {
25 memorySize := resource.NewQuantity(5*1024*1024*1024, resource.BinarySI)
26 fmt.Printf("memorySize = %v\n", memorySize)
27
28 diskSize := resource.NewQuantity(5*1000*1000*1000, resource.DecimalSI)
29 fmt.Printf("diskSize = %v\n", diskSize)
30
31 cores := resource.NewMilliQuantity(5300, resource.DecimalSI)
32 fmt.Printf("cores = %v\n", cores)
33
34 // Output:
35 // memorySize = 5Gi
36 // diskSize = 5G
37 // cores = 5300m
38 }
39
40 func ExampleMustParse() {
41 memorySize := resource.MustParse("5Gi")
42 fmt.Printf("memorySize = %v (%v)\n", memorySize.Value(), memorySize.Format)
43
44 diskSize := resource.MustParse("5G")
45 fmt.Printf("diskSize = %v (%v)\n", diskSize.Value(), diskSize.Format)
46
47 cores := resource.MustParse("5300m")
48 fmt.Printf("milliCores = %v (%v)\n", cores.MilliValue(), cores.Format)
49
50 cores2 := resource.MustParse("5.4")
51 fmt.Printf("milliCores = %v (%v)\n", cores2.MilliValue(), cores2.Format)
52
53 // Output:
54 // memorySize = 5368709120 (BinarySI)
55 // diskSize = 5000000000 (DecimalSI)
56 // milliCores = 5300 (DecimalSI)
57 // milliCores = 5400 (DecimalSI)
58 }
0 /*
1 Copyright 2014 The Kubernetes Authors All rights reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 */
15
16 package resource
17
18 import (
19 //"reflect"
20 "encoding/json"
21 "testing"
22
23 "github.com/appc/spec/Godeps/_workspace/src/github.com/spf13/pflag"
24 "github.com/appc/spec/Godeps/_workspace/src/speter.net/go/exp/math/dec/inf"
25 fuzz "github.com/google/gofuzz"
26 )
27
28 var (
29 testQuantityFlag = QuantityFlag("quantityFlag", "1M", "dummy flag for testing the quantity flag mechanism")
30 )
31
32 func dec(i int64, exponent int) *inf.Dec {
33 // See the below test-- scale is the negative of an exponent.
34 return inf.NewDec(i, inf.Scale(-exponent))
35 }
36
37 func TestDec(t *testing.T) {
38 table := []struct {
39 got *inf.Dec
40 expect string
41 }{
42 {dec(1, 0), "1"},
43 {dec(1, 1), "10"},
44 {dec(5, 2), "500"},
45 {dec(8, 3), "8000"},
46 {dec(2, 0), "2"},
47 {dec(1, -1), "0.1"},
48 {dec(3, -2), "0.03"},
49 {dec(4, -3), "0.004"},
50 }
51
52 for _, item := range table {
53 if e, a := item.expect, item.got.String(); e != a {
54 t.Errorf("expected %v, got %v", e, a)
55 }
56 }
57 }
58
59 // TestQuantityParseZero ensures that when a 0 quantity is passed, its string value is 0
60 func TestQuantityParseZero(t *testing.T) {
61 zero := MustParse("0")
62 if expected, actual := "0", zero.String(); expected != actual {
63 t.Errorf("Expected %v, actual %v", expected, actual)
64 }
65 }
66
67 // Verifies that you get 0 as canonical value if internal value is 0, and not 0<suffix>
68 func TestQuantityCanocicalizeZero(t *testing.T) {
69 val := MustParse("1000m")
70 x := val.Amount
71 y := dec(1, 0)
72 z := val.Amount.Sub(x, y)
73 zero := Quantity{z, DecimalSI}
74 if expected, actual := "0", zero.String(); expected != actual {
75 t.Errorf("Expected %v, actual %v", expected, actual)
76 }
77 }
78
79 func TestQuantityParse(t *testing.T) {
80 table := []struct {
81 input string
82 expect Quantity
83 }{
84 {"0", Quantity{dec(0, 0), DecimalSI}},
85 {"0m", Quantity{dec(0, 0), DecimalSI}},
86 {"0Ki", Quantity{dec(0, 0), BinarySI}},
87 {"0k", Quantity{dec(0, 0), DecimalSI}},
88 {"0Mi", Quantity{dec(0, 0), BinarySI}},
89 {"0M", Quantity{dec(0, 0), DecimalSI}},
90 {"0Gi", Quantity{dec(0, 0), BinarySI}},
91 {"0G", Quantity{dec(0, 0), DecimalSI}},
92 {"0Ti", Quantity{dec(0, 0), BinarySI}},
93 {"0T", Quantity{dec(0, 0), DecimalSI}},
94
95 // Binary suffixes
96 {"1Ki", Quantity{dec(1024, 0), BinarySI}},
97 {"8Ki", Quantity{dec(8*1024, 0), BinarySI}},
98 {"7Mi", Quantity{dec(7*1024*1024, 0), BinarySI}},
99 {"6Gi", Quantity{dec(6*1024*1024*1024, 0), BinarySI}},
100 {"5Ti", Quantity{dec(5*1024*1024*1024*1024, 0), BinarySI}},
101 {"4Pi", Quantity{dec(4*1024*1024*1024*1024*1024, 0), BinarySI}},
102 {"3Ei", Quantity{dec(3*1024*1024*1024*1024*1024*1024, 0), BinarySI}},
103
104 {"10Ti", Quantity{dec(10*1024*1024*1024*1024, 0), BinarySI}},
105 {"100Ti", Quantity{dec(100*1024*1024*1024*1024, 0), BinarySI}},
106
107 // Decimal suffixes
108 {"3m", Quantity{dec(3, -3), DecimalSI}},
109 {"9", Quantity{dec(9, 0), DecimalSI}},
110 {"8k", Quantity{dec(8, 3), DecimalSI}},
111 {"7M", Quantity{dec(7, 6), DecimalSI}},
112 {"6G", Quantity{dec(6, 9), DecimalSI}},
113 {"5T", Quantity{dec(5, 12), DecimalSI}},
114 {"40T", Quantity{dec(4, 13), DecimalSI}},
115 {"300T", Quantity{dec(3, 14), DecimalSI}},
116 {"2P", Quantity{dec(2, 15), DecimalSI}},
117 {"1E", Quantity{dec(1, 18), DecimalSI}},
118
119 // Decimal exponents
120 {"1E-3", Quantity{dec(1, -3), DecimalExponent}},
121 {"1e3", Quantity{dec(1, 3), DecimalExponent}},
122 {"1E6", Quantity{dec(1, 6), DecimalExponent}},
123 {"1e9", Quantity{dec(1, 9), DecimalExponent}},
124 {"1E12", Quantity{dec(1, 12), DecimalExponent}},
125 {"1e15", Quantity{dec(1, 15), DecimalExponent}},
126 {"1E18", Quantity{dec(1, 18), DecimalExponent}},
127
128 // Nonstandard but still parsable
129 {"1e14", Quantity{dec(1, 14), DecimalExponent}},
130 {"1e13", Quantity{dec(1, 13), DecimalExponent}},
131 {"1e3", Quantity{dec(1, 3), DecimalExponent}},
132 {"100.035k", Quantity{dec(100035, 0), DecimalSI}},
133
134 // Things that look like floating point
135 {"0.001", Quantity{dec(1, -3), DecimalSI}},
136 {"0.0005k", Quantity{dec(5, -1), DecimalSI}},
137 {"0.005", Quantity{dec(5, -3), DecimalSI}},
138 {"0.05", Quantity{dec(5, -2), DecimalSI}},
139 {"0.5", Quantity{dec(5, -1), DecimalSI}},
140 {"0.00050k", Quantity{dec(5, -1), DecimalSI}},
141 {"0.00500", Quantity{dec(5, -3), DecimalSI}},
142 {"0.05000", Quantity{dec(5, -2), DecimalSI}},
143 {"0.50000", Quantity{dec(5, -1), DecimalSI}},
144 {"0.5e0", Quantity{dec(5, -1), DecimalExponent}},
145 {"0.5e-1", Quantity{dec(5, -2), DecimalExponent}},
146 {"0.5e-2", Quantity{dec(5, -3), DecimalExponent}},
147 {"0.5e0", Quantity{dec(5, -1), DecimalExponent}},
148 {"10.035M", Quantity{dec(10035, 3), DecimalSI}},
149
150 {"1.2e3", Quantity{dec(12, 2), DecimalExponent}},
151 {"1.3E+6", Quantity{dec(13, 5), DecimalExponent}},
152 {"1.40e9", Quantity{dec(14, 8), DecimalExponent}},
153 {"1.53E12", Quantity{dec(153, 10), DecimalExponent}},
154 {"1.6e15", Quantity{dec(16, 14), DecimalExponent}},
155 {"1.7E18", Quantity{dec(17, 17), DecimalExponent}},
156
157 {"9.01", Quantity{dec(901, -2), DecimalSI}},
158 {"8.1k", Quantity{dec(81, 2), DecimalSI}},
159 {"7.123456M", Quantity{dec(7123456, 0), DecimalSI}},
160 {"6.987654321G", Quantity{dec(6987654321, 0), DecimalSI}},
161 {"5.444T", Quantity{dec(5444, 9), DecimalSI}},
162 {"40.1T", Quantity{dec(401, 11), DecimalSI}},
163 {"300.2T", Quantity{dec(3002, 11), DecimalSI}},
164 {"2.5P", Quantity{dec(25, 14), DecimalSI}},
165 {"1.01E", Quantity{dec(101, 16), DecimalSI}},
166
167 // Things that saturate/round
168 {"3.001m", Quantity{dec(4, -3), DecimalSI}},
169 {"1.1E-3", Quantity{dec(2, -3), DecimalExponent}},
170 {"0.0001", Quantity{dec(1, -3), DecimalSI}},
171 {"0.0005", Quantity{dec(1, -3), DecimalSI}},
172 {"0.00050", Quantity{dec(1, -3), DecimalSI}},
173 {"0.5e-3", Quantity{dec(1, -3), DecimalExponent}},
174 {"0.9m", Quantity{dec(1, -3), DecimalSI}},
175 {"0.12345", Quantity{dec(124, -3), DecimalSI}},
176 {"0.12354", Quantity{dec(124, -3), DecimalSI}},
177 {"9Ei", Quantity{maxAllowed, BinarySI}},
178 {"9223372036854775807Ki", Quantity{maxAllowed, BinarySI}},
179 {"12E", Quantity{maxAllowed, DecimalSI}},
180
181 // We'll accept fractional binary stuff, too.
182 {"100.035Ki", Quantity{dec(10243584, -2), BinarySI}},
183 {"0.5Mi", Quantity{dec(.5*1024*1024, 0), BinarySI}},
184 {"0.05Gi", Quantity{dec(536870912, -1), BinarySI}},
185 {"0.025Ti", Quantity{dec(274877906944, -1), BinarySI}},
186
187 // Things written by trolls
188 {"0.000001Ki", Quantity{dec(2, -3), DecimalSI}}, // rounds up, changes format
189 {".001", Quantity{dec(1, -3), DecimalSI}},
190 {".0001k", Quantity{dec(100, -3), DecimalSI}},
191 {"1.", Quantity{dec(1, 0), DecimalSI}},
192 {"1.G", Quantity{dec(1, 9), DecimalSI}},
193 }
194
195 for _, item := range table {
196 got, err := ParseQuantity(item.input)
197 if err != nil {
198 t.Errorf("%v: unexpected error: %v", item.input, err)
199 continue
200 }
201 if e, a := item.expect.Amount, got.Amount; e.Cmp(a) != 0 {
202 t.Errorf("%v: expected %v, got %v", item.input, e, a)
203 }
204 if e, a := item.expect.Format, got.Format; e != a {
205 t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
206 }
207 }
208
209 // Try the negative version of everything
210 desired := &inf.Dec{}
211 for _, item := range table {
212 got, err := ParseQuantity("-" + item.input)
213 if err != nil {
214 t.Errorf("-%v: unexpected error: %v", item.input, err)
215 continue
216 }
217 desired.Neg(item.expect.Amount)
218 if e, a := desired, got.Amount; e.Cmp(a) != 0 {
219 t.Errorf("%v: expected %v, got %v", item.input, e, a)
220 }
221 if e, a := item.expect.Format, got.Format; e != a {
222 t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
223 }
224 }
225
226 // Try everything with an explicit +
227 for _, item := range table {
228 got, err := ParseQuantity("+" + item.input)
229 if err != nil {
230 t.Errorf("-%v: unexpected error: %v", item.input, err)
231 continue
232 }
233 if e, a := item.expect.Amount, got.Amount; e.Cmp(a) != 0 {
234 t.Errorf("%v: expected %v, got %v", item.input, e, a)
235 }
236 if e, a := item.expect.Format, got.Format; e != a {
237 t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
238 }
239 }
240
241 invalid := []string{
242 "1.1.M",
243 "1+1.0M",
244 "0.1mi",
245 "0.1am",
246 "aoeu",
247 ".5i",
248 "1i",
249 "-3.01i",
250 }
251 for _, item := range invalid {
252 _, err := ParseQuantity(item)
253 if err == nil {
254 t.Errorf("%v parsed unexpectedly", item)
255 }
256 }
257 }
258
259 func TestQuantityString(t *testing.T) {
260 table := []struct {
261 in Quantity
262 expect string
263 }{
264 {Quantity{dec(1024*1024*1024, 0), BinarySI}, "1Gi"},
265 {Quantity{dec(300*1024*1024, 0), BinarySI}, "300Mi"},
266 {Quantity{dec(6*1024, 0), BinarySI}, "6Ki"},
267 {Quantity{dec(1001*1024*1024*1024, 0), BinarySI}, "1001Gi"},
268 {Quantity{dec(1024*1024*1024*1024, 0), BinarySI}, "1Ti"},
269 {Quantity{dec(5, 0), BinarySI}, "5"},
270 {Quantity{dec(500, -3), BinarySI}, "500m"},
271 {Quantity{dec(1, 9), DecimalSI}, "1G"},
272 {Quantity{dec(1000, 6), DecimalSI}, "1G"},
273 {Quantity{dec(1000000, 3), DecimalSI}, "1G"},
274 {Quantity{dec(1000000000, 0), DecimalSI}, "1G"},
275 {Quantity{dec(1, -3), DecimalSI}, "1m"},
276 {Quantity{dec(80, -3), DecimalSI}, "80m"},
277 {Quantity{dec(1080, -3), DecimalSI}, "1080m"},
278 {Quantity{dec(108, -2), DecimalSI}, "1080m"},
279 {Quantity{dec(10800, -4), DecimalSI}, "1080m"},
280 {Quantity{dec(300, 6), DecimalSI}, "300M"},
281 {Quantity{dec(1, 12), DecimalSI}, "1T"},
282 {Quantity{dec(1234567, 6), DecimalSI}, "1234567M"},
283 {Quantity{dec(1234567, -3), BinarySI}, "1234567m"},
284 {Quantity{dec(3, 3), DecimalSI}, "3k"},
285 {Quantity{dec(1025, 0), BinarySI}, "1025"},
286 {Quantity{dec(0, 0), DecimalSI}, "0"},
287 {Quantity{dec(0, 0), BinarySI}, "0"},
288 {Quantity{dec(1, 9), DecimalExponent}, "1e9"},
289 {Quantity{dec(1, -3), DecimalExponent}, "1e-3"},
290 {Quantity{dec(80, -3), DecimalExponent}, "80e-3"},
291 {Quantity{dec(300, 6), DecimalExponent}, "300e6"},
292 {Quantity{dec(1, 12), DecimalExponent}, "1e12"},
293 {Quantity{dec(1, 3), DecimalExponent}, "1e3"},
294 {Quantity{dec(3, 3), DecimalExponent}, "3e3"},
295 {Quantity{dec(3, 3), DecimalSI}, "3k"},
296 {Quantity{dec(0, 0), DecimalExponent}, "0"},
297 }
298 for _, item := range table {
299 got := item.in.String()
300 if e, a := item.expect, got; e != a {
301 t.Errorf("%#v: expected %v, got %v", item.in, e, a)
302 }
303 }
304 desired := &inf.Dec{} // Avoid modifying the values in the table.
305 for _, item := range table {
306 if item.in.Amount.Cmp(decZero) == 0 {
307 // Don't expect it to print "-0" ever
308 continue
309 }
310 q := item.in
311 q.Amount = desired.Neg(q.Amount)
312 if e, a := "-"+item.expect, q.String(); e != a {
313 t.Errorf("%#v: expected %v, got %v", item.in, e, a)
314 }
315 }
316 }
317
318 func TestQuantityParseEmit(t *testing.T) {
319 table := []struct {
320 in string
321 expect string
322 }{
323 {"1Ki", "1Ki"},
324 {"1Mi", "1Mi"},
325 {"1Gi", "1Gi"},
326 {"1024Mi", "1Gi"},
327 {"1000M", "1G"},
328 {".000001Ki", "2m"},
329 }
330
331 for _, item := range table {
332 q, err := ParseQuantity(item.in)
333 if err != nil {
334 t.Errorf("Couldn't parse %v", item.in)
335 continue
336 }
337 if e, a := item.expect, q.String(); e != a {
338 t.Errorf("%#v: expected %v, got %v", item.in, e, a)
339 }
340 }
341 for _, item := range table {
342 q, err := ParseQuantity("-" + item.in)
343 if err != nil {
344 t.Errorf("Couldn't parse %v", item.in)
345 continue
346 }
347 if q.Amount.Cmp(decZero) == 0 {
348 continue
349 }
350 if e, a := "-"+item.expect, q.String(); e != a {
351 t.Errorf("%#v: expected %v, got %v", item.in, e, a)
352 }
353 }
354 }
355
356 var fuzzer = fuzz.New().Funcs(
357 func(q *Quantity, c fuzz.Continue) {
358 q.Amount = &inf.Dec{}
359 if c.RandBool() {
360 q.Format = BinarySI
361 if c.RandBool() {
362 q.Amount.SetScale(0)
363 q.Amount.SetUnscaled(c.Int63())
364 return
365 }
366 // Be sure to test cases like 1Mi
367 q.Amount.SetScale(0)
368 q.Amount.SetUnscaled(c.Int63n(1024) << uint(10*c.Intn(5)))
369 return
370 }
371 if c.RandBool() {
372 q.Format = DecimalSI
373 } else {
374 q.Format = DecimalExponent
375 }
376 if c.RandBool() {
377 q.Amount.SetScale(inf.Scale(c.Intn(4)))
378 q.Amount.SetUnscaled(c.Int63())
379 return
380 }
381 // Be sure to test cases like 1M
382 q.Amount.SetScale(inf.Scale(3 - c.Intn(15)))
383 q.Amount.SetUnscaled(c.Int63n(1000))
384 },
385 )
386
387 func TestJSON(t *testing.T) {
388 for i := 0; i < 500; i++ {
389 q := &Quantity{}
390 fuzzer.Fuzz(q)
391 b, err := json.Marshal(q)
392 if err != nil {
393 t.Errorf("error encoding %v", q)
394 }
395 q2 := &Quantity{}
396 err = json.Unmarshal(b, q2)
397 if err != nil {
398 t.Errorf("%v: error decoding %v", q, string(b))
399 }
400 if q2.Amount.Cmp(q.Amount) != 0 {
401 t.Errorf("Expected equal: %v, %v (json was '%v')", q, q2, string(b))
402 }
403 }
404 }
405
406 func TestMilliNewSet(t *testing.T) {
407 table := []struct {
408 value int64
409 format Format
410 expect string
411 exact bool
412 }{
413 {1, DecimalSI, "1m", true},
414 {1000, DecimalSI, "1", true},
415 {1234000, DecimalSI, "1234", true},
416 {1024, BinarySI, "1024m", false}, // Format changes
417 {1000000, "invalidFormatDefaultsToExponent", "1e3", true},
418 {1024 * 1024, BinarySI, "1048576m", false}, // Format changes
419 }
420
421 for _, item := range table {
422 q := NewMilliQuantity(item.value, item.format)
423 if e, a := item.expect, q.String(); e != a {
424 t.Errorf("Expected %v, got %v; %#v", e, a, q)
425 }
426 if !item.exact {
427 continue
428 }
429 q2, err := ParseQuantity(q.String())
430 if err != nil {
431 t.Errorf("Round trip failed on %v", q)
432 }
433 if e, a := item.value, q2.MilliValue(); e != a {
434 t.Errorf("Expected %v, got %v", e, a)
435 }
436 }
437
438 for _, item := range table {
439 q := NewQuantity(0, item.format)
440 q.SetMilli(item.value)
441 if e, a := item.expect, q.String(); e != a {
442 t.Errorf("Set: Expected %v, got %v; %#v", e, a, q)
443 }
444 }
445 }
446
447 func TestNewSet(t *testing.T) {
448 table := []struct {
449 value int64
450 format Format
451 expect string
452 }{
453 {1, DecimalSI, "1"},
454 {1000, DecimalSI, "1k"},
455 {1234000, DecimalSI, "1234k"},
456 {1024, BinarySI, "1Ki"},
457 {1000000, "invalidFormatDefaultsToExponent", "1e6"},
458 {1024 * 1024, BinarySI, "1Mi"},
459 }
460
461 for _, item := range table {
462 q := NewQuantity(item.value, item.format)
463 if e, a := item.expect, q.String(); e != a {
464 t.Errorf("Expected %v, got %v; %#v", e, a, q)
465 }
466 q2, err := ParseQuantity(q.String())
467 if err != nil {
468 t.Errorf("Round trip failed on %v", q)
469 }
470 if e, a := item.value, q2.Value(); e != a {
471 t.Errorf("Expected %v, got %v", e, a)
472 }
473 }
474
475 for _, item := range table {
476 q := NewQuantity(0, item.format)
477 q.Set(item.value)
478 if e, a := item.expect, q.String(); e != a {
479 t.Errorf("Set: Expected %v, got %v; %#v", e, a, q)
480 }
481 }
482 }
483
484 func TestUninitializedNoCrash(t *testing.T) {
485 var q Quantity
486
487 q.Value()
488 q.MilliValue()
489 q.Copy()
490 q.String()
491 q.MarshalJSON()
492 }
493
494 func TestCopy(t *testing.T) {
495 q := NewQuantity(5, DecimalSI)
496 c := q.Copy()
497 c.Set(6)
498 if q.Value() == 6 {
499 t.Errorf("Copy didn't")
500 }
501 }
502
503 func TestQFlagSet(t *testing.T) {
504 qf := qFlag{&Quantity{}}
505 qf.Set("1Ki")
506 if e, a := "1Ki", qf.String(); e != a {
507 t.Errorf("Unexpected result %v != %v", e, a)
508 }
509 }
510
511 func TestQFlagIsPFlag(t *testing.T) {
512 var pfv pflag.Value = qFlag{}
513 if e, a := "quantity", pfv.Type(); e != a {
514 t.Errorf("Unexpected result %v != %v", e, a)
515 }
516 }
0 /*
1 Copyright 2014 The Kubernetes Authors All rights reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 */
15
16 package resource
17
18 import (
19 "strconv"
20 )
21
22 type suffix string
23
24 // suffixer can interpret and construct suffixes.
25 type suffixer interface {
26 interpret(suffix) (base, exponent int, fmt Format, ok bool)
27 construct(base, exponent int, fmt Format) (s suffix, ok bool)
28 }
29
30 // quantitySuffixer handles suffixes for all three formats that quantity
31 // can handle.
32 var quantitySuffixer = newSuffixer()
33
34 type bePair struct {
35 base, exponent int
36 }
37
38 type listSuffixer struct {
39 suffixToBE map[suffix]bePair
40 beToSuffix map[bePair]suffix
41 }
42
43 func (ls *listSuffixer) addSuffix(s suffix, pair bePair) {
44 if ls.suffixToBE == nil {
45 ls.suffixToBE = map[suffix]bePair{}
46 }
47 if ls.beToSuffix == nil {
48 ls.beToSuffix = map[bePair]suffix{}
49 }
50 ls.suffixToBE[s] = pair
51 ls.beToSuffix[pair] = s
52 }
53
54 func (ls *listSuffixer) lookup(s suffix) (base, exponent int, ok bool) {
55 pair, ok := ls.suffixToBE[s]
56 if !ok {
57 return 0, 0, false
58 }
59 return pair.base, pair.exponent, true
60 }
61
62 func (ls *listSuffixer) construct(base, exponent int) (s suffix, ok bool) {
63 s, ok = ls.beToSuffix[bePair{base, exponent}]
64 return
65 }
66
67 type suffixHandler struct {
68 decSuffixes listSuffixer
69 binSuffixes listSuffixer
70 }
71
72 func newSuffixer() suffixer {
73 sh := &suffixHandler{}
74
75 sh.binSuffixes.addSuffix("Ki", bePair{2, 10})
76 sh.binSuffixes.addSuffix("Mi", bePair{2, 20})
77 sh.binSuffixes.addSuffix("Gi", bePair{2, 30})
78 sh.binSuffixes.addSuffix("Ti", bePair{2, 40})
79 sh.binSuffixes.addSuffix("Pi", bePair{2, 50})
80 sh.binSuffixes.addSuffix("Ei", bePair{2, 60})
81 // Don't emit an error when trying to produce
82 // a suffix for 2^0.
83 sh.decSuffixes.addSuffix("", bePair{2, 0})
84
85 sh.decSuffixes.addSuffix("m", bePair{10, -3})
86 sh.decSuffixes.addSuffix("", bePair{10, 0})
87 sh.decSuffixes.addSuffix("k", bePair{10, 3})
88 sh.decSuffixes.addSuffix("M", bePair{10, 6})
89 sh.decSuffixes.addSuffix("G", bePair{10, 9})
90 sh.decSuffixes.addSuffix("T", bePair{10, 12})
91 sh.decSuffixes.addSuffix("P", bePair{10, 15})
92 sh.decSuffixes.addSuffix("E", bePair{10, 18})
93
94 return sh
95 }
96
97 func (sh *suffixHandler) construct(base, exponent int, fmt Format) (s suffix, ok bool) {
98 switch fmt {
99 case DecimalSI:
100 return sh.decSuffixes.construct(base, exponent)
101 case BinarySI:
102 return sh.binSuffixes.construct(base, exponent)
103 case DecimalExponent:
104 if base != 10 {
105 return "", false
106 }
107 if exponent == 0 {
108 return "", true
109 }
110 return suffix("e" + strconv.FormatInt(int64(exponent), 10)), true
111 }
112 return "", false
113 }
114
115 func (sh *suffixHandler) interpret(suffix suffix) (base, exponent int, fmt Format, ok bool) {
116 // Try lookup tables first
117 if b, e, ok := sh.decSuffixes.lookup(suffix); ok {
118 return b, e, DecimalSI, true
119 }
120 if b, e, ok := sh.binSuffixes.lookup(suffix); ok {
121 return b, e, BinarySI, true
122 }
123
124 if len(suffix) > 1 && (suffix[0] == 'E' || suffix[0] == 'e') {
125 parsed, err := strconv.ParseInt(string(suffix[1:]), 10, 64)
126 if err != nil {
127 return 0, 0, DecimalExponent, false
128 }
129 return 10, int(parsed), DecimalExponent, true
130 }
131
132 return 0, 0, DecimalExponent, false
133 }
00 # App Container
11
22 [![Build Status](https://travis-ci.org/appc/spec.png?branch=master)](https://travis-ci.org/appc/spec)
3
4 ![appc logo](logos/appc-horizontal-color.png)
35
46 This repository contains schema definitions and tools for the App Container (appc) specification.
57 These include technical details on how an appc image is downloaded over a network, cryptographically verified, and executed on a host.
3739
3840 - [goaci](https://github.com/appc/goaci) - ACI builder for Go projects
3941 - [docker2aci](https://github.com/appc/docker2aci) - ACI builder from Docker images
40 - [actool](https://github.com/appc/spec/tree/master/actool) - ACI builder from root filesystems
42 - [deb2aci](https://github.com/klizhentas/deb2aci) - ACI builder from Debian packages
43 - [actool](https://github.com/appc/spec/tree/master/actool) - Simple tool to assemble ACIs from root filesystems
44 - [acbuild](https://github.com/appc/acbuild) - A versatile tool for building and manipulating ACIs
4145 - [baci](https://github.com/sgotti/baci) - A generic ACI build project
46 - [openwrt-aci](https://github.com/1player/openwrt-aci) - A tool to build ACIs based on OpenWRT snapshots
47 - [oci2aci](https://github.com/huawei-openlab/oci2aci) - ACI builder from OCI bundle
4248
4349 ## What are some implementations of the spec?
4450
5965
6066 ### Building ACIs
6167
62 `actool` can be used to build an Application Container Image from an [Image Layout](SPEC.md#image-layout) - that is, from an Image Manifest and an application root filesystem (rootfs).
68 Various tools [listed above](#what-is-the-promise-of-the-app-container-spec) can be used to build ACIs from existing images or based on other sources.
69
70 As an example of building an ACI from scratch, `actool` can be used to build an Application Container Image from an [Image Layout](spec/aci.md#image-layout) - that is, from an Image Manifest and an application root filesystem (rootfs).
6371
6472 For example, to build a simple ACI (in this case consisting of a single binary), one could do the following:
6573 ```
7280 $ cat /tmp/my-app/manifest
7381 {
7482 "acKind": "ImageManifest",
75 "acVersion": "0.6.1",
83 "acVersion": "0.7.1",
7684 "name": "my-app",
7785 "labels": [
7886 {"name": "os", "value": "linux"},
104112 $ tar xf /tmp/my-app.aci manifest -O | python -m json.tool
105113 {
106114 "acKind": "ImageManifest",
107 "acVersion": "0.6.1",
115 "acVersion": "0.7.1",
108116 "annotations": null,
109117 "app": {
110118 "environment": [],
1313 * Designing for composability and independent implementations
1414 * Using common technologies for cryptography, archiving, compression and transport
1515 * Using the DNS namespace to name and discover images
16
17 ### Requirements
18
19 The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this and other documents of the specification are to be interpreted as described in [RFC2119](http://tools.ietf.org/html/rfc2119).
1620
1721 ## Sections
1822
0 0.6.1
0 0.7.1
44 set -eu
55
66 PREFIX="ace"
7 : ${NO_SIGNATURE=}
78
89 if ! [[ $0 =~ "${PREFIX}/build_aci" ]]; then
910 echo "invoke from repository root" 1>&2
3031 # TODO(jonboulle): create uncompressed instead, then gzip?
3132 HASH=sha512-$(gzip -d -f ../ace-validator-${typ}.aci -c | openssl dgst -sha512 -hex -r | awk '{print $1}')
3233 if [ -z "$NO_SIGNATURE" ] ; then
33 gpg --cipher-algo AES256 --output ace-validator-${typ}.aci.asc --detach-sig ../ace-validator-${typ}.aci
34 gpg --cipher-algo AES256 --armor --output ace-validator-${typ}.aci.asc --detach-sig ../ace-validator-${typ}.aci
3435 mv ace-validator-${typ}.aci.asc ../
3536 fi
3637 popd >/dev/null
00 {
1 "acVersion": "0.6.1",
1 "acVersion": "0.7.1",
22 "acKind": "ImageManifest",
33 "name": "coreos.com/ace-validator-main",
44 "labels": [
5 { "name": "version", "value": "0.6.1" },
5 { "name": "version", "value": "0.7.1" },
66 { "name": "os", "value": "linux" },
77 { "name": "arch", "value": "amd64" }
88 ],
00 {
1 "acVersion": "0.6.1",
1 "acVersion": "0.7.1",
22 "acKind": "ImageManifest",
33 "name": "coreos.com/ace-validator-sidekick",
44 "labels": [
5 { "name": "version", "value": "0.6.1" },
5 { "name": "version", "value": "0.7.1" },
66 { "name": "os", "value": "linux" },
77 { "name": "arch", "value": "amd64" }
88 ],
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // +build !linux
15 // +build !freebsd
16
17 package main
18
19 import (
20 "syscall"
21 )
22
23 func isSameFilesystem(a, b *syscall.Statfs_t) bool {
24 return a.Fsid == b.Fsid
25 }
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // +build freebsd
15
16 package main
17
18 import (
19 "syscall"
20 )
21
22 func isSameFilesystem(a, b *syscall.Statfs_t) bool {
23 if a.Fsid != (syscall.Fsid{}) || b.Fsid != (syscall.Fsid{}) {
24 // If Fsid is not empty, we can just compare the IDs
25 return a.Fsid == b.Fsid
26 }
27 // Fsids are zero, this happens in jails, but we can compare the rest
28 return a.Fstypename == b.Fstypename &&
29 a.Mntfromname == b.Mntfromname &&
30 a.Mntonname == b.Mntonname
31 }
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // +build linux
15
16 package main
17
18 import (
19 "fmt"
20 "os"
21 )
22
23 func checkMountImpl(d string, readonly bool) error {
24 mountinfoPath := fmt.Sprintf("/proc/self/mountinfo")
25 mi, err := os.Open(mountinfoPath)
26 if err != nil {
27 return err
28 }
29 defer mi.Close()
30
31 isMounted, ro, err := parseMountinfo(mi, d)
32 if err != nil {
33 return err
34 }
35 if !isMounted {
36 return fmt.Errorf("%q is not a mount point", d)
37 }
38
39 if ro == readonly {
40 return nil
41 } else {
42 return fmt.Errorf("%q mounted ro=%t, want %t", d, ro, readonly)
43 }
44 }
0 // +build !linux
1
2 package main
3
4 import (
5 "fmt"
6 "path/filepath"
7 "syscall"
8 )
9
10 func checkMountStatfs(d string, readonly bool) error {
11 // or....
12 // os.Stat(path).Sys().(*syscall.Stat_t).Dev
13 sfs1 := &syscall.Statfs_t{}
14 if err := syscall.Statfs(d, sfs1); err != nil {
15 return fmt.Errorf("error calling statfs on %q: %v", d, err)
16 }
17 sfs2 := &syscall.Statfs_t{}
18 if err := syscall.Statfs(filepath.Dir(d), sfs2); err != nil {
19 return fmt.Errorf("error calling statfs on %q: %v", d, err)
20 }
21 if isSameFilesystem(sfs1, sfs2) {
22 return fmt.Errorf("%q is not a mount point", d)
23 }
24 ro := sfs1.Flags&syscall.O_RDONLY == 1
25 if ro != readonly {
26 return fmt.Errorf("%q mounted ro=%t, want %t", d, ro, readonly)
27 }
28
29 return nil
30 }
31
32 func checkMountImpl(d string, readonly bool) error {
33 return checkMountStatfs(d, readonly)
34 }
4141 */
4242
4343 import (
44 "bufio"
4445 "bytes"
4546 "encoding/json"
4647 "fmt"
48 "io"
4749 "io/ioutil"
4850 "net/http"
4951 "net/url"
5052 "os"
51 "path/filepath"
5253 "reflect"
5354 "strings"
54 "syscall"
5555 "time"
5656
5757 "github.com/appc/spec/schema"
9393 },
9494 }
9595 // "Name"
96 an = "coreos.com/ace-validator-main"
96 an = "ace-validator-main"
9797 )
9898
9999 type results []error
428428 // Verify
429429 _, err = metadataPostForm(metadataURL, "/pod/hmac/verify", url.Values{
430430 "content": []string{plaintext},
431 "uid": []string{string(uuid)},
431 "uuid": []string{string(uuid)},
432432 "signature": []string{string(sig)},
433433 })
434434
470470 // checkMount checks that the given string is a mount point, and that it is
471471 // mounted appropriately read-only or not according to the given bool
472472 func checkMount(d string, readonly bool) error {
473 // or....
474 // os.Stat(path).Sys().(*syscall.Stat_t).Dev
475 sfs1 := &syscall.Statfs_t{}
476 if err := syscall.Statfs(d, sfs1); err != nil {
477 return fmt.Errorf("error calling statfs on %q: %v", d, err)
478 }
479 sfs2 := &syscall.Statfs_t{}
480 if err := syscall.Statfs(filepath.Dir(d), sfs2); err != nil {
481 return fmt.Errorf("error calling statfs on %q: %v", d, err)
482 }
483 if sfs1.Fsid == sfs2.Fsid {
484 return fmt.Errorf("%q is not a mount point", d)
485 }
486 ro := sfs1.Flags&syscall.O_RDONLY == 1
487 if ro != readonly {
488 return fmt.Errorf("%q mounted ro=%t, want %t", d, ro, readonly)
489 }
490 return nil
473 return checkMountImpl(d, readonly)
474 }
475
476 // parseMountinfo parses a Reader representing a /proc/PID/mountinfo file and
477 // returns whether dir is mounted and if so, whether it is read-only or not
478 func parseMountinfo(mountinfo io.Reader, dir string) (isMounted bool, readOnly bool, err error) {
479 sc := bufio.NewScanner(mountinfo)
480 for sc.Scan() {
481 var (
482 mountID int
483 parentID int
484 majorMinor string
485 root string
486 mountPoint string
487 mountOptions string
488 )
489
490 _, err := fmt.Sscanf(sc.Text(), "%d %d %s %s %s %s",
491 &mountID, &parentID, &majorMinor, &root, &mountPoint, &mountOptions)
492 if err != nil {
493 return false, false, err
494 }
495
496 if mountPoint == dir {
497 isMounted = true
498 optionsParts := strings.Split(mountOptions, ",")
499 for _, o := range optionsParts {
500 switch o {
501 case "ro":
502 readOnly = true
503 case "rw":
504 readOnly = false
505 }
506 }
507 }
508 }
509
510 return
491511 }
492512
493513 // assertNotExistsAndCreate asserts that a file at the given path does not
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package main
15
16 import (
17 "strings"
18 "testing"
19 )
20
21 var mountinfoTests = []struct {
22 mountinfo string
23 mountPoint string
24 readOnly bool
25 expectMounted bool
26 expectErr bool
27 }{
28 {
29 `15 19 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
30 16 19 0:15 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sys rw
31 17 19 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs dev rw,size=6089152k,nr_inodes=1522288,mode=755
32 18 19 0:16 / /run rw,nosuid,nodev,relatime shared:12 - tmpfs run rw,mode=755
33 19 0 254:0 / / rw,relatime shared:1 - ext4 /dev/mapper/cryptroot rw,discard,data=ordered
34 20 16 0:17 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw
35 21 17 0:18 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw
36 66 19 8:2 / /mountPoint1 rw,relatime,nodev shared:26 slave:88 - ext4 /dev/sda2 rw,discard,data=ordered`,
37 "/mountPoint1",
38 false,
39 true,
40 false,
41 },
42 {
43 `15 19 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
44 16 19 0:15 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sys rw
45 17 19 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs dev rw,size=6089152k,nr_inodes=1522288,mode=755
46 18 19 0:16 / /run rw,nosuid,nodev,relatime shared:12 - tmpfs run rw,mode=755
47 66 19 8:2 / /mountPoint2 relatime,ro,nodev shared:26 slave:88 - ext4 /dev/sda2 rw,discard,data=ordered
48 19 0 254:0 / / rw,relatime shared:1 - ext4 /dev/mapper/cryptroot rw,discard,data=ordered
49 20 16 0:17 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw
50 21 17 0:18 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw`,
51 "/mountPoint2",
52 true,
53 true,
54 false,
55 },
56 {
57 `15 19 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
58 16 19 0:15 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sys rw
59 17 19 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs dev rw,size=6089152k,nr_inodes=1522288,mode=755
60 18 19 0:16 / /run rw,nosuid,nodev,relatime shared:12 - tmpfs run rw,mode=755
61 19 0 254:0 / / rw,relatime shared:1 - ext4 /dev/mapper/cryptroot rw,discard,data=ordered
62 20 16 0:17 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw
63 21 17 0:18 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw`,
64 "/mountPoint3",
65 false,
66 false,
67 false,
68 },
69 {
70 `15 19 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
71 12 19 8:2 /var/tmp /mountPoint4 rw,relatime,nodev - ext4 /dev/sda2 rw,discard,data=ordered
72 16 19 - sys rw
73 17 19 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs dev rw,size=6089152k,nr_inodes=1522288,mode=755
74 18 19 0:16 / /run rw,nosuid,nodev,relatime shared:12 - tmpfs run rw,mode=755
75 19 0 254:0 / / rw,relatime shared:1 - ext4 /dev/mapper/cryptroot rw,discard,data=ordered
76 20 16 0:17 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw
77 21 17 0:18 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw`,
78 "/mountPoint4",
79 false,
80 false,
81 true,
82 },
83 }
84
85 func TestCheckMountLinux(t *testing.T) {
86 for i, mi := range mountinfoTests {
87 isMounted, ro, err := parseMountinfo(strings.NewReader(mi.mountinfo), mi.mountPoint)
88 if err != nil {
89 if mi.expectErr {
90 continue
91 } else {
92 t.Fatalf("#%d: unexpected error: `%v`", i, err)
93 }
94 }
95 if isMounted != mi.expectMounted {
96 t.Fatalf("#%d: expected isMounted=`%v` but got `%v`", i, mi.expectMounted, isMounted)
97 }
98 if ro != mi.readOnly {
99 t.Fatalf("#%d: expected readOnly=`%v` but got `%v`", i, mi.readOnly, ro)
100 }
101 }
102 }
2222 "github.com/appc/spec/pkg/tarheader"
2323 )
2424
25 // TarHeaderWalkFunc is the type of the function which allows setting tar
26 // headers or filtering out tar entries when building an ACI. It will be
27 // applied to every entry in the tar file.
28 //
29 // If true is returned, the entry will be included in the final ACI; if false,
30 // the entry will not be included.
31 type TarHeaderWalkFunc func(hdr *tar.Header) bool
32
2533 // BuildWalker creates a filepath.WalkFunc that walks over the given root
2634 // (which should represent an ACI layout on disk) and adds the files in the
2735 // rootfs/ subdirectory to the given ArchiveWriter
28 func BuildWalker(root string, aw ArchiveWriter) filepath.WalkFunc {
36 func BuildWalker(root string, aw ArchiveWriter, cb TarHeaderWalkFunc) filepath.WalkFunc {
2937 // cache of inode -> filepath, used to leverage hard links in the archive
3038 inos := map[uint64]string{}
3139 return func(path string, info os.FileInfo, err error) error {
8593 hdr.Size = 0
8694 r = nil
8795 }
96
97 if cb != nil {
98 if !cb(hdr) {
99 return nil
100 }
101 }
102
88103 if err := aw.AddFile(hdr, r); err != nil {
89104 return err
90105 }
102102 }
103103 }
104104
105 // XzReader shells out to a command line xz executable (if
105 // XzReader is an io.ReadCloser which decompresses xz compressed data.
106 type XzReader struct {
107 io.ReadCloser
108 cmd *exec.Cmd
109 closech chan error
110 }
111
112 // NewXzReader shells out to a command line xz executable (if
106113 // available) to decompress the given io.Reader using the xz
107 // compression format
108 func XzReader(r io.Reader) io.ReadCloser {
114 // compression format and returns an *XzReader.
115 // It is the caller's responsibility to call Close on the XzReader when done.
116 func NewXzReader(r io.Reader) (*XzReader, error) {
109117 rpipe, wpipe := io.Pipe()
110118 ex, err := exec.LookPath("xz")
111119 if err != nil {
112120 log.Fatalf("couldn't find xz executable: %v", err)
113121 }
114122 cmd := exec.Command(ex, "--decompress", "--stdout")
123
124 closech := make(chan error)
125
115126 cmd.Stdin = r
116127 cmd.Stdout = wpipe
117128
118129 go func() {
119130 err := cmd.Run()
120131 wpipe.CloseWithError(err)
132 closech <- err
121133 }()
122134
123 return rpipe
135 return &XzReader{rpipe, cmd, closech}, nil
136 }
137
138 func (r *XzReader) Close() error {
139 r.ReadCloser.Close()
140 r.cmd.Process.Kill()
141 return <-r.closech
124142 }
125143
126144 // ManifestFromImage extracts a new schema.ImageManifest from the given ACI image.
131149 if err != nil {
132150 return nil, err
133151 }
152 defer tr.Close()
134153
135154 for {
136155 hdr, err := tr.Next()
154173 }
155174 }
156175
157 // NewCompressedTarReader creates a new tar.Reader reading from the given ACI image.
158 func NewCompressedTarReader(rs io.ReadSeeker) (*tar.Reader, error) {
176 // TarReadCloser embeds a *tar.Reader and the related io.Closer
177 // It is the caller's responsibility to call Close on TarReadCloser when
178 // done.
179 type TarReadCloser struct {
180 *tar.Reader
181 io.Closer
182 }
183
184 func (r *TarReadCloser) Close() error {
185 return r.Closer.Close()
186 }
187
188 // NewCompressedTarReader creates a new TarReadCloser reading from the
189 // given ACI image.
190 // It is the caller's responsibility to call Close on the TarReadCloser
191 // when done.
192 func NewCompressedTarReader(rs io.ReadSeeker) (*TarReadCloser, error) {
159193 cr, err := NewCompressedReader(rs)
160194 if err != nil {
161195 return nil, err
162196 }
163 return tar.NewReader(cr), nil
164 }
165
166 // NewCompressedReader creates a new io.Reader from the given ACI image.
167 func NewCompressedReader(rs io.ReadSeeker) (io.Reader, error) {
197 return &TarReadCloser{tar.NewReader(cr), cr}, nil
198 }
199
200 // NewCompressedReader creates a new io.ReaderCloser from the given ACI image.
201 // It is the caller's responsibility to call Close on the Reader when done.
202 func NewCompressedReader(rs io.ReadSeeker) (io.ReadCloser, error) {
168203
169204 var (
170 dr io.Reader
205 dr io.ReadCloser
171206 err error
172207 )
173208
193228 return nil, err
194229 }
195230 case TypeBzip2:
196 dr = bzip2.NewReader(rs)
231 dr = ioutil.NopCloser(bzip2.NewReader(rs))
197232 case TypeXz:
198 dr = XzReader(rs)
233 dr, err = NewXzReader(rs)
234 if err != nil {
235 return nil, err
236 }
199237 case TypeTar:
200 dr = rs
238 dr = ioutil.NopCloser(rs)
201239 case TypeUnknown:
202240 return nil, errors.New("error: unknown image filetype")
203241 default:
2727 return nil, err
2828 }
2929
30 manifestBody := `{"acKind":"ImageManifest","acVersion":"0.6.1","name":"example.com/app"}`
30 manifestBody := `{"acKind":"ImageManifest","acVersion":"0.7.1","name":"example.com/app"}`
3131
3232 gw := gzip.NewWriter(tf)
3333 tw := tar.NewWriter(gw)
3939 "strings"
4040
4141 "github.com/appc/spec/schema"
42 "github.com/appc/spec/schema/types"
4243 )
4344
4445 const (
4748 // Path to rootfs directory inside the layout
4849 RootfsDir = "rootfs"
4950 )
51
52 type ErrOldVersion struct {
53 version types.SemVer
54 }
55
56 func (e ErrOldVersion) Error() string {
57 return fmt.Sprintf("ACVersion too old. Found major version %v, expected %v", e.version.Major, schema.AppContainerVersion.Major)
58 }
5059
5160 var (
5261 ErrNoRootFS = errors.New("no rootfs found in layout")
163172 if err := a.UnmarshalJSON(b); err != nil {
164173 return fmt.Errorf("image manifest validation failed: %v", err)
165174 }
175 if a.ACVersion.LessThanMajor(schema.AppContainerVersion) {
176 return ErrOldVersion{
177 version: a.ACVersion,
178 }
179 }
166180 for _, f := range files {
167181 if !strings.HasPrefix(f, "rootfs") {
168182 return fmt.Errorf("unrecognized file path in layout: %q", f)
1414 package aci
1515
1616 import (
17 "fmt"
1718 "io/ioutil"
1819 "os"
1920 "path"
2021 "testing"
22
23 "github.com/appc/spec/schema"
2124 )
2225
2326 func newValidateLayoutTest() (string, error) {
3538 }
3639
3740 evilManifestBody := "malformedManifest"
38 manifestBody := `{"acKind":"ImageManifest","acVersion":"0.3.0","name":"example.com/app"}`
41 manifestBody := fmt.Sprintf(`{"acKind":"ImageManifest","acVersion":"%s","name":"example.com/app"}`, schema.AppContainerVersion)
3942
4043 evilManifestPath := "rootfs/manifest"
4144 evilManifestPath = path.Join(td, evilManifestPath)
2828 var (
2929 buildNocompress bool
3030 buildOverwrite bool
31 buildOwnerRoot bool
3132 cmdBuild = &Command{
3233 Name: "build",
3334 Description: `Build an ACI from a given directory. The directory should
3536 before the ACI is created. The produced ACI will be
3637 gzip-compressed by default.`,
3738 Summary: "Build an ACI from an Image Layout (experimental)",
38 Usage: `[--overwrite] [--no-compression] DIRECTORY OUTPUT_FILE`,
39 Usage: `[--overwrite] [--no-compression] [--owner-root] DIRECTORY OUTPUT_FILE`,
3940 Run: runBuild,
4041 }
4142 )
4243
4344 func init() {
4445 cmdBuild.Flags.BoolVar(&buildOverwrite, "overwrite", false, "Overwrite target file if it already exists")
46 cmdBuild.Flags.BoolVar(&buildOwnerRoot, "owner-root", false, "Force ownership to root:root on all files")
4547 cmdBuild.Flags.BoolVar(&buildNocompress, "no-compression", false, "Do not gzip-compress the produced ACI")
4648 }
4749
5759 if ext != schema.ACIExtension {
5860 stderr("build: Extension must be %s (given %s)", schema.ACIExtension, ext)
5961 return 1
62 }
63
64 // TODO(jonboulle): stream the validation so we don't have to walk the rootfs twice
65 if err := aci.ValidateLayout(root); err != nil {
66 if e, ok := err.(aci.ErrOldVersion); ok {
67 stderr("build: Warning: %v. Please update your manifest.", e)
68 } else {
69 stderr("build: Layout failed validation: %v", err)
70 return 1
71 }
6072 }
6173
6274 mode := os.O_CREATE | os.O_WRONLY
94106 }
95107 }()
96108
97 // TODO(jonboulle): stream the validation so we don't have to walk the rootfs twice
98 if err := aci.ValidateLayout(root); err != nil {
99 stderr("build: Layout failed validation: %v", err)
100 return 1
101 }
102109 mpath := filepath.Join(root, aci.ManifestFile)
103110 b, err := ioutil.ReadFile(mpath)
104111 if err != nil {
112119 }
113120 iw := aci.NewImageWriter(im, tr)
114121
115 err = filepath.Walk(root, aci.BuildWalker(root, iw))
122 var walkerCb aci.TarHeaderWalkFunc
123 if buildOwnerRoot {
124 walkerCb = func(hdr *tar.Header) bool {
125 hdr.Uid, hdr.Gid = 0, 0
126 hdr.Uname, hdr.Gname = "root", "root"
127 return true
128 }
129 }
130
131 err = filepath.Walk(root, aci.BuildWalker(root, iw, walkerCb))
116132 if err != nil {
117133 stderr("build: Error walking rootfs: %v", err)
118134 return 1
2424 "os"
2525 "path"
2626 "path/filepath"
27 "strconv"
2728 "strings"
2829
2930 "github.com/appc/spec/aci"
3536 inputFile string
3637 outputFile string
3738
38 patchNocompress bool
39 patchOverwrite bool
40 patchReplace bool
41 patchManifestFile string
42 patchName string
43 patchExec string
44 patchUser string
45 patchGroup string
46 patchCaps string
47 patchMounts string
48 patchPorts string
49 patchIsolators string
39 patchNocompress bool
40 patchOverwrite bool
41 patchReplace bool
42 patchManifestFile string
43 patchName string
44 patchExec string
45 patchUser string
46 patchGroup string
47 patchSupplementaryGIDs string
48 patchCaps string
49 patchMounts string
50 patchPorts string
51 patchIsolators string
5052
5153 catPrettyPrint bool
5254
6264 [--capability=CAP_SYS_ADMIN,CAP_NET_ADMIN]
6365 [--mounts=work,path=/opt,readOnly=true[:work2,...]]
6466 [--ports=query,protocol=tcp,port=8080[:query2,...]]
65 [--isolators=resource/cpu,request=50,limit=100[:resource/memory,...]]
67 [--supplementary-groups=gid1,gid2,...]
68 [--isolators=resource/cpu,request=50m,limit=100m[:resource/memory,...]]
6669 [--replace]
6770 INPUT_ACI_FILE
6871 [OUTPUT_ACI_FILE]`,
8790 cmdPatchManifest.Flags.StringVar(&patchExec, "exec", "", "Replace the command line to launch the executable")
8891 cmdPatchManifest.Flags.StringVar(&patchUser, "user", "", "Replace user")
8992 cmdPatchManifest.Flags.StringVar(&patchGroup, "group", "", "Replace group")
93 cmdPatchManifest.Flags.StringVar(&patchSupplementaryGIDs, "supplementary-groups", "", "Replace supplementary groups, expects a comma-separated list.")
9094 cmdPatchManifest.Flags.StringVar(&patchCaps, "capability", "", "Replace capability")
9195 cmdPatchManifest.Flags.StringVar(&patchMounts, "mounts", "", "Replace mount points")
9296 cmdPatchManifest.Flags.StringVar(&patchPorts, "ports", "", "Replace ports")
144148 im.Name = *name
145149 }
146150
151 var app *types.App = im.App
147152 if patchExec != "" {
148 im.App.Exec = strings.Split(patchExec, " ")
153 if app == nil {
154 // if the original manifest was missing an app and
155 // patchExec is set let's assume the user is trying to
156 // inject one...
157 im.App = &types.App{}
158 app = im.App
159 }
160 app.Exec = strings.Split(patchExec, " ")
161 }
162
163 if patchUser != "" || patchGroup != "" || patchSupplementaryGIDs != "" || patchCaps != "" || patchMounts != "" || patchPorts != "" || patchIsolators != "" {
164 // ...but if we still don't have an app and the user is trying
165 // to patch one of its other parameters, it's an error
166 if app == nil {
167 return fmt.Errorf("no app in the supplied manifest and no exec command provided")
168 }
149169 }
150170
151171 if patchUser != "" {
152 im.App.User = patchUser
153 }
172 app.User = patchUser
173 }
174
154175 if patchGroup != "" {
155 im.App.Group = patchGroup
156 }
157
158 var app *types.App
159 if patchCaps != "" || patchMounts != "" || patchPorts != "" || patchIsolators != "" {
160 app = im.App
161 if app == nil {
162 return fmt.Errorf("no app in the manifest")
176 app.Group = patchGroup
177 }
178
179 if patchSupplementaryGIDs != "" {
180 app.SupplementaryGIDs = []int{}
181 gids := strings.Split(patchSupplementaryGIDs, ",")
182 for _, g := range gids {
183 gid, err := strconv.Atoi(g)
184 if err != nil {
185 return fmt.Errorf("invalid supplementary group %q: %v", g, err)
186 }
187 app.SupplementaryGIDs = append(app.SupplementaryGIDs, gid)
163188 }
164189 }
165190
403428 stderr("patch-manifest: Cannot extract %s: %v", inputFile, err)
404429 return 1
405430 }
431 defer tr.Close()
406432
407433 var newManifest []byte
408434
421447 }
422448 }
423449
424 err = extractManifest(tr, tw, false, newManifest)
450 err = extractManifest(tr.Reader, tw, false, newManifest)
425451 if err != nil {
426452 stderr("patch-manifest: Unable to read %s: %v", inputFile, err)
427453 return 1
458484 stderr("cat-manifest: Cannot extract %s: %v", inputFile, err)
459485 return 1
460486 }
461
462 err = extractManifest(tr, nil, true, nil)
487 defer tr.Close()
488
489 err = extractManifest(tr.Reader, nil, true, nil)
463490 if err != nil {
464491 stderr("cat-manifest: Unable to read %s: %v", inputFile, err)
465492 return 1
1414 package main
1515
1616 import (
17 "archive/tar"
18 "compress/bzip2"
19 "compress/gzip"
20 "errors"
2117 "fmt"
22 "io"
2318 "io/ioutil"
2419 "os"
2520 "strings"
106101 stderr("%s: valid image layout", path)
107102 }
108103 case typeAppImage:
109 fr, err := maybeDecompress(fh)
104 tr, err := aci.NewCompressedTarReader(fh)
110105 if err != nil {
111106 stderr("%s: error decompressing file: %v", path, err)
112107 return 1
113108 }
114 tr := tar.NewReader(fr)
115 err = aci.ValidateArchive(tr)
109 err = aci.ValidateArchive(tr.Reader)
110 tr.Close()
116111 fh.Close()
117112 if err != nil {
118 stderr("%s: error validating: %v", path, err)
119 exit = 1
113 if e, ok := err.(aci.ErrOldVersion); ok {
114 stderr("%s: warning: %v", path, e)
115 } else {
116 stderr("%s: error validating: %v", path, err)
117 exit = 1
118 }
120119 } else if globalFlags.Debug {
121120 stderr("%s: valid app container image", path)
122121 }
175174 return "", nil
176175 }
177176 }
178
179 func maybeDecompress(rs io.ReadSeeker) (io.Reader, error) {
180 // TODO(jonboulle): this is a bit redundant with detectValType
181 typ, err := aci.DetectFileType(rs)
182 if err != nil {
183 return nil, err
184 }
185 if _, err := rs.Seek(0, 0); err != nil {
186 return nil, err
187 }
188 var r io.Reader
189 switch typ {
190 case aci.TypeGzip:
191 r, err = gzip.NewReader(rs)
192 if err != nil {
193 return nil, fmt.Errorf("error reading gzip: %v", err)
194 }
195 case aci.TypeBzip2:
196 r = bzip2.NewReader(rs)
197 case aci.TypeXz:
198 r = aci.XzReader(rs)
199 case aci.TypeTar:
200 r = rs
201 case aci.TypeUnknown:
202 return nil, errors.New("unknown filetype")
203 default:
204 // should never happen
205 panic(fmt.Sprintf("bad type returned from DetectFileType: %v", typ))
206 }
207 return r, nil
208 }
181181 pre := strings.Join(parts[:end], "/")
182182
183183 eps, err = doDiscover(pre, app, insecure)
184 derr := discoverFn(pre, eps, err)
185 if derr != nil {
186 return err
184 if derr := discoverFn(pre, eps, err); derr != nil {
185 return derr
187186 }
188187 }
189188
4545 // Example app parameters:
4646 // example.com/reduce-worker:1.0.0
4747 // example.com/reduce-worker,channel=alpha,label=value
48 // example.com/reduce-worker:1.0.0,label=value
49 //
50 // As can be seen in above examples - colon, comma and equal sign have
51 // special meaning. If any of them has to be a part of a label's value
52 // then consider writing your own string to App parser.
4853 func NewAppFromString(app string) (*App, error) {
4954 var (
5055 name string
5156 labels map[types.ACIdentifier]string
5257 )
5358
54 app = strings.Replace(app, ":", ",version=", -1)
55 app = "name=" + app
56 v, err := url.ParseQuery(strings.Replace(app, ",", "&", -1))
59 preparedApp, err := prepareAppString(app)
60 if err != nil {
61 return nil, err
62 }
63 v, err := url.ParseQuery(preparedApp)
5764 if err != nil {
5865 return nil, err
5966 }
7986 return a, nil
8087 }
8188
89 func prepareAppString(app string) (string, error) {
90 if err := checkColon(app); err != nil {
91 return "", err
92 }
93
94 app = "name=" + strings.Replace(app, ":", ",version=", 1)
95 return makeQueryString(app)
96 }
97
98 func checkColon(app string) error {
99 firstComma := strings.IndexRune(app, ',')
100 firstColon := strings.IndexRune(app, ':')
101 if firstColon > firstComma && firstComma > -1 {
102 return fmt.Errorf("malformed app string - colon may appear only right after the app name")
103 }
104 if strings.Count(app, ":") > 1 {
105 return fmt.Errorf("malformed app string - colon may appear at most once")
106 }
107 return nil
108 }
109
110 func makeQueryString(app string) (string, error) {
111 parts := strings.Split(app, ",")
112 escapedParts := make([]string, len(parts))
113 for i, s := range parts {
114 p := strings.SplitN(s, "=", 2)
115 if len(p) != 2 {
116 return "", fmt.Errorf("malformed app string - has a label without a value: %s", p[0])
117 }
118 escapedParts[i] = fmt.Sprintf("%s=%s", p[0], url.QueryEscape(p[1]))
119 }
120 return strings.Join(escapedParts, "&"), nil
121 }
122
82123 func (a *App) Copy() *App {
83124 ac := &App{
84125 Name: a.Name,
5151
5252 false,
5353 },
54 {
55 "example.com/app:1.2.3,special=!*'();@&+$/?#[],channel=beta",
56
57 &App{
58 Name: "example.com/app",
59 Labels: map[types.ACIdentifier]string{
60 "version": "1.2.3",
61 "special": "!*'();@&+$/?#[]",
62 "channel": "beta",
63 },
64 },
65
66 false,
67 },
5468
5569 // bad AC name for app
5670 {
7185 // multi-value labels
7286 {
7387 "foo.com/bar,channel=alpha,dog=woof,channel=beta",
88
89 nil,
90 true,
91 },
92 // colon coming after some label instead of being
93 // right after the name
94 {
95 "example.com/app,channel=beta:1.2.3",
96
97 nil,
98 true,
99 },
100 // two colons in string
101 {
102 "example.com/app:3.2.1,channel=beta:1.2.3",
103
104 nil,
105 true,
106 },
107 // two version labels, one implicit, one explicit
108 {
109 "example.com/app:3.2.1,version=1.2.3",
74110
75111 nil,
76112 true,
00 {
11 "acKind": "ImageManifest",
2 "acVersion": "0.6.1",
2 "acVersion": "0.7.1",
33 "name": "example.com/reduce-worker",
44 "labels": [
55 {
4646 "isolators": [
4747 {
4848 "name": "resource/cpu",
49 "value": {"limit": "20"}
49 "value": {"limit": "20m"}
5050 },
5151 {
5252 "name": "resource/memory",
00 {
1 "acVersion": "0.6.1",
21 "acKind": "PodManifest",
2 "acVersion": "0.7.1",
33 "apps": [
44 {
55 "name": "reduce-worker",
99 },
1010 "app": {
1111 "exec": [
12 "/bin/reduce-worker",
12 "/usr/bin/reduce-worker",
1313 "--debug=true",
1414 "--data-dir=/mnt/foo"
1515 ],
1717 "user": "0",
1818 "mountPoints": [
1919 {
20 "name": "work",
21 "path": "/mnt/foo"
20 "name": "database",
21 "path": "/var/lib/db"
2222 }
2323 ],
2424 "isolators": [
2929 ]
3030 },
3131 "mounts": [
32 {"volume": "work", "mountPoint": "work"}
32 {
33 "volume": "dbvolume",
34 "path": "/var/lib/db"
35 }
3336 ]
3437 },
3538 {
4548 ]
4649 },
4750 "mounts": [
48 {"volume": "database", "mountPoint": "db"},
49 {"volume": "buildoutput", "mountPoint": "out"}
51 {
52 "volume": "dbvolume",
53 "path": "/mnt/db"
54 },
55 {
56 "volume": "buildoutput",
57 "path": "/mnt/out"
58 }
5059 ],
5160 "annotations": [
5261 {
7180 ],
7281 "volumes": [
7382 {
74 "name": "database",
83 "name": "dbvolume",
7584 "kind": "host",
7685 "source": "/opt/tenant1/database",
7786 "readOnly": true
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 255.3 72" style="enable-background:new 0 0 255.3 72;" xml:space="preserve">
4 <g>
5 <g>
6 <path d="M115.2,29.5h2.3v-1.1c0-8.1-4.4-10.8-10.4-10.8c-6.9,0-11.8,4-11.8,4l-2.7-4.3c0,0,5.5-4.7,14.9-4.7
7 c10.1,0,15.6,5.6,15.6,16v26.8h-5.4v-4.6c0-2.1,0.2-3.5,0.2-3.5h-0.2c0,0-3.5,9.1-14,9.1c-7,0-14.2-4.1-14.2-12.4
8 C89.6,29.8,107.9,29.5,115.2,29.5z M104.7,51.5c8,0,12.8-8.3,12.8-15.5v-1.8h-2.4c-6.6,0-19.7,0.2-19.7,9.5
9 C95.4,47.6,98.5,51.5,104.7,51.5z"/>
10 <path d="M130.1,13.5h5.4v4.1c0,1.8-0.2,3.2-0.2,3.2h0.2c0,0,3.4-8.3,14.5-8.3c11.2,0,18.2,9,18.2,22c0,13.2-7.9,21.9-18.7,21.9
11 c-10.1,0-13.7-8-13.7-8h-0.2c0,0,0.2,1.5,0.2,3.7v20h-5.7V13.5z M148.9,51.4c7.4,0,13.5-6.2,13.5-16.9c0-10.2-5.4-16.8-13.2-16.8
12 c-7,0-13.6,5-13.6,16.9C135.6,43,140.3,51.4,148.9,51.4z"/>
13 <path d="M174.4,13.5h5.4v4.1c0,1.8-0.2,3.2-0.2,3.2h0.2c0,0,3.4-8.3,14.5-8.3c11.2,0,18.2,9,18.2,22c0,13.2-7.9,21.9-18.7,21.9
14 c-10.1,0-13.7-8-13.7-8h-0.2c0,0,0.2,1.5,0.2,3.7v20h-5.7V13.5z M193.2,51.4c7.4,0,13.5-6.2,13.5-16.9c0-10.2-5.4-16.8-13.2-16.8
15 c-7,0-13.6,5-13.6,16.9C179.8,43,184.5,51.4,193.2,51.4z"/>
16 <path d="M239,12.5c10.2,0,15.3,6,15.3,6l-2.9,4.2c0,0-4.7-5.1-12.2-5.1c-9.1,0-16.3,6.8-16.3,16.8c0,9.8,7.2,16.8,16.5,16.8
17 c8.5,0,13.4-5.9,13.4-5.9l2.6,4.4c0,0-5.7,6.6-16.3,6.6c-12.4,0-22-8.9-22-21.9C217,21.6,226.7,12.5,239,12.5z"/>
18 </g>
19 <path d="M66.9,52l-2.3-3.9h-4.5L57.8,52l1.6,2.8h-3.2l-1.6,2.8l-3.1-5.4H44l-3.1,5.4l-3.1-5.4h-6.2l5-8.7l-5.7-9.8h-10l4.3-7.5
20 l3.1,5.4h7.5l3.8-6.5l-3.1-5.4h10l5-8.7l1.6,2.8h3.2l-1.6,2.8l2.3,3.9h4.5l2.3-3.9l-2.3-3.9h-3.2L60,9.8l-2.3-3.9h-4.5l-1.6,2.8
21 l-5-8.7H35.2l-5,8.7l-3.1-5.4h-7.5l-3.8,6.5l3.1,5.4h-10L3.2,25l5.7,9.8h10l-4.3,7.5l-3.1-5.4H3.8L0,43.5l3.8,6.6h7.6l3.1-5.4
22 l4.3,7.5h-3.7l-2.5,4.4l2.5,4.4h5l2.5-4.4l-1.9-3.2h8.7l-3.1,5.4l3.8,6.5h6.2L34.9,68l2.3,3.9h4.5l2.3-3.9l-2.3-3.9h-3.2l2.4-4.2
23 l3.1,5.4h7.5l3.1-5.4l1.6,2.8h4.5l2.3-3.9l-1.6-2.8h3.2L66.9,52z M20.9,16.4h6.2l3.1-5.4l4.3,7.5h-6.2l-3.1,5.4L20.9,16.4z
24 M35.2,30.4H29L25.9,25l3.1-5.4h6.2l3.1,5.4L35.2,30.4z M60.9,13.8l1.6,2.8l-1.6,2.8h-3.2l-1.6-2.8l1.6-2.8h0l0,0H60.9z M53.8,7.1
25 H57l1.6,2.8L57,12.6h0l0,0h-3.2l-1.6-2.8L53.8,7.1z M35.9,1.2h10l5,8.7l-5,8.7h-10l-5-8.7L35.9,1.2z M17.2,9.8l3.1-5.4h6.2l3.1,5.4
26 l-3.1,5.4h-6.2h0L17.2,9.8z M9.6,33.7l-5-8.7l5-8.7h10h0l5,8.7l-5,8.7h0H9.6z M10.8,48.9H4.5l-3.1-5.4l3.1-5.4h6.3l3.1,5.4
27 L10.8,48.9z M20.3,34.9L20.3,34.9h10l5,8.7l-5,8.7h-10l-5-8.7L20.3,34.9z M19.6,59.8h-3.7L14,56.6l1.8-3.2h3.7l1.8,3.2L19.6,59.8z
28 M27.8,58.7l3.1-5.4h6.2l3.1,5.4l-3.1,5.4h-6.2L27.8,58.7z M42.6,68L41,70.7h-3.2L36.2,68l1.6-2.8H41L42.6,68z M50.9,64.1h-6.2
29 l-3.1-5.4l3.1-5.4h6.2l3.1,5.4L50.9,64.1z M61.7,58.7l-1.6,2.8h-3.2l-1.6-2.8l1.6-2.8h3.2l0,0h0L61.7,58.7z M60.8,54.8L60.8,54.8
30 L60.8,54.8L59.2,52l1.6-2.8h3.2l1.6,2.8l-1.6,2.8H60.8z"/>
31 </g>
32 </svg>
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 255.3 72" style="enable-background:new 0 0 255.3 72;" xml:space="preserve">
4 <g>
5 <g>
6 <path style="fill:#F38B00;" d="M115.2,29.5h2.3v-1.1c0-8.1-4.4-10.8-10.4-10.8c-6.9,0-11.8,4-11.8,4l-2.7-4.3
7 c0,0,5.5-4.7,14.9-4.7c10.1,0,15.6,5.6,15.6,16v26.8h-5.4v-4.6c0-2.1,0.2-3.5,0.2-3.5h-0.2c0,0-3.5,9.1-14,9.1
8 c-7,0-14.2-4.1-14.2-12.4C89.6,29.8,107.9,29.5,115.2,29.5z M104.7,51.5c8,0,12.8-8.3,12.8-15.5v-1.8h-2.4
9 c-6.6,0-19.7,0.2-19.7,9.5C95.4,47.6,98.5,51.5,104.7,51.5z"/>
10 <path style="fill:#F38B00;" d="M130.1,13.5h5.4v4.1c0,1.8-0.2,3.2-0.2,3.2h0.2c0,0,3.4-8.3,14.5-8.3c11.2,0,18.2,9,18.2,22
11 c0,13.2-7.9,21.9-18.7,21.9c-10.1,0-13.7-8-13.7-8h-0.2c0,0,0.2,1.5,0.2,3.7v20h-5.7V13.5z M148.9,51.4c7.4,0,13.5-6.2,13.5-16.9
12 c0-10.2-5.4-16.8-13.2-16.8c-7,0-13.6,5-13.6,16.9C135.6,43,140.3,51.4,148.9,51.4z"/>
13 <path style="fill:#F38B00;" d="M174.4,13.5h5.4v4.1c0,1.8-0.2,3.2-0.2,3.2h0.2c0,0,3.4-8.3,14.5-8.3c11.2,0,18.2,9,18.2,22
14 c0,13.2-7.9,21.9-18.7,21.9c-10.1,0-13.7-8-13.7-8h-0.2c0,0,0.2,1.5,0.2,3.7v20h-5.7V13.5z M193.2,51.4c7.4,0,13.5-6.2,13.5-16.9
15 c0-10.2-5.4-16.8-13.2-16.8c-7,0-13.6,5-13.6,16.9C179.8,43,184.5,51.4,193.2,51.4z"/>
16 <path style="fill:#F38B00;" d="M239,12.5c10.2,0,15.3,6,15.3,6l-2.9,4.2c0,0-4.7-5.1-12.2-5.1c-9.1,0-16.3,6.8-16.3,16.8
17 c0,9.8,7.2,16.8,16.5,16.8c8.5,0,13.4-5.9,13.4-5.9l2.6,4.4c0,0-5.7,6.6-16.3,6.6c-12.4,0-22-8.9-22-21.9
18 C217,21.6,226.7,12.5,239,12.5z"/>
19 </g>
20 <path style="fill:#FFB547;" d="M66.9,52l-2.3-3.9h-4.5L57.8,52l1.6,2.8h-3.2l-1.6,2.8l-3.1-5.4H44l-3.1,5.4l-3.1-5.4h-6.2l5-8.7
21 l-5.7-9.8h-10l4.3-7.5l3.1,5.4h7.5l3.8-6.5l-3.1-5.4h10l5-8.7l1.6,2.8h3.2l-1.6,2.8l2.3,3.9h4.5l2.3-3.9l-2.3-3.9h-3.2L60,9.8
22 l-2.3-3.9h-4.5l-1.6,2.8l-5-8.7H35.2l-5,8.7l-3.1-5.4h-7.5l-3.8,6.5l3.1,5.4h-10L3.2,25l5.7,9.8h10l-4.3,7.5l-3.1-5.4H3.8L0,43.5
23 l3.8,6.6h7.6l3.1-5.4l4.3,7.5h-3.7l-2.5,4.4l2.5,4.4h5l2.5-4.4l-1.9-3.2h8.7l-3.1,5.4l3.8,6.5h6.2L34.9,68l2.3,3.9h4.5l2.3-3.9
24 l-2.3-3.9h-3.2l2.4-4.2l3.1,5.4h7.5l3.1-5.4l1.6,2.8h4.5l2.3-3.9l-1.6-2.8h3.2L66.9,52z M20.9,16.4h6.2l3.1-5.4l4.3,7.5h-6.2
25 l-3.1,5.4L20.9,16.4z M35.2,30.4H29L25.9,25l3.1-5.4h6.2l3.1,5.4L35.2,30.4z M60.9,13.8l1.6,2.8l-1.6,2.8h-3.2l-1.6-2.8l1.6-2.8h0
26 l0,0H60.9z M53.8,7.1H57l1.6,2.8L57,12.6h0l0,0h-3.2l-1.6-2.8L53.8,7.1z M35.9,1.2h10l5,8.7l-5,8.7h-10l-5-8.7L35.9,1.2z M17.2,9.8
27 l3.1-5.4h6.2l3.1,5.4l-3.1,5.4h-6.2h0L17.2,9.8z M9.6,33.7l-5-8.7l5-8.7h10h0l5,8.7l-5,8.7h0H9.6z M10.8,48.9H4.5l-3.1-5.4l3.1-5.4
28 h6.3l3.1,5.4L10.8,48.9z M20.3,34.9L20.3,34.9h10l5,8.7l-5,8.7h-10l-5-8.7L20.3,34.9z M19.6,59.8h-3.7L14,56.6l1.8-3.2h3.7l1.8,3.2
29 L19.6,59.8z M27.8,58.7l3.1-5.4h6.2l3.1,5.4l-3.1,5.4h-6.2L27.8,58.7z M42.6,68L41,70.7h-3.2L36.2,68l1.6-2.8H41L42.6,68z
30 M50.9,64.1h-6.2l-3.1-5.4l3.1-5.4h6.2l3.1,5.4L50.9,64.1z M61.7,58.7l-1.6,2.8h-3.2l-1.6-2.8l1.6-2.8h3.2l0,0h0L61.7,58.7z
31 M60.8,54.8L60.8,54.8L60.8,54.8L59.2,52l1.6-2.8h3.2l1.6,2.8l-1.6,2.8H60.8z"/>
32 </g>
33 </svg>
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 144.3 234.4" style="enable-background:new 0 0 144.3 234.4;" xml:space="preserve">
4 <path d="M143.7,111.3l-4.9-8.4h-9.7l-4.8,8.4l3.4,5.9h-6.8l-3.4,5.9l-6.6-11.5H94.7l-6.6,11.5l-6.6-11.5H68.2l10.7-18.5l-12.1-21
5 H45.4l9.3-16l6.6,11.5h16.1l8.1-14l-6.6-11.5h21.4l10.7-18.5l3.4,5.9h6.8l-3.4,5.9l4.8,8.4h9.7l4.9-8.4l-4.9-8.4h-6.8l3.4-5.9
6 l-4.9-8.4h-9.7l-3.4,5.9L100.2,0H76L65.3,18.5L58.6,7.1H42.5l-8.1,14l6.6,11.5H19.7l-12.1,21l12.1,21h21.4l-9.2,16l-6.7-11.6H8.8
7 L0.6,93.1l8.2,14.1h16.3l6.7-11.6l9.3,16.1h-7.9l-5.4,9.4l5.4,9.4H44l5.4-9.4l-4-6.9h18.5l-6.6,11.5l8.1,14h13.2l-3.4,5.9l4.8,8.4
8 h9.7l4.9-8.4l-4.9-8.4H83l5.2-8.9l6.6,11.5h16.1l6.6-11.5l3.4,5.9h9.7l4.8-8.4l-3.4-5.9h6.8L143.7,111.3z M45.4,35h13.2l6.6-11.5
9 l9.3,16H61.3L54.7,51L45.4,35z M76,65H62.7l-6.6-11.5l6.6-11.5H76l6.6,11.5L76,65z M130.8,29.4l3.4,5.9l-3.4,5.9H124l-3.4-5.9
10 l3.4-5.9h0l0,0H130.8z M115.8,15.1h6.8L126,21l-3.4,5.9h0l0,0h-6.8l-3.4-5.9L115.8,15.1z M77.4,2.5h21.4L109.5,21L98.8,39.6H77.4
11 L66.7,21L77.4,2.5z M37.3,21L44,9.6h13.2L63.8,21l-6.6,11.5H44h0L37.3,21z M21.1,72.1L10.4,53.5L21.1,35h21.4h0l10.7,18.5L42.5,72.1
12 h0H21.1z M23.6,104.7H10.2L3.5,93.1l6.7-11.6h13.4l6.7,11.6L23.6,104.7z M43.9,74.6L43.9,74.6h21.4L76,93.1l-10.7,18.5H43.9
13 L33.3,93.1L43.9,74.6z M42.5,127.8h-7.9l-4-6.8l4-6.8h7.9l4,6.8L42.5,127.8z M60.2,125.6l6.6-11.5h13.2l6.6,11.5l-6.6,11.5H66.8
14 L60.2,125.6z M91.7,145.4l-3.4,5.9h-6.8l-3.4-5.9l3.4-5.9h6.8L91.7,145.4z M109.4,137.1H96.2l-6.6-11.5l6.6-11.5h13.2l6.6,11.5
15 L109.4,137.1z M132.6,125.6l-3.4,5.9h-6.8l-3.4-5.9l3.4-5.9h6.8l0,0h0L132.6,125.6z M130.6,117.2L130.6,117.2L130.6,117.2l-3.4-5.9
16 l3.4-5.9h6.8l3.4,5.9l-3.4,5.9H130.6z"/>
17 <g>
18 <path d="M22.3,197.4h2v-0.9c0-7-3.8-9.4-9.1-9.4c-6,0-10.3,3.5-10.3,3.5l-2.3-3.8c0,0,4.8-4.1,13-4.1c8.8,0,13.6,4.9,13.6,14v23.3
19 h-4.7v-4c0-1.8,0.1-3,0.1-3h-0.1c0,0-3,7.9-12.2,7.9C6.3,220.8,0,217.2,0,210C0,197.7,16,197.4,22.3,197.4z M13.2,216.6
20 c7,0,11.2-7.2,11.2-13.5v-1.6h-2.1c-5.7,0-17.2,0.2-17.2,8.3C5.1,213.1,7.7,216.6,13.2,216.6z"/>
21 <path d="M35.3,183.5H40v3.5c0,1.6-0.1,2.8-0.1,2.8H40c0,0,3-7.2,12.6-7.2c9.8,0,15.9,7.8,15.9,19.1c0,11.5-6.9,19-16.3,19
22 c-8.8,0-11.9-7-11.9-7h-0.1c0,0,0.1,1.3,0.1,3.2v17.4h-4.9V183.5z M51.7,216.4c6.4,0,11.7-5.4,11.7-14.7c0-8.9-4.7-14.6-11.5-14.6
23 c-6.1,0-11.8,4.3-11.8,14.7C40.1,209.1,44.2,216.4,51.7,216.4z"/>
24 <path d="M73.8,183.5h4.7v3.5c0,1.6-0.1,2.8-0.1,2.8h0.1c0,0,3-7.2,12.6-7.2c9.8,0,15.9,7.8,15.9,19.1c0,11.5-6.9,19-16.3,19
25 c-8.8,0-11.9-7-11.9-7h-0.1c0,0,0.1,1.3,0.1,3.2v17.4h-4.9V183.5z M90.2,216.4c6.4,0,11.7-5.4,11.7-14.7c0-8.9-4.7-14.6-11.5-14.6
26 c-6.1,0-11.8,4.3-11.8,14.7C78.6,209.1,82.7,216.4,90.2,216.4z"/>
27 <path d="M130.1,182.6c8.9,0,13.3,5.2,13.3,5.2l-2.5,3.7c0,0-4.1-4.4-10.6-4.4c-8,0-14.2,5.9-14.2,14.6c0,8.5,6.3,14.6,14.3,14.6
28 c7.4,0,11.7-5.1,11.7-5.1l2.2,3.8c0,0-4.9,5.7-14.2,5.7c-10.8,0-19.1-7.7-19.1-19C111,190.5,119.4,182.6,130.1,182.6z"/>
29 </g>
30 </svg>
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 144.3 234.4" style="enable-background:new 0 0 144.3 234.4;" xml:space="preserve">
4 <path style="fill:#FFB547;" d="M143.7,111.3l-4.9-8.4h-9.7l-4.8,8.4l3.4,5.9h-6.8l-3.4,5.9l-6.6-11.5H94.7l-6.6,11.5l-6.6-11.5H68.2
5 l10.7-18.5l-12.1-21H45.4l9.3-16l6.6,11.5h16.1l8.1-14l-6.6-11.5h21.4l10.7-18.5l3.4,5.9h6.8l-3.4,5.9l4.8,8.4h9.7l4.9-8.4l-4.9-8.4
6 h-6.8l3.4-5.9l-4.9-8.4h-9.7l-3.4,5.9L100.2,0H76L65.3,18.5L58.6,7.1H42.5l-8.1,14l6.6,11.5H19.7l-12.1,21l12.1,21h21.4l-9.2,16
7 l-6.7-11.6H8.8L0.6,93.1l8.2,14.1h16.3l6.7-11.6l9.3,16.1h-7.9l-5.4,9.4l5.4,9.4H44l5.4-9.4l-4-6.9h18.5l-6.6,11.5l8.1,14h13.2
8 l-3.4,5.9l4.8,8.4h9.7l4.9-8.4l-4.9-8.4H83l5.2-8.9l6.6,11.5h16.1l6.6-11.5l3.4,5.9h9.7l4.8-8.4l-3.4-5.9h6.8L143.7,111.3z M45.4,35
9 h13.2l6.6-11.5l9.3,16H61.3L54.7,51L45.4,35z M76,65H62.7l-6.6-11.5l6.6-11.5H76l6.6,11.5L76,65z M130.8,29.4l3.4,5.9l-3.4,5.9H124
10 l-3.4-5.9l3.4-5.9h0l0,0H130.8z M115.8,15.1h6.8L126,21l-3.4,5.9h0l0,0h-6.8l-3.4-5.9L115.8,15.1z M77.4,2.5h21.4L109.5,21
11 L98.8,39.6H77.4L66.7,21L77.4,2.5z M37.3,21L44,9.6h13.2L63.8,21l-6.6,11.5H44h0L37.3,21z M21.1,72.1L10.4,53.5L21.1,35h21.4h0
12 l10.7,18.5L42.5,72.1h0H21.1z M23.6,104.7H10.2L3.5,93.1l6.7-11.6h13.4l6.7,11.6L23.6,104.7z M43.9,74.6L43.9,74.6h21.4L76,93.1
13 l-10.7,18.5H43.9L33.3,93.1L43.9,74.6z M42.5,127.8h-7.9l-4-6.8l4-6.8h7.9l4,6.8L42.5,127.8z M60.2,125.6l6.6-11.5h13.2l6.6,11.5
14 l-6.6,11.5H66.8L60.2,125.6z M91.7,145.4l-3.4,5.9h-6.8l-3.4-5.9l3.4-5.9h6.8L91.7,145.4z M109.4,137.1H96.2l-6.6-11.5l6.6-11.5
15 h13.2l6.6,11.5L109.4,137.1z M132.6,125.6l-3.4,5.9h-6.8l-3.4-5.9l3.4-5.9h6.8l0,0h0L132.6,125.6z M130.6,117.2L130.6,117.2
16 L130.6,117.2l-3.4-5.9l3.4-5.9h6.8l3.4,5.9l-3.4,5.9H130.6z"/>
17 <g>
18 <path style="fill:#F38B00;" d="M22.3,197.4h2v-0.9c0-7-3.8-9.4-9.1-9.4c-6,0-10.3,3.5-10.3,3.5l-2.3-3.8c0,0,4.8-4.1,13-4.1
19 c8.8,0,13.6,4.9,13.6,14v23.3h-4.7v-4c0-1.8,0.1-3,0.1-3h-0.1c0,0-3,7.9-12.2,7.9C6.3,220.8,0,217.2,0,210
20 C0,197.7,16,197.4,22.3,197.4z M13.2,216.6c7,0,11.2-7.2,11.2-13.5v-1.6h-2.1c-5.7,0-17.2,0.2-17.2,8.3
21 C5.1,213.1,7.7,216.6,13.2,216.6z"/>
22 <path style="fill:#F38B00;" d="M35.3,183.5H40v3.5c0,1.6-0.1,2.8-0.1,2.8H40c0,0,3-7.2,12.6-7.2c9.8,0,15.9,7.8,15.9,19.1
23 c0,11.5-6.9,19-16.3,19c-8.8,0-11.9-7-11.9-7h-0.1c0,0,0.1,1.3,0.1,3.2v17.4h-4.9V183.5z M51.7,216.4c6.4,0,11.7-5.4,11.7-14.7
24 c0-8.9-4.7-14.6-11.5-14.6c-6.1,0-11.8,4.3-11.8,14.7C40.1,209.1,44.2,216.4,51.7,216.4z"/>
25 <path style="fill:#F38B00;" d="M73.8,183.5h4.7v3.5c0,1.6-0.1,2.8-0.1,2.8h0.1c0,0,3-7.2,12.6-7.2c9.8,0,15.9,7.8,15.9,19.1
26 c0,11.5-6.9,19-16.3,19c-8.8,0-11.9-7-11.9-7h-0.1c0,0,0.1,1.3,0.1,3.2v17.4h-4.9V183.5z M90.2,216.4c6.4,0,11.7-5.4,11.7-14.7
27 c0-8.9-4.7-14.6-11.5-14.6c-6.1,0-11.8,4.3-11.8,14.7C78.6,209.1,82.7,216.4,90.2,216.4z"/>
28 <path style="fill:#F38B00;" d="M130.1,182.6c8.9,0,13.3,5.2,13.3,5.2l-2.5,3.7c0,0-4.1-4.4-10.6-4.4c-8,0-14.2,5.9-14.2,14.6
29 c0,8.5,6.3,14.6,14.3,14.6c7.4,0,11.7-5.1,11.7-5.1l2.2,3.8c0,0-4.9,5.7-14.2,5.7c-10.8,0-19.1-7.7-19.1-19
30 C111,190.5,119.4,182.6,130.1,182.6z"/>
31 </g>
32 </svg>
0 <?xml version="1.0" encoding="utf-8"?>
1 <!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
2 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
3 viewBox="0 0 144.3 234.4" style="enable-background:new 0 0 144.3 234.4;" xml:space="preserve">
4 <g>
5 <path style="fill:#FFFFFF;" d="M143.7,111.3l-4.9-8.4h-9.7l-4.8,8.4l3.4,5.9h-6.8l-3.4,5.9l-6.6-11.5H94.7l-6.6,11.5l-6.6-11.5
6 H68.2l10.7-18.5l-12.1-21H45.4l9.3-16l6.6,11.5h16.1l8.1-14l-6.6-11.5h21.4l10.7-18.5l3.4,5.9h6.8l-3.4,5.9l4.8,8.4h9.7l4.9-8.4
7 l-4.9-8.4h-6.8l3.4-5.9l-4.9-8.4h-9.7l-3.4,5.9L100.2,0H76L65.3,18.5L58.6,7.1H42.5l-8.1,14l6.6,11.5H19.7l-12.1,21l12.1,21h21.4
8 l-9.2,16l-6.7-11.6H8.8L0.6,93.1l8.2,14.1h16.3l6.7-11.6l9.3,16.1h-7.9l-5.4,9.4l5.4,9.4H44l5.4-9.4l-4-6.9h18.5l-6.6,11.5l8.1,14
9 h13.2l-3.4,5.9l4.8,8.4h9.7l4.9-8.4l-4.9-8.4H83l5.2-8.9l6.6,11.5h16.1l6.6-11.5l3.4,5.9h9.7l4.8-8.4l-3.4-5.9h6.8L143.7,111.3z
10 M45.4,35h13.2l6.6-11.5l9.3,16H61.3L54.7,51L45.4,35z M76,65H62.7l-6.6-11.5l6.6-11.5H76l6.6,11.5L76,65z M130.8,29.4l3.4,5.9
11 l-3.4,5.9H124l-3.4-5.9l3.4-5.9h0l0,0H130.8z M115.8,15.1h6.8L126,21l-3.4,5.9h0l0,0h-6.8l-3.4-5.9L115.8,15.1z M77.4,2.5h21.4
12 L109.5,21L98.8,39.6H77.4L66.7,21L77.4,2.5z M37.3,21L44,9.6h13.2L63.8,21l-6.6,11.5H44h0L37.3,21z M21.1,72.1L10.4,53.5L21.1,35
13 h21.4h0l10.7,18.5L42.5,72.1h0H21.1z M23.6,104.7H10.2L3.5,93.1l6.7-11.6h13.4l6.7,11.6L23.6,104.7z M43.9,74.6L43.9,74.6h21.4
14 L76,93.1l-10.7,18.5H43.9L33.3,93.1L43.9,74.6z M42.5,127.8h-7.9l-4-6.8l4-6.8h7.9l4,6.8L42.5,127.8z M60.2,125.6l6.6-11.5h13.2
15 l6.6,11.5l-6.6,11.5H66.8L60.2,125.6z M91.7,145.4l-3.4,5.9h-6.8l-3.4-5.9l3.4-5.9h6.8L91.7,145.4z M109.4,137.1H96.2l-6.6-11.5
16 l6.6-11.5h13.2l6.6,11.5L109.4,137.1z M132.6,125.6l-3.4,5.9h-6.8l-3.4-5.9l3.4-5.9h6.8l0,0h0L132.6,125.6z M130.6,117.2
17 L130.6,117.2L130.6,117.2l-3.4-5.9l3.4-5.9h6.8l3.4,5.9l-3.4,5.9H130.6z"/>
18 <g>
19 <path style="fill:#FFFFFF;" d="M22.3,197.4h2v-0.9c0-7-3.8-9.4-9.1-9.4c-6,0-10.3,3.5-10.3,3.5l-2.3-3.8c0,0,4.8-4.1,13-4.1
20 c8.8,0,13.6,4.9,13.6,14v23.3h-4.7v-4c0-1.8,0.1-3,0.1-3h-0.1c0,0-3,7.9-12.2,7.9C6.3,220.8,0,217.2,0,210
21 C0,197.7,16,197.4,22.3,197.4z M13.2,216.6c7,0,11.2-7.2,11.2-13.5v-1.6h-2.1c-5.7,0-17.2,0.2-17.2,8.3
22 C5.1,213.1,7.7,216.6,13.2,216.6z"/>
23 <path style="fill:#FFFFFF;" d="M35.3,183.5H40v3.5c0,1.6-0.1,2.8-0.1,2.8H40c0,0,3-7.2,12.6-7.2c9.8,0,15.9,7.8,15.9,19.1
24 c0,11.5-6.9,19-16.3,19c-8.8,0-11.9-7-11.9-7h-0.1c0,0,0.1,1.3,0.1,3.2v17.4h-4.9V183.5z M51.7,216.4c6.4,0,11.7-5.4,11.7-14.7
25 c0-8.9-4.7-14.6-11.5-14.6c-6.1,0-11.8,4.3-11.8,14.7C40.1,209.1,44.2,216.4,51.7,216.4z"/>
26 <path style="fill:#FFFFFF;" d="M73.8,183.5h4.7v3.5c0,1.6-0.1,2.8-0.1,2.8h0.1c0,0,3-7.2,12.6-7.2c9.8,0,15.9,7.8,15.9,19.1
27 c0,11.5-6.9,19-16.3,19c-8.8,0-11.9-7-11.9-7h-0.1c0,0,0.1,1.3,0.1,3.2v17.4h-4.9V183.5z M90.2,216.4c6.4,0,11.7-5.4,11.7-14.7
28 c0-8.9-4.7-14.6-11.5-14.6c-6.1,0-11.8,4.3-11.8,14.7C78.6,209.1,82.7,216.4,90.2,216.4z"/>
29 <path style="fill:#FFFFFF;" d="M130.1,182.6c8.9,0,13.3,5.2,13.3,5.2l-2.5,3.7c0,0-4.1-4.4-10.6-4.4c-8,0-14.2,5.9-14.2,14.6
30 c0,8.5,6.3,14.6,14.3,14.6c7.4,0,11.7-5.1,11.7-5.1l2.2,3.8c0,0-4.9,5.7-14.2,5.7c-10.8,0-19.1-7.7-19.1-19
31 C111,190.5,119.4,182.6,130.1,182.6z"/>
32 </g>
33 </g>
34 </svg>
21482148 `
21492149 {
21502150 "acKind": "ImageManifest",
2151 "acVersion": "0.6.1",
2151 "acVersion": "0.7.1",
21522152 "name": "example.com/test_empty_rootfs"
21532153 }
21542154 `,
21742174 `
21752175 {
21762176 "acKind": "ImageManifest",
2177 "acVersion": "0.6.1",
2177 "acVersion": "0.7.1",
21782178 "name": "example.com/test_empty_rootfs_pwl",
21792179 "pathWhitelist": ["foo"]
21802180 }
1111 // See the License for the specific language governing permissions and
1212 // limitations under the License.
1313
14 // +build linux freebsd netbsd openbsd
14 // +build linux freebsd netbsd openbsd darwin
1515
1616 package device
1717
1919 imj := `
2020 {
2121 "acKind": "ImageManifest",
22 "acVersion": "0.6.1",
22 "acVersion": "0.7.1",
2323 "name": "example.com/test"
2424 }
2525 `
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package lastditch
15
16 import (
17 "fmt"
18 "strings"
19 )
20
21 // extJ returns a JSON snippet describing an extra field with a given
22 // name
23 func extJ(name string) string {
24 return fmt.Sprintf(`"%s": [],`, name)
25 }
26
27 // labsJ returns a labels array JSON snippet with given labels
28 func labsJ(labels ...string) string {
29 return fmt.Sprintf("[%s]", strings.Join(labels, ","))
30 }
31
32 // labsI returns a labels array instance with given labels
33 func labsI(labels ...Label) Labels {
34 if labels == nil {
35 return Labels{}
36 }
37 return labels
38 }
39
40 // labJ returns a label JSON snippet with given name and value
41 func labJ(name, value, extra string) string {
42 return fmt.Sprintf(`
43 {
44 %s
45 "name": "%s",
46 "value": "%s"
47 }`, extra, name, value)
48 }
49
50 // labI returns a label instance with given name and value
51 func labI(name, value string) Label {
52 return Label{
53 Name: name,
54 Value: value,
55 }
56 }
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // Package lastditch provides fallback redefinitions of parts of
15 // schemas provided by schema package.
16 //
17 // Almost no validation of schemas is done (besides checking if data
18 // really is `JSON`-encoded and kind is either `ImageManifest` or
19 // `PodManifest`. This is to get as much data as possible from an
20 // invalid manifest. The main aim of the package is to be used for the
21 // better error reporting. The another aim might be to force some
22 // operation (like removing a pod), which would otherwise fail because
23 // of an invalid manifest.
24 //
25 // To avoid validation during deserialization, types provided by this
26 // package use plain strings.
27 package lastditch
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package lastditch
15
16 import (
17 "encoding/json"
18
19 "github.com/appc/spec/schema"
20 "github.com/appc/spec/schema/types"
21 )
22
23 type ImageManifest struct {
24 ACVersion string `json:"acVersion"`
25 ACKind string `json:"acKind"`
26 Name string `json:"name"`
27 Labels Labels `json:"labels,omitempty"`
28 }
29
30 // a type just to avoid a recursion during unmarshalling
31 type imageManifest ImageManifest
32
33 func (im *ImageManifest) UnmarshalJSON(data []byte) error {
34 i := imageManifest(*im)
35 err := json.Unmarshal(data, &i)
36 if err != nil {
37 return err
38 }
39 if i.ACKind != string(schema.ImageManifestKind) {
40 return types.InvalidACKindError(schema.ImageManifestKind)
41 }
42 *im = ImageManifest(i)
43 return nil
44 }
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package lastditch
15
16 import (
17 "fmt"
18 "reflect"
19 "testing"
20 )
21
22 func TestInvalidImageManifest(t *testing.T) {
23 tests := []struct {
24 desc string
25 json string
26 expected ImageManifest
27 }{
28 {
29 desc: "Check an empty image manifest",
30 json: imgJ("", labsJ(), ""),
31 expected: imgI("", labsI()),
32 },
33 {
34 desc: "Check an image manifest with an empty label",
35 json: imgJ("example.com/test!", labsJ(labJ("", "", "")), ""),
36 expected: imgI("example.com/test!", labsI(labI("", ""))),
37 },
38 {
39 desc: "Check an image manifest with an invalid name",
40 json: imgJ("example.com/test!", labsJ(), ""),
41 expected: imgI("example.com/test!", labsI()),
42 },
43 {
44 desc: "Check an image manifest with labels with invalid names",
45 json: imgJ("im", labsJ(labJ("!n1", "v1", ""), labJ("N2~", "v2", "")), ""),
46 expected: imgI("im", labsI(labI("!n1", "v1"), labI("N2~", "v2"))),
47 },
48 {
49 desc: "Check an image manifest with duplicated labels",
50 json: imgJ("im", labsJ(labJ("n1", "v1", ""), labJ("n1", "v2", "")), ""),
51 expected: imgI("im", labsI(labI("n1", "v1"), labI("n1", "v2"))),
52 },
53 {
54 desc: "Check an image manifest with some extra fields",
55 json: imgJ("im", labsJ(), extJ("stuff")),
56 expected: imgI("im", labsI()),
57 },
58 {
59 desc: "Check an image manifest with a label containing some extra fields",
60 json: imgJ("im", labsJ(labJ("n1", "v1", extJ("clutter"))), extJ("stuff")),
61 expected: imgI("im", labsI(labI("n1", "v1"))),
62 },
63 }
64 for _, tt := range tests {
65 got := ImageManifest{}
66 if err := got.UnmarshalJSON([]byte(tt.json)); err != nil {
67 t.Errorf("%s: unexpected error during unmarshalling image manifest: %v", tt.desc, err)
68 }
69 if !reflect.DeepEqual(tt.expected, got) {
70 t.Errorf("%s: did not get expected image manifest, got:\n %#v\nexpected:\n %#v", tt.desc, got, tt.expected)
71 }
72 }
73 }
74
75 func TestBogusImageManifest(t *testing.T) {
76 bogus := []string{`
77 {
78 "acKind": "Bogus",
79 "acVersion": "0.7.1",
80 }
81 `, `
82 <html>
83 <head>
84 <title>Certainly not a JSON</title>
85 </head>
86 </html>`,
87 }
88
89 for _, str := range bogus {
90 im := ImageManifest{}
91 if im.UnmarshalJSON([]byte(str)) == nil {
92 t.Errorf("bogus image manifest unmarshalled successfully: %s", str)
93 }
94 }
95 }
96
97 // imgJ returns an image manifest JSON with given name and labels
98 func imgJ(name, labels, extra string) string {
99 return fmt.Sprintf(`
100 {
101 %s
102 "acKind": "ImageManifest",
103 "acVersion": "0.7.1",
104 "name": "%s",
105 "labels": %s
106 }`, extra, name, labels)
107 }
108
109 // imgI returns an image manifest instance with given name and labels
110 func imgI(name string, labels Labels) ImageManifest {
111 return ImageManifest{
112 ACVersion: "0.7.1",
113 ACKind: "ImageManifest",
114 Name: name,
115 Labels: labels,
116 }
117 }
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package lastditch
15
16 import (
17 "encoding/json"
18 )
19
20 type Labels []Label
21
22 // a type just to avoid a recursion during unmarshalling
23 type labels Labels
24
25 type Label struct {
26 Name string `json:"name"`
27 Value string `json:"value"`
28 }
29
30 func (l *Labels) UnmarshalJSON(data []byte) error {
31 var jl labels
32 if err := json.Unmarshal(data, &jl); err != nil {
33 return err
34 }
35 *l = Labels(jl)
36 return nil
37 }
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package lastditch
15
16 import (
17 "encoding/json"
18
19 "github.com/appc/spec/schema"
20 "github.com/appc/spec/schema/types"
21 )
22
23 type PodManifest struct {
24 ACVersion string `json:"acVersion"`
25 ACKind string `json:"acKind"`
26 Apps AppList `json:"apps"`
27 }
28
29 type AppList []RuntimeApp
30
31 type RuntimeApp struct {
32 Name string `json:"name"`
33 Image RuntimeImage `json:"image"`
34 }
35
36 type RuntimeImage struct {
37 Name string `json:"name"`
38 ID string `json:"id"`
39 Labels Labels `json:"labels,omitempty"`
40 }
41
42 // a type just to avoid a recursion during unmarshalling
43 type podManifest PodManifest
44
45 func (pm *PodManifest) UnmarshalJSON(data []byte) error {
46 p := podManifest(*pm)
47 err := json.Unmarshal(data, &p)
48 if err != nil {
49 return err
50 }
51 if p.ACKind != string(schema.PodManifestKind) {
52 return types.InvalidACKindError(schema.PodManifestKind)
53 }
54 *pm = PodManifest(p)
55 return nil
56 }
0 // Copyright 2015 The appc Authors
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package lastditch
15
16 import (
17 "fmt"
18 "reflect"
19 "strings"
20 "testing"
21 )
22
23 func TestInvalidPodManifest(t *testing.T) {
24 tests := []struct {
25 desc string
26 json string
27 expected PodManifest
28 }{
29 {
30 desc: "Check an empty pod manifest",
31 json: podJ(appsJ(), ""),
32 expected: podI(appsI()),
33 },
34 {
35 desc: "Check a pod manifest with an empty app",
36 json: podJ(appsJ(appJ("", rImgJ("i", "id", labsJ(), ""), "")), ""),
37 expected: podI(appsI(appI("", rImgI("i", "id", labsI())))),
38 },
39 {
40 desc: "Check a pod manifest with an app based on an empty image",
41 json: podJ(appsJ(appJ("a", rImgJ("", "", labsJ(), ""), "")), ""),
42 expected: podI(appsI(appI("a", rImgI("", "", labsI())))),
43 },
44 {
45 desc: "Check a pod manifest with an app based on an image containing an empty label",
46 json: podJ(appsJ(appJ("a", rImgJ("", "", labsJ(labJ("", "", "")), ""), "")), ""),
47 expected: podI(appsI(appI("a", rImgI("", "", labsI(labI("", "")))))),
48 },
49 {
50 desc: "Check a pod manifest with an invalid app name",
51 json: podJ(appsJ(appJ("!", rImgJ("i", "id", labsJ(), ""), "")), ""),
52 expected: podI(appsI(appI("!", rImgI("i", "id", labsI())))),
53 },
54 {
55 desc: "Check a pod manifest with duplicated app names",
56 json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(), ""), ""), appJ("a", rImgJ("", "", labsJ(), ""), "")), ""),
57 expected: podI(appsI(appI("a", rImgI("i", "id", labsI())), appI("a", rImgI("", "", labsI())))),
58 },
59 {
60 desc: "Check a pod manifest with an invalid image name and ID",
61 json: podJ(appsJ(appJ("?", rImgJ("!!!", "&&&", labsJ(), ""), "")), ""),
62 expected: podI(appsI(appI("?", rImgI("!!!", "&&&", labsI())))),
63 },
64 {
65 desc: "Check a pod manifest with an app based on an image containing labels with invalid names",
66 json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(labJ("!n1", "v1", ""), labJ("N2~", "v2", "")), ""), "")), ""),
67 expected: podI(appsI(appI("a", rImgI("i", "id", labsI(labI("!n1", "v1"), labI("N2~", "v2")))))),
68 },
69 {
70 desc: "Check a pod manifest with an app based on an image containing repeated labels",
71 json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(labJ("n1", "v1", ""), labJ("n1", "v2", "")), ""), "")), ""),
72 expected: podI(appsI(appI("a", rImgI("i", "id", labsI(labI("n1", "v1"), labI("n1", "v2")))))),
73 },
74 {
75 desc: "Check a pod manifest with some extra fields",
76 json: podJ(appsJ(), extJ("goblins")),
77 expected: podI(appsI()),
78 },
79 {
80 desc: "Check a pod manifest with an app containing some extra fields",
81 json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(), ""), extJ("trolls"))), extJ("goblins")),
82 expected: podI(appsI(appI("a", rImgI("i", "id", labsI())))),
83 },
84 {
85 desc: "Check a pod manifest with an app based on an image containing some extra fields",
86 json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(), extJ("stuff")), extJ("trolls"))), extJ("goblins")),
87 expected: podI(appsI(appI("a", rImgI("i", "id", labsI())))),
88 },
89 {
90 desc: "Check a pod manifest with an app based on an image containing labels with some extra fields",
91 json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(labJ("n", "v", extJ("color"))), extJ("stuff")), extJ("trolls"))), extJ("goblins")),
92 expected: podI(appsI(appI("a", rImgI("i", "id", labsI(labI("n", "v")))))),
93 },
94 }
95 for _, tt := range tests {
96 got := PodManifest{}
97 if err := got.UnmarshalJSON([]byte(tt.json)); err != nil {
98 t.Errorf("%s: unexpected error during unmarshalling pod manifest: %v", tt.desc, err)
99 }
100 if !reflect.DeepEqual(tt.expected, got) {
101 t.Errorf("%s: did not get expected pod manifest, got:\n %#v\nexpected:\n %#v", tt.desc, got, tt.expected)
102 }
103 }
104 }
105
106 func TestBogusPodManifest(t *testing.T) {
107 bogus := []string{
108 `
109 {
110 "acKind": "Bogus",
111 "acVersion": "0.7.1",
112 }
113 `,
114 `
115 <html>
116 <head>
117 <title>Certainly not a JSON</title>
118 </head>
119 </html>`,
120 }
121
122 for _, str := range bogus {
123 pm := PodManifest{}
124 if pm.UnmarshalJSON([]byte(str)) == nil {
125 t.Errorf("bogus pod manifest unmarshalled successfully: %s", str)
126 }
127 }
128 }
129
130 // podJ returns a pod manifest JSON with given apps
131 func podJ(apps, extra string) string {
132 return fmt.Sprintf(`
133 {
134 %s
135 "acKind": "PodManifest",
136 "acVersion": "0.7.1",
137 "apps": %s
138 }`, extra, apps)
139 }
140
141 // podI returns a pod manifest instance with given apps
142 func podI(apps AppList) PodManifest {
143 return PodManifest{
144 ACVersion: "0.7.1",
145 ACKind: "PodManifest",
146 Apps: apps,
147 }
148 }
149
150 // appsJ returns an applist JSON snippet with given apps
151 func appsJ(apps ...string) string {
152 return fmt.Sprintf("[%s]", strings.Join(apps, ","))
153 }
154
155 // appsI returns an applist instance with given apps
156 func appsI(apps ...RuntimeApp) AppList {
157 if apps == nil {
158 return AppList{}
159 }
160 return apps
161 }
162
163 // appJ returns an app JSON snippet with given name and image
164 func appJ(name, image, extra string) string {
165 return fmt.Sprintf(`
166 {
167 %s
168 "name": "%s",
169 "image": %s
170 }`, extra, name, image)
171 }
172
173 // appI returns an app instance with given name and image
174 func appI(name string, image RuntimeImage) RuntimeApp {
175 return RuntimeApp{
176 Name: name,
177 Image: image,
178 }
179 }
180
181 // rImgJ returns a runtime image JSON snippet with given name, id and
182 // labels
183 func rImgJ(name, id, labels, extra string) string {
184 return fmt.Sprintf(`
185 {
186 %s
187 "name": "%s",
188 "id": "%s",
189 "labels": %s
190 }`, extra, name, id, labels)
191 }
192
193 // rImgI returns a runtime image instance with given name, id and
194 // labels
195 func rImgI(name, id string, labels Labels) RuntimeImage {
196 return RuntimeImage{
197 Name: name,
198 ID: id,
199 Labels: labels,
200 }
201 }
125125 return nil
126126 }
127127
128 // Mount describes the mapping between a volume and an apps
129 // MountPoint that will be fulfilled at runtime.
128 // Mount describes the mapping between a volume and the path it is mounted
129 // inside of an app's filesystem.
130130 type Mount struct {
131 Volume types.ACName `json:"volume"`
132 MountPoint types.ACName `json:"mountPoint"`
131 Volume types.ACName `json:"volume"`
132 Path string `json:"path"`
133133 }
134134
135135 func (r Mount) assertValid() error {
136136 if r.Volume.Empty() {
137137 return errors.New("volume must be set")
138138 }
139 if r.MountPoint.Empty() {
140 return errors.New("mountPoint must be set")
139 if r.Path == "" {
140 return errors.New("path must be set")
141141 }
142142 return nil
143143 }
2121 )
2222
2323 type App struct {
24 Exec Exec `json:"exec"`
25 EventHandlers []EventHandler `json:"eventHandlers,omitempty"`
26 User string `json:"user"`
27 Group string `json:"group"`
28 WorkingDirectory string `json:"workingDirectory,omitempty"`
29 Environment Environment `json:"environment,omitempty"`
30 MountPoints []MountPoint `json:"mountPoints,omitempty"`
31 Ports []Port `json:"ports,omitempty"`
32 Isolators Isolators `json:"isolators,omitempty"`
24 Exec Exec `json:"exec"`
25 EventHandlers []EventHandler `json:"eventHandlers,omitempty"`
26 User string `json:"user"`
27 Group string `json:"group"`
28 SupplementaryGIDs []int `json:"supplementaryGIDs,omitempty"`
29 WorkingDirectory string `json:"workingDirectory,omitempty"`
30 Environment Environment `json:"environment,omitempty"`
31 MountPoints []MountPoint `json:"mountPoints,omitempty"`
32 Ports []Port `json:"ports,omitempty"`
33 Isolators Isolators `json:"isolators,omitempty"`
3334 }
3435
3536 // app is a model to facilitate extra validation during the
1717 "encoding/json"
1818 "errors"
1919
20 "github.com/appc/spec/Godeps/_workspace/src/github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
20 "github.com/appc/spec/Godeps/_workspace/src/k8s.io/kubernetes/pkg/api/resource"
2121 )
2222
2323 var (
9797 {
9898 `{
9999 "name": "resource/cpu",
100 "value": {"request": "30", "limit": "1"}
100 "value": {"request": "30m", "limit": "1m"}
101101 }`,
102102 false,
103103 },
152152 [
153153 {
154154 "name": "resource/cpu",
155 "value": {"request": "30", "limit": "1"}
155 "value": {"request": "30m", "limit": "1m"}
156156 },
157157 {
158158 "name": "resource/memory",
201201 var r Resource = v
202202 glimit := r.Limit()
203203 grequest := r.Request()
204 if glimit.Value() != tt.wlimit || grequest.Value() != tt.wrequest {
205 t.Errorf("#%d: glimit=%v, want %v, grequest=%v, want %v", i, glimit.Value(), tt.wlimit, grequest.Value(), tt.wrequest)
206 }
204
205 var vlimit, vrequest int64
206 if tt.name == "resource/cpu" {
207 vlimit, vrequest = glimit.MilliValue(), grequest.MilliValue()
208 } else {
209 vlimit, vrequest = glimit.Value(), grequest.Value()
210 }
211
212 if vlimit != tt.wlimit {
213 t.Errorf("#%d: glimit=%v, want %v", i, vlimit, tt.wlimit)
214 }
215 if vrequest != tt.wrequest {
216 t.Errorf("#%d: grequest=%v, want %v", i, vrequest, tt.wrequest)
217 }
218
207219 case LinuxCapabilitiesSet:
208220 var s LinuxCapabilitiesSet = v
209221 if !reflect.DeepEqual(s.Set(), tt.wset) {
3434 Value string `json:"value"`
3535 }
3636
37 func (l Labels) assertValid() error {
38 seen := map[ACIdentifier]string{}
39 for _, lbl := range l {
40 if lbl.Name == "name" {
41 return fmt.Errorf(`invalid label name: "name"`)
42 }
43 _, ok := seen[lbl.Name]
44 if ok {
45 return fmt.Errorf(`duplicate labels of name %q`, lbl.Name)
46 }
47 seen[lbl.Name] = lbl.Value
48 }
49 if os, ok := seen["os"]; ok {
50 if validArchs, ok := ValidOSArch[os]; !ok {
37 // IsValidOsArch checks if a OS-architecture combination is valid given a map
38 // of valid OS-architectures
39 func IsValidOSArch(labels map[ACIdentifier]string, validOSArch map[string][]string) error {
40 if os, ok := labels["os"]; ok {
41 if validArchs, ok := validOSArch[os]; !ok {
5142 // Not a whitelisted OS. TODO: how to warn rather than fail?
52 validOses := make([]string, 0, len(ValidOSArch))
53 for validOs := range ValidOSArch {
43 validOses := make([]string, 0, len(validOSArch))
44 for validOs := range validOSArch {
5445 validOses = append(validOses, validOs)
5546 }
5647 sort.Strings(validOses)
5849 } else {
5950 // Whitelisted OS. We check arch here, as arch makes sense only
6051 // when os is defined.
61 if arch, ok := seen["arch"]; ok {
52 if arch, ok := labels["arch"]; ok {
6253 found := false
6354 for _, validArch := range validArchs {
6455 if arch == validArch {
7364 }
7465 }
7566 return nil
67 }
68
69 func (l Labels) assertValid() error {
70 seen := map[ACIdentifier]string{}
71 for _, lbl := range l {
72 if lbl.Name == "name" {
73 return fmt.Errorf(`invalid label name: "name"`)
74 }
75 _, ok := seen[lbl.Name]
76 if ok {
77 return fmt.Errorf(`duplicate labels of name %q`, lbl.Name)
78 }
79 seen[lbl.Name] = lbl.Value
80 }
81 return IsValidOSArch(seen, ValidOSArch)
7682 }
7783
7884 func (l Labels) MarshalJSON() ([]byte, error) {
4343 return &v, nil
4444 }
4545
46 func (sv SemVer) LessThanMajor(versionB SemVer) bool {
47 majorA := semver.Version(sv).Major
48 majorB := semver.Version(versionB).Major
49 if majorA < majorB {
50 return true
51 }
52 return false
53 }
54
55 func (sv SemVer) LessThanExact(versionB SemVer) bool {
56 vA := semver.Version(sv)
57 vB := semver.Version(versionB)
58 return vA.LessThan(vB)
59 }
60
4661 func (sv SemVer) String() string {
4762 s := semver.Version(sv)
4863 return s.String()
2121 // version represents the canonical version of the appc spec and tooling.
2222 // For now, the schema and tooling is coupled with the spec itself, so
2323 // this must be kept in sync with the VERSION file in the root of the repo.
24 version string = "0.6.1"
24 version string = "0.7.1"
2525 )
2626
2727 var (
2525 Each app in a pod will start chrooted into its own unique read-write filesystem before execution.
2626
2727 An app's filesystem must be *rendered* in an empty directory by the following process (or equivalent):
28 - The `rootfs` contained in the ACI is extracted
29 - If the ACI contains a non-empty `dependencies` field in its `ImageManifest`, the `rootfs` of each dependent image is extracted, in the order in which they are listed
30 - If the ACI contains a non-empty `pathWhitelist` field in its `ImageManifest`, *all* paths not in the whitelist must be removed
28
29 1. If the ACI contains a non-empty `dependencies` field in its `ImageManifest`, the `rootfs` of each dependent image is extracted into the target directory, in the order in which they are listed.
30 2. The `rootfs` contained in the ACI is extracted into the target directory
31 3. If the ACI contains a non-empty `pathWhitelist` field in its `ImageManifest`, *all* paths not in the whitelist must be removed from the target directory
32
33 If during rootfs extraction a path is already present in the target directory from an earlier dependency, the previously extracted path MUST be overwritten. If the existing path is a symbolic link to a directory, the link MUST NOT be followed and it MUST be removed and replaced with the new path.
3134
3235 Every execution of an app MUST start from a clean copy of this rendered filesystem.
3336
4851 For example, say that the worker-backup and reduce-worker both have a `mountPoint` named "work".
4952 In this case, the executor will bind mount the host's `/opt/tenant1/work` directory into the `path` of each of the matching "work" `mountPoint`s of the two app filesystems.
5053
54 If the target `path` does not exist in the rendered filesystem, it SHOULD be created, including any missing parent directories.
55
56 If the target `path` is a non-empty directory, its contents SHOULD be discarded (e.g. obscured by the bind mount).
57 If the target `path` refers to a file, the ACE SHOULD remove that file and create a directory in its place.
58 In either of these cases, the ACE SHOULD warn the user that existing files are being masked by a volume.
59
60 If multiple targets have overlapping target `path`s (for example, if one is nested within another), the ACE SHOULD consider this an error.
61
62 The target `path` directories that the ACE creates SHOULD be owned by UID 0 and GID 0, and have access mode `0755` (`rwxr-xr-xr-x`).
63 The ACE implementation MAY provide a method for administrator to specify different permissions on a per-pod basis.
64
65 The ACE SHOULD NOT create any paths in the host file system, and MUST consider missing volume source paths an error.
66 If the ACE does modify the host file system, it SHOULD be possible to disable this behaviour.
67 The ACE MAY implement single-file volumes if the underlying operating system supports it.
68
69 If the host volume's `source` path is a symbolic link, the ACE SHOULD consider it an error, and SHOULD NOT attempt to use this link's target as volume.
70 The ACE SHOULD also consider it an error if any intermediate directory in volume's `source` path is a symbolic link.
71 If the ACE chooses to support symbolic links as volume sources, it SHOULD provide a way to enable or disable this behaviour on a per-pod basis (e.g. as a boolean isolator or a command line switch).
72
5173 #### Network Setup
5274
5375 A Pod must have a loopback network interface and zero or more [layer 3](http://en.wikipedia.org/wiki/Network_layer) (commonly called the IP layer) network interfaces, which can be instantiated in any number of ways (e.g. veth, macvlan, ipvlan, device pass-through).
143165 Limit and request quantities must always be represented internally (i.e. for encoding and any processing) as an integer value (i.e. NOT floating point) in a resource type's natural base units (e.g., bytes, not megabytes or gigabytes).
144166 For convenience, when specified by users quantities may either be unsuffixed, have metric suffices (E, P, T, G, M, K) or binary (power-of-two) suffices (Ei, Pi, Ti, Gi, Mi, Ki).
145167 For example, the following strings represent the same value: "128974848", "125952Ki", "123Mi".
146 Sub-units (e.g. decimals, "0.3", or milli-units, "300m") are NOT permissible.
168 Small quantities can be represented directly as decimals (e.g., 0.3), or using milli-units (e.g., "300m").
147169
148170 #### resource/block-bandwidth
149171
185207
186208 **Parameters:**
187209
188 * **request** milli-cores that are requested
189 * **limit** milli-cores that can be consumed before the kernel temporarily throttles the process
210 * **request** cores that are requested
211 * **limit** cores that can be consumed before the kernel temporarily throttles the process
190212
191213 ```json
192214 "name": "resource/cpu",
193215 "value": {
194 "request": "250",
195 "limit": "500"
196 }
197 ```
198
199 **Note**: a milli-core is the milli-seconds/second that the app/pod will be able to run. e.g. 1000 would represent full use of a single CPU core every second.
216 "request": "250m",
217 "limit": "500m"
218 }
219 ```
220
221 **Note**: a core is the seconds/second that the app/pod will be able to run. e.g. 1 (or 1000m for 1000 milli-seconds) would represent full use of a single CPU core every second.
200222
201223 #### resource/memory
202224
7777 ```json
7878 {
7979 "acKind": "ImageManifest",
80 "acVersion": "0.6.1",
80 "acVersion": "0.7.1",
8181 "name": "example.com/reduce-worker",
8282 "labels": [
8383 {
100100 ],
101101 "user": "100",
102102 "group": "300",
103 "supplementaryGids": [
104 400,
105 500
106 ],
103107 "eventHandlers": [
104108 {
105109 "exec": [
126130 {
127131 "name": "resource/cpu",
128132 "value": {
129 "request": "250",
130 "limit": "500"
133 "request": "250m",
134 "limit": "500m"
131135 }
132136 },
133137 {
220224 * **app** (object, optional) if present, defines the default parameters that can be used to execute this image as an application.
221225 * **exec** (list of strings, required) executable to launch and any flags (must be non-empty; the executable must be an absolute path within the app rootfs; ACE can append or override). These strings are not evaluated in any way and environment variables are not substituted.
222226 * **user**, **group** (string, required) indicates either the username/group name or the UID/GID the app is to be run as (freeform string). The user and group values may be all numbers to indicate a UID/GID, however it is possible on some systems (POSIX) to have usernames that are all numerical. The user and group values will first be resolved using the image's own `/etc/passwd` or `/etc/group`. If no valid matches are found, then if the string is all numerical, it shall be converted to an integer and used as the UID/GID. If the user or group field begins with a "/", the owner and group of the file found at that absolute path inside the rootfs is used as the UID/GID of the process. Example values for the fields include `root`, `1000`, or `/usr/bin/ping`.
227 * **supplementaryGIDs** (list of unsigned integers, optional) indicates additional (supplementary) group IDs (GIDs) as which the app's processes should run.
223228 * **eventHandlers** (list of objects, optional) allows the app to have several hooks based on lifecycle events. For example, you may want to execute a script before the main process starts up to download a dataset or backup onto the filesystem. An eventHandler is a simple object with two fields - an **exec** (array of strings, ACE can append or override), and a **name** (there may be only one eventHandler of a given name), which must be one of:
224229 * **pre-start** - executed and must exit before the long running main **exec** binary is launched
225230 * **post-stop** - executed if the main **exec** process is killed. This can be used to cleanup resources in the case of clean application shutdown, but cannot be relied upon in the face of machine failure.
226231 * **workingDirectory** (string, optional) working directory of the launched application, relative to the application image's root (must be an absolute path, defaults to "/", ACE can override). If the directory does not exist in the application's assembled rootfs (including any dependent images and mounted volumes), the ACE must fail execution.
227232 * **environment** (list of objects, optional) represents the app's environment variables (ACE can append). The listed objects must have two key-value pairs: **name** and **value**. The **name** must consist solely of letters, digits, and underscores '_' as outlined in [IEEE Std 1003.1-2001](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html). The **value** is an arbitrary string. These values are not evaluated in any way, and no substitutions are made.
228233 * **isolators** (list of objects of type [Isolator](types.md#isolator-type), optional) list of isolation steps that SHOULD be applied to the app.
229 * **mountPoints** (list of objects, optional) locations where an app is expecting external data to be mounted. The listed objects contain the following key-value pairs: the **name** indicates an executor-defined label to look up a mount point, and the **path** stipulates where it is to be mounted inside the rootfs. The name is restricted to the [AC Name](types.md#ac-name-type) Type formatting. **readOnly** is a boolean indicating whether or not the mount point will be read-only (defaults to "false" if unsupplied).
234 * **mountPoints** (list of objects, optional) locations where an app is expecting external data to be mounted. The listed objects contain the following key-value pairs: the **name** indicates a label to refer to a mount point (which may be used by the executor when resolving a mount to a volume in the PodManifest), and the **path** stipulates where it is to be mounted inside the rootfs. The name is restricted to the [AC Name](types.md#ac-name-type) Type formatting. **readOnly** is a boolean indicating whether or not the mount point will be read-only (defaults to "false" if unsupplied).
230235 * **ports** (list of objects, optional) ports that this app will be listening on once started. This field is informational: example uses include helping users to discover the listening ports of the application, or indicating to executors ports that should be exposed on the host. This information could also optionally be used to limit the inbound connections to the container via firewall rules to only ports that are explicitly exposed. Each object can represent either a single port or a port range (contiguous set of ports).
231236 * **name** (string, required, restricted to the [AC Name](#ac-name-type) formatting) descriptive name for this port; for example, "http" or "database". This field is used as a key in the [Pod Manifest](#pod-manifest-schema) when specifying ports to be forwarded from the host.
232237 * **protocol** (string, required) protocol that will be used on this port. MAY be any value, but typically SHOULD be a transport layer (Layer 4) protocol - for example, "tcp" or "udp". The executor MAY refuse to execute if this field contains an unrecognized value.
237242 * **imageName** (string of type [AC Identifier](types.md#ac-identifier-type), required) name of the dependent App Container Image.
238243 * **imageID** (string of type [Image ID](types.md#image-id-type), optional) content hash of the dependency. If provided, the retrieved dependency must match the hash. This can be used to produce deterministic, repeatable builds of an App Container Image that has dependencies.
239244 * **labels** (list of objects, optional) a list of the very same form as the aforementioned label objects in the top level ImageManifest. See [Dependency Matching](#dependency-matching) for how these are used.
245 * **size** (integer, optional) the size of the image referenced dependency, in bytes. This field is optional; if it is present, the ACE SHOULD ensure it matches when retrieving a dependency, to mitigate "endless data" attacks.
240246 * **pathWhitelist** (list of strings, optional) whitelist of absolute paths that will exist in the app's rootfs after rendering. This must be a complete and absolute set. An empty list is equivalent to an absent value and means that all files in this image and any dependencies will be available in the rootfs.
241 * **size** (integer, optional) the size of the image referenced dependency, in bytes. This field is optional; if it is present, the ACE SHOULD ensure it matches when retrieving a dependency, to mitigate "endless data" attacks.
242247 * **annotations** (list of objects, optional) any extra metadata you wish to add to the image. Each object has two key-value pairs: the *name* is restricted to the [AC Identifier](types.md#ac-identifier-type) formatting and *value* is an arbitrary string. Annotation names must be unique within the list. Annotations can be used by systems outside of the ACE (ACE can override). If you are defining new annotations, please consider submitting them to the specification. If you intend for your field to remain special to your application please be a good citizen and prefix an appropriate namespace to your key names. Recognized annotations include:
243248 * **created** date on which the image was built (string, [timestamps type](types.md#timestamps-type))
244249 * **authors** contact details of the people or organization responsible for the image (freeform string)
248253 #### Dependency Matching
249254
250255 Dependency matching is based on a combination of the three different fields of the dependency - **imageName**, **imageID**, and **labels**.
251 First, the image discovery mechanism is used to locate a dependency based on the **imageName**.
252 If any labels are specified in the dependency, they are passed to the image discovery mechanism, and will be used when locating the image.
253
254 If the image discovery process successfully returns an image, it will be compared as follows.
255 If the dependency specification has an image ID, it will be compared against the hash of image returned, and must match.
256 Otherwise, the labels in the dependency specification are compared against the labels in the retrieved ACI (i.e. in its ImageManifest), and must match.
257 A label is considered to match if it meets one of two criteria:
258 - It is present in the dependency specification and present in the dependency's ImageManifest with the same value.
259 - It is absent from the dependency specification and present in the dependency's ImageManifest, with any value.
256 First, the image discovery mechanism is used to locate a dependency based on the **imageName** and **labels** (see [App Container Image Discovery](discovery.md)).
257
258 If the image discovery process successfully returns an image and the dependency specification has an image ID, it will be compared against the hash of image returned, and MUST match.
259
260260 This facilitates "wildcard" matching and a variety of common usage patterns, like "noarch" or "latest" dependencies.
261261 For example, an ACI containing a set of bash scripts might omit both "os" and "arch", and hence could be used as a dependency by a variety of different ACIs.
262262 Alternatively, an ACI might specify a dependency with no image ID and no "version" label, and the image discovery mechanism could always retrieve the latest version of an ACI.
9999 Implementations of the spec are responsible for enforcing any signature validation rules set in place by the operator.
100100 For example, in a testing environment, signature validation might be disabled, in which case the implementation would omit the signature retrieval.
101101
102 Implementations must ensure that the name in the Image Manifest in the retrieved ACI matches the initial name used for discovery.
102 Implementations MUST ensure that the initial name and labels used for discovery matches the name and labels in the Image Manifest.
103
104 A label is considered to match if it meets one of two criteria:
105 - It is present in the discovery labels and present in the Image Manifest with the same value.
106 - It is absent from the discovery labels and present in Image Manifest, with any value.
103107
104108 ### Authentication
105109
2727
2828 ```json
2929 {
30 "acVersion": "0.6.1",
30 "acVersion": "0.7.1",
3131 "acKind": "PodManifest",
3232 "apps": [
3333 {
5353 "mountPoints": [
5454 {
5555 "name": "work",
56 "path": "/mnt/foo"
56 "path": "/var/lib/work"
5757 }
5858 ]
5959 },
6060 "mounts": [
61 {"volume": "work", "mountPoint": "work"}
61 {
62 "volume": "worklib",
63 "path": "/var/lib/work"
64 }
6265 ]
6366 },
6467 {
9396 ]
9497 },
9598 "mounts": [
96 {"volume": "work", "mountPoint": "backup"}
99 {
100 "volume": "worklib",
101 "path": "/mnt/bar"
102 }
97103 ],
98104 "annotations": [
99105 {
118124 ],
119125 "volumes": [
120126 {
121 "name": "work",
127 "name": "worklib",
122128 "kind": "host",
123129 "source": "/opt/tenant1/work",
124130 "readOnly": true
157163 * **labels** (list of objects, optional) additional labels characterizing the image
158164 * **app** (object, optional) substitute for the app object of the referred image's ImageManifest. See [Image Manifest Schema](aci.md#image-manifest-schema) for what the app object contains.
159165 * **mounts** (list of objects, optional) list of mounts mapping an app mountPoint to a volume. Each mount has the following set of key-value pairs:
160 * **volume** (string, required) name of the volume that will fulfill this mount (restricted to the [AC Name](types.md#ac-name-type) formatting)
161 * **mountPoint** (string, required) name of the app mount point to place the volume on (restricted to the [AC Name](types.md#ac-name-type) formatting)
166 * **volume** (string, required) name of the volume that will fulfill this mount (restricted to the [AC Name](types.md#ac-name-type) formatting); this is a key into the list of `volumes`, below.
167 * **path** (string, required) path inside the app filesystem to mount the volume; generally this will come from one of an app's mountPoint paths. For example, if an app has a mountPoint named "work" with path "/var/lib/work", an executor should map an appropriate volume to fulfill that mountPoint by using a `mount` object with that path.
162168 * **annotations** (list of objects, optional) arbitrary metadata appended to the app. The annotation objects must have a *name* key that has a value that is restricted to the [AC Name](types.md#ac-name-type) formatting and *value* key that is an arbitrary string). Annotation names must be unique within the list. These will be merged with annotations provided by the image manifest when queried via the metadata service; values in this list take precedence over those in the image manifest.
163169 * **volumes** (list of objects, optional) list of volumes which will be mounted into each application's filesystem
164 * **name** (string, required) used to map the volume to an app's mountPoint at runtime. (restricted to the [AC Name](types.md#ac-name-type) formatting)
165 * **kind** (string, required) either "empty" or "host". "empty" fulfills a mount point by ensuring the path exists (i.e., writes go to the app's chroot). "host" fulfills a mount point with a bind mount from a **source**.
170 * **name** (string, required) descriptive label for the volume (restricted to the [AC Name](types.md#ac-name-type) formatting), used as an index by the `mounts` objects (above).
171 * **readOnly** (boolean, optional, defaults to "false" if unsupplied) whether or not the volume will be mounted read only.
172 * **kind** (string, required) either:
173 * **empty** - creates an empty directory on the host and bind mounts it into the container. All containers in the pod share the mount, and the lifetime of the volume is equal to the lifetime of the pod (i.e. the directory on the host machine is removed when the pod's filesystem is garbage collected)
174 * **host** - fulfills a mount point with a bind mount from a **source** directory on the host.
166175 * **source** (string, required if **kind** is "host") absolute path on host to be bind mounted under a mount point in each app's chroot.
167 * **readOnly** (boolean, optional if **kind** is "host", defaults to "false" if unsupplied) whether or not the volume will be mounted read only.
168176 * **isolators** (list of objects of type [Isolator](types.md#isolator-type), optional) list of isolation steps that will apply to this pod.
169177 * **annotations** (list of objects, optional) arbitrary metadata the executor will make available to applications via the metadata service. Objects must contain two key-value pairs: **name** is restricted to the [AC Name](types.md#ac-name-type) formatting and **value** is an arbitrary string). Annotation names must be unique within the list.
170178 * **ports** (list of objects, optional) list of ports that SHOULD be exposed on the host.
1515
1616 source ./build
1717
18 TESTABLE_AND_FORMATTABLE="aci discovery pkg/acirenderer pkg/tarheader schema schema/types"
18 TESTABLE_AND_FORMATTABLE="aci discovery pkg/acirenderer pkg/tarheader schema schema/lastditch schema/types"
1919 FORMATTABLE="$TESTABLE_AND_FORMATTABLE ace actool"
2020
2121 # user has not provided PKG override