diff -Nru charm-2.1.1/debian/changelog charm-2.2.0/debian/changelog --- charm-2.1.1/debian/changelog 2016-04-14 18:06:24.000000000 +0000 +++ charm-2.2.0/debian/changelog 2016-09-03 01:44:02.000000000 +0000 @@ -1,8 +1,25 @@ -charm (2.1.1-0ubuntu1~ubuntu15.10.1~ppa1) wily; urgency=medium +charm (2.2.0-0ubuntu1~ubuntu14.04.1~ppa2) trusty; urgency=medium - * No-change backport to wily + * No-change backport to trusty - -- Marco Ceppi Thu, 14 Apr 2016 14:06:24 -0400 + -- Marco Ceppi Fri, 02 Sep 2016 21:44:02 -0400 + +charm (2.2.0-0ubuntu1) xenial; urgency=medium + + * Replace development channel with edge. + * Prepare for four channels. + * Rename publish to release + * Add term publishing plugin to command whitelist + + -- Marco Ceppi Fri, 02 Sep 2016 21:43:02 -0400 + +charm (2.1.2-0ubuntu1) xenial; urgency=medium + + * Improved VCS log parsing + * charm resources fixes + * d/test: added autopkg tests + + -- Marco Ceppi Thu, 28 Apr 2016 02:09:50 -0400 charm (2.1.1-0ubuntu1) xenial; urgency=medium diff -Nru charm-2.1.1/debian/control charm-2.2.0/debian/control --- charm-2.1.1/debian/control 2016-04-14 13:12:29.000000000 +0000 +++ charm-2.2.0/debian/control 2016-04-28 07:09:07.000000000 +0000 @@ -1,8 +1,7 @@ Source: charm Section: devel Priority: extra -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Marco Ceppi +Maintainer: Marco Ceppi Build-Depends: debhelper (>= 9~), devscripts, golang-go (>= 2:1.2), @@ -15,7 +14,7 @@ Depends: distro-info, ${misc:Depends}, ${shlibs:Depends} Breaks: charm-tools (<< 2.0) Replaces: charm-tools (<< 2.0) -Recommends: charm-tools (>= 2.0), juju2 | juju +Recommends: charm-tools (>= 2.0), juju-2.0 | juju Description: Tool for publishing and maintaining Juju charms The charm command provides commands and tools for managing Juju charms in the Juju charm store. diff -Nru charm-2.1.1/debian/tests/control charm-2.2.0/debian/tests/control --- charm-2.1.1/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/debian/tests/control 2016-04-28 07:08:44.000000000 +0000 @@ -0,0 +1,2 @@ +Tests: run +Depends: @ diff -Nru charm-2.1.1/debian/tests/run charm-2.2.0/debian/tests/run --- charm-2.1.1/debian/tests/run 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/debian/tests/run 2016-04-28 06:14:55.000000000 +0000 @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +charm help + diff -Nru charm-2.1.1/src/github.com/beorn7/perks/histogram/bench_test.go charm-2.2.0/src/github.com/beorn7/perks/histogram/bench_test.go --- charm-2.1.1/src/github.com/beorn7/perks/histogram/bench_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/histogram/bench_test.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,26 @@ +package histogram + +import ( + "math/rand" + "testing" +) + +func BenchmarkInsert10Bins(b *testing.B) { + b.StopTimer() + h := New(10) + b.StartTimer() + for i := 0; i < b.N; i++ { + f := rand.ExpFloat64() + h.Insert(f) + } +} + +func BenchmarkInsert100Bins(b *testing.B) { + b.StopTimer() + h := New(100) + b.StartTimer() + for i := 0; i < b.N; i++ { + f := rand.ExpFloat64() + h.Insert(f) + } +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/histogram/histogram.go charm-2.2.0/src/github.com/beorn7/perks/histogram/histogram.go --- charm-2.1.1/src/github.com/beorn7/perks/histogram/histogram.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/histogram/histogram.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,108 @@ +// Package histogram provides a Go implementation of BigML's histogram package +// for Clojure/Java. It is currently experimental. +package histogram + +import ( + "container/heap" + "math" + "sort" +) + +type Bin struct { + Count int + Sum float64 +} + +func (b *Bin) Update(x *Bin) { + b.Count += x.Count + b.Sum += x.Sum +} + +func (b *Bin) Mean() float64 { + return b.Sum / float64(b.Count) +} + +type Bins []*Bin + +func (bs Bins) Len() int { return len(bs) } +func (bs Bins) Less(i, j int) bool { return bs[i].Mean() < bs[j].Mean() } +func (bs Bins) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] } + +func (bs *Bins) Push(x interface{}) { + *bs = append(*bs, x.(*Bin)) +} + +func (bs *Bins) Pop() interface{} { + return bs.remove(len(*bs) - 1) +} + +func (bs *Bins) remove(n int) *Bin { + if n < 0 || len(*bs) < n { + return nil + } + x := (*bs)[n] + *bs = append((*bs)[:n], (*bs)[n+1:]...) + return x +} + +type Histogram struct { + res *reservoir +} + +func New(maxBins int) *Histogram { + return &Histogram{res: newReservoir(maxBins)} +} + +func (h *Histogram) Insert(f float64) { + h.res.insert(&Bin{1, f}) + h.res.compress() +} + +func (h *Histogram) Bins() Bins { + return h.res.bins +} + +type reservoir struct { + n int + maxBins int + bins Bins +} + +func newReservoir(maxBins int) *reservoir { + return &reservoir{maxBins: maxBins} +} + +func (r *reservoir) insert(bin *Bin) { + r.n += bin.Count + i := sort.Search(len(r.bins), func(i int) bool { + return r.bins[i].Mean() >= bin.Mean() + }) + if i < 0 || i == r.bins.Len() { + // TODO(blake): Maybe use an .insert(i, bin) instead of + // performing the extra work of a heap.Push. + heap.Push(&r.bins, bin) + return + } + r.bins[i].Update(bin) +} + +func (r *reservoir) compress() { + for r.bins.Len() > r.maxBins { + minGapIndex := -1 + minGap := math.MaxFloat64 + for i := 0; i < r.bins.Len()-1; i++ { + gap := gapWeight(r.bins[i], r.bins[i+1]) + if minGap > gap { + minGap = gap + minGapIndex = i + } + } + prev := r.bins[minGapIndex] + next := r.bins.remove(minGapIndex + 1) + prev.Update(next) + } +} + +func gapWeight(prev, next *Bin) float64 { + return next.Mean() - prev.Mean() +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/histogram/histogram_test.go charm-2.2.0/src/github.com/beorn7/perks/histogram/histogram_test.go --- charm-2.1.1/src/github.com/beorn7/perks/histogram/histogram_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/histogram/histogram_test.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,38 @@ +package histogram + +import ( + "math/rand" + "testing" +) + +func TestHistogram(t *testing.T) { + const numPoints = 1e6 + const maxBins = 3 + + h := New(maxBins) + for i := 0; i < numPoints; i++ { + f := rand.ExpFloat64() + h.Insert(f) + } + + bins := h.Bins() + if g := len(bins); g > maxBins { + t.Fatalf("got %d bins, wanted <= %d", g, maxBins) + } + + for _, b := range bins { + t.Logf("%+v", b) + } + + if g := count(h.Bins()); g != numPoints { + t.Fatalf("binned %d points, wanted %d", g, numPoints) + } +} + +func count(bins Bins) int { + binCounts := 0 + for _, b := range bins { + binCounts += b.Count + } + return binCounts +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/LICENSE charm-2.2.0/src/github.com/beorn7/perks/LICENSE --- charm-2.1.1/src/github.com/beorn7/perks/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/LICENSE 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,20 @@ +Copyright (C) 2013 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -Nru charm-2.1.1/src/github.com/beorn7/perks/quantile/bench_test.go charm-2.2.0/src/github.com/beorn7/perks/quantile/bench_test.go --- charm-2.1.1/src/github.com/beorn7/perks/quantile/bench_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/quantile/bench_test.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,63 @@ +package quantile + +import ( + "testing" +) + +func BenchmarkInsertTargeted(b *testing.B) { + b.ReportAllocs() + + s := NewTargeted(Targets) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) { + s := NewTargeted(TargetsSmallEpsilon) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertBiased(b *testing.B) { + s := NewLowBiased(0.01) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) { + s := NewLowBiased(0.0001) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkQuery(b *testing.B) { + s := NewTargeted(Targets) + for i := float64(0); i < 1e6; i++ { + s.Insert(i) + } + b.ResetTimer() + n := float64(b.N) + for i := float64(0); i < n; i++ { + s.Query(i / n) + } +} + +func BenchmarkQuerySmallEpsilon(b *testing.B) { + s := NewTargeted(TargetsSmallEpsilon) + for i := float64(0); i < 1e6; i++ { + s.Insert(i) + } + b.ResetTimer() + n := float64(b.N) + for i := float64(0); i < n; i++ { + s.Query(i / n) + } +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/quantile/exampledata.txt charm-2.2.0/src/github.com/beorn7/perks/quantile/exampledata.txt --- charm-2.1.1/src/github.com/beorn7/perks/quantile/exampledata.txt 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/quantile/exampledata.txt 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,2388 @@ +8 +5 +26 +12 +5 +235 +13 +6 +28 +30 +3 +3 +3 +3 +5 +2 +33 +7 +2 +4 +7 +12 +14 +5 +8 +3 +10 +4 +5 +3 +6 +6 +209 +20 +3 +10 +14 +3 +4 +6 +8 +5 +11 +7 +3 +2 +3 +3 +212 +5 +222 +4 +10 +10 +5 +6 +3 +8 +3 +10 +254 +220 +2 +3 +5 +24 +5 +4 +222 +7 +3 +3 +223 +8 +15 +12 +14 +14 +3 +2 +2 +3 +13 +3 +11 +4 +4 +6 +5 +7 +13 +5 +3 +5 +2 +5 +3 +5 +2 +7 +15 +17 +14 +3 +6 +6 +3 +17 +5 +4 +7 +6 +4 +4 +8 +6 +8 +3 +9 +3 +6 +3 +4 +5 +3 +3 +660 +4 +6 +10 +3 +6 +3 +2 +5 +13 +2 +4 +4 +10 +4 +8 +4 +3 +7 +9 +9 +3 +10 +37 +3 +13 +4 +12 +3 +6 +10 +8 +5 +21 +2 +3 +8 +3 +2 +3 +3 +4 +12 +2 +4 +8 +8 +4 +3 +2 +20 +1 +6 +32 +2 +11 +6 +18 +3 +8 +11 +3 +212 +3 +4 +2 +6 +7 +12 +11 +3 +2 +16 +10 +6 +4 +6 +3 +2 +7 +3 +2 +2 +2 +2 +5 +6 +4 +3 +10 +3 +4 +6 +5 +3 +4 +4 +5 +6 +4 +3 +4 +4 +5 +7 +5 +5 +3 +2 +7 +2 +4 +12 +4 +5 +6 +2 +4 +4 +8 +4 +15 +13 +7 +16 +5 +3 +23 +5 +5 +7 +3 +2 +9 +8 +7 +5 +8 +11 +4 +10 +76 +4 +47 +4 +3 +2 +7 +4 +2 +3 +37 +10 +4 +2 +20 +5 +4 +4 +10 +10 +4 +3 +7 +23 +240 +7 +13 +5 +5 +3 +3 +2 +5 +4 +2 +8 +7 +19 +2 +23 +8 +7 +2 +5 +3 +8 +3 +8 +13 +5 +5 +5 +2 +3 +23 +4 +9 +8 +4 +3 +3 +5 +220 +2 +3 +4 +6 +14 +3 +53 +6 +2 +5 +18 +6 +3 +219 +6 +5 +2 +5 +3 +6 +5 +15 +4 +3 +17 +3 +2 +4 +7 +2 +3 +3 +4 +4 +3 +2 +664 +6 +3 +23 +5 +5 +16 +5 +8 +2 +4 +2 +24 +12 +3 +2 +3 +5 +8 +3 +5 +4 +3 +14 +3 +5 +8 +2 +3 +7 +9 +4 +2 +3 +6 +8 +4 +3 +4 +6 +5 +3 +3 +6 +3 +19 +4 +4 +6 +3 +6 +3 +5 +22 +5 +4 +4 +3 +8 +11 +4 +9 +7 +6 +13 +4 +4 +4 +6 +17 +9 +3 +3 +3 +4 +3 +221 +5 +11 +3 +4 +2 +12 +6 +3 +5 +7 +5 +7 +4 +9 +7 +14 +37 +19 +217 +16 +3 +5 +2 +2 +7 +19 +7 +6 +7 +4 +24 +5 +11 +4 +7 +7 +9 +13 +3 +4 +3 +6 +28 +4 +4 +5 +5 +2 +5 +6 +4 +4 +6 +10 +5 +4 +3 +2 +3 +3 +6 +5 +5 +4 +3 +2 +3 +7 +4 +6 +18 +16 +8 +16 +4 +5 +8 +6 +9 +13 +1545 +6 +215 +6 +5 +6 +3 +45 +31 +5 +2 +2 +4 +3 +3 +2 +5 +4 +3 +5 +7 +7 +4 +5 +8 +5 +4 +749 +2 +31 +9 +11 +2 +11 +5 +4 +4 +7 +9 +11 +4 +5 +4 +7 +3 +4 +6 +2 +15 +3 +4 +3 +4 +3 +5 +2 +13 +5 +5 +3 +3 +23 +4 +4 +5 +7 +4 +13 +2 +4 +3 +4 +2 +6 +2 +7 +3 +5 +5 +3 +29 +5 +4 +4 +3 +10 +2 +3 +79 +16 +6 +6 +7 +7 +3 +5 +5 +7 +4 +3 +7 +9 +5 +6 +5 +9 +6 +3 +6 +4 +17 +2 +10 +9 +3 +6 +2 +3 +21 +22 +5 +11 +4 +2 +17 +2 +224 +2 +14 +3 +4 +4 +2 +4 +4 +4 +4 +5 +3 +4 +4 +10 +2 +6 +3 +3 +5 +7 +2 +7 +5 +6 +3 +218 +2 +2 +5 +2 +6 +3 +5 +222 +14 +6 +33 +3 +2 +5 +3 +3 +3 +9 +5 +3 +3 +2 +7 +4 +3 +4 +3 +5 +6 +5 +26 +4 +13 +9 +7 +3 +221 +3 +3 +4 +4 +4 +4 +2 +18 +5 +3 +7 +9 +6 +8 +3 +10 +3 +11 +9 +5 +4 +17 +5 +5 +6 +6 +3 +2 +4 +12 +17 +6 +7 +218 +4 +2 +4 +10 +3 +5 +15 +3 +9 +4 +3 +3 +6 +29 +3 +3 +4 +5 +5 +3 +8 +5 +6 +6 +7 +5 +3 +5 +3 +29 +2 +31 +5 +15 +24 +16 +5 +207 +4 +3 +3 +2 +15 +4 +4 +13 +5 +5 +4 +6 +10 +2 +7 +8 +4 +6 +20 +5 +3 +4 +3 +12 +12 +5 +17 +7 +3 +3 +3 +6 +10 +3 +5 +25 +80 +4 +9 +3 +2 +11 +3 +3 +2 +3 +8 +7 +5 +5 +19 +5 +3 +3 +12 +11 +2 +6 +5 +5 +5 +3 +3 +3 +4 +209 +14 +3 +2 +5 +19 +4 +4 +3 +4 +14 +5 +6 +4 +13 +9 +7 +4 +7 +10 +2 +9 +5 +7 +2 +8 +4 +6 +5 +5 +222 +8 +7 +12 +5 +216 +3 +4 +4 +6 +3 +14 +8 +7 +13 +4 +3 +3 +3 +3 +17 +5 +4 +3 +33 +6 +6 +33 +7 +5 +3 +8 +7 +5 +2 +9 +4 +2 +233 +24 +7 +4 +8 +10 +3 +4 +15 +2 +16 +3 +3 +13 +12 +7 +5 +4 +207 +4 +2 +4 +27 +15 +2 +5 +2 +25 +6 +5 +5 +6 +13 +6 +18 +6 +4 +12 +225 +10 +7 +5 +2 +2 +11 +4 +14 +21 +8 +10 +3 +5 +4 +232 +2 +5 +5 +3 +7 +17 +11 +6 +6 +23 +4 +6 +3 +5 +4 +2 +17 +3 +6 +5 +8 +3 +2 +2 +14 +9 +4 +4 +2 +5 +5 +3 +7 +6 +12 +6 +10 +3 +6 +2 +2 +19 +5 +4 +4 +9 +2 +4 +13 +3 +5 +6 +3 +6 +5 +4 +9 +6 +3 +5 +7 +3 +6 +6 +4 +3 +10 +6 +3 +221 +3 +5 +3 +6 +4 +8 +5 +3 +6 +4 +4 +2 +54 +5 +6 +11 +3 +3 +4 +4 +4 +3 +7 +3 +11 +11 +7 +10 +6 +13 +223 +213 +15 +231 +7 +3 +7 +228 +2 +3 +4 +4 +5 +6 +7 +4 +13 +3 +4 +5 +3 +6 +4 +6 +7 +2 +4 +3 +4 +3 +3 +6 +3 +7 +3 +5 +18 +5 +6 +8 +10 +3 +3 +3 +2 +4 +2 +4 +4 +5 +6 +6 +4 +10 +13 +3 +12 +5 +12 +16 +8 +4 +19 +11 +2 +4 +5 +6 +8 +5 +6 +4 +18 +10 +4 +2 +216 +6 +6 +6 +2 +4 +12 +8 +3 +11 +5 +6 +14 +5 +3 +13 +4 +5 +4 +5 +3 +28 +6 +3 +7 +219 +3 +9 +7 +3 +10 +6 +3 +4 +19 +5 +7 +11 +6 +15 +19 +4 +13 +11 +3 +7 +5 +10 +2 +8 +11 +2 +6 +4 +6 +24 +6 +3 +3 +3 +3 +6 +18 +4 +11 +4 +2 +5 +10 +8 +3 +9 +5 +3 +4 +5 +6 +2 +5 +7 +4 +4 +14 +6 +4 +4 +5 +5 +7 +2 +4 +3 +7 +3 +3 +6 +4 +5 +4 +4 +4 +3 +3 +3 +3 +8 +14 +2 +3 +5 +3 +2 +4 +5 +3 +7 +3 +3 +18 +3 +4 +4 +5 +7 +3 +3 +3 +13 +5 +4 +8 +211 +5 +5 +3 +5 +2 +5 +4 +2 +655 +6 +3 +5 +11 +2 +5 +3 +12 +9 +15 +11 +5 +12 +217 +2 +6 +17 +3 +3 +207 +5 +5 +4 +5 +9 +3 +2 +8 +5 +4 +3 +2 +5 +12 +4 +14 +5 +4 +2 +13 +5 +8 +4 +225 +4 +3 +4 +5 +4 +3 +3 +6 +23 +9 +2 +6 +7 +233 +4 +4 +6 +18 +3 +4 +6 +3 +4 +4 +2 +3 +7 +4 +13 +227 +4 +3 +5 +4 +2 +12 +9 +17 +3 +7 +14 +6 +4 +5 +21 +4 +8 +9 +2 +9 +25 +16 +3 +6 +4 +7 +8 +5 +2 +3 +5 +4 +3 +3 +5 +3 +3 +3 +2 +3 +19 +2 +4 +3 +4 +2 +3 +4 +4 +2 +4 +3 +3 +3 +2 +6 +3 +17 +5 +6 +4 +3 +13 +5 +3 +3 +3 +4 +9 +4 +2 +14 +12 +4 +5 +24 +4 +3 +37 +12 +11 +21 +3 +4 +3 +13 +4 +2 +3 +15 +4 +11 +4 +4 +3 +8 +3 +4 +4 +12 +8 +5 +3 +3 +4 +2 +220 +3 +5 +223 +3 +3 +3 +10 +3 +15 +4 +241 +9 +7 +3 +6 +6 +23 +4 +13 +7 +3 +4 +7 +4 +9 +3 +3 +4 +10 +5 +5 +1 +5 +24 +2 +4 +5 +5 +6 +14 +3 +8 +2 +3 +5 +13 +13 +3 +5 +2 +3 +15 +3 +4 +2 +10 +4 +4 +4 +5 +5 +3 +5 +3 +4 +7 +4 +27 +3 +6 +4 +15 +3 +5 +6 +6 +5 +4 +8 +3 +9 +2 +6 +3 +4 +3 +7 +4 +18 +3 +11 +3 +3 +8 +9 +7 +24 +3 +219 +7 +10 +4 +5 +9 +12 +2 +5 +4 +4 +4 +3 +3 +19 +5 +8 +16 +8 +6 +22 +3 +23 +3 +242 +9 +4 +3 +3 +5 +7 +3 +3 +5 +8 +3 +7 +5 +14 +8 +10 +3 +4 +3 +7 +4 +6 +7 +4 +10 +4 +3 +11 +3 +7 +10 +3 +13 +6 +8 +12 +10 +5 +7 +9 +3 +4 +7 +7 +10 +8 +30 +9 +19 +4 +3 +19 +15 +4 +13 +3 +215 +223 +4 +7 +4 +8 +17 +16 +3 +7 +6 +5 +5 +4 +12 +3 +7 +4 +4 +13 +4 +5 +2 +5 +6 +5 +6 +6 +7 +10 +18 +23 +9 +3 +3 +6 +5 +2 +4 +2 +7 +3 +3 +2 +5 +5 +14 +10 +224 +6 +3 +4 +3 +7 +5 +9 +3 +6 +4 +2 +5 +11 +4 +3 +3 +2 +8 +4 +7 +4 +10 +7 +3 +3 +18 +18 +17 +3 +3 +3 +4 +5 +3 +3 +4 +12 +7 +3 +11 +13 +5 +4 +7 +13 +5 +4 +11 +3 +12 +3 +6 +4 +4 +21 +4 +6 +9 +5 +3 +10 +8 +4 +6 +4 +4 +6 +5 +4 +8 +6 +4 +6 +4 +4 +5 +9 +6 +3 +4 +2 +9 +3 +18 +2 +4 +3 +13 +3 +6 +6 +8 +7 +9 +3 +2 +16 +3 +4 +6 +3 +2 +33 +22 +14 +4 +9 +12 +4 +5 +6 +3 +23 +9 +4 +3 +5 +5 +3 +4 +5 +3 +5 +3 +10 +4 +5 +5 +8 +4 +4 +6 +8 +5 +4 +3 +4 +6 +3 +3 +3 +5 +9 +12 +6 +5 +9 +3 +5 +3 +2 +2 +2 +18 +3 +2 +21 +2 +5 +4 +6 +4 +5 +10 +3 +9 +3 +2 +10 +7 +3 +6 +6 +4 +4 +8 +12 +7 +3 +7 +3 +3 +9 +3 +4 +5 +4 +4 +5 +5 +10 +15 +4 +4 +14 +6 +227 +3 +14 +5 +216 +22 +5 +4 +2 +2 +6 +3 +4 +2 +9 +9 +4 +3 +28 +13 +11 +4 +5 +3 +3 +2 +3 +3 +5 +3 +4 +3 +5 +23 +26 +3 +4 +5 +6 +4 +6 +3 +5 +5 +3 +4 +3 +2 +2 +2 +7 +14 +3 +6 +7 +17 +2 +2 +15 +14 +16 +4 +6 +7 +13 +6 +4 +5 +6 +16 +3 +3 +28 +3 +6 +15 +3 +9 +2 +4 +6 +3 +3 +22 +4 +12 +6 +7 +2 +5 +4 +10 +3 +16 +6 +9 +2 +5 +12 +7 +5 +5 +5 +5 +2 +11 +9 +17 +4 +3 +11 +7 +3 +5 +15 +4 +3 +4 +211 +8 +7 +5 +4 +7 +6 +7 +6 +3 +6 +5 +6 +5 +3 +4 +4 +26 +4 +6 +10 +4 +4 +3 +2 +3 +3 +4 +5 +9 +3 +9 +4 +4 +5 +5 +8 +2 +4 +2 +3 +8 +4 +11 +19 +5 +8 +6 +3 +5 +6 +12 +3 +2 +4 +16 +12 +3 +4 +4 +8 +6 +5 +6 +6 +219 +8 +222 +6 +16 +3 +13 +19 +5 +4 +3 +11 +6 +10 +4 +7 +7 +12 +5 +3 +3 +5 +6 +10 +3 +8 +2 +5 +4 +7 +2 +4 +4 +2 +12 +9 +6 +4 +2 +40 +2 +4 +10 +4 +223 +4 +2 +20 +6 +7 +24 +5 +4 +5 +2 +20 +16 +6 +5 +13 +2 +3 +3 +19 +3 +2 +4 +5 +6 +7 +11 +12 +5 +6 +7 +7 +3 +5 +3 +5 +3 +14 +3 +4 +4 +2 +11 +1 +7 +3 +9 +6 +11 +12 +5 +8 +6 +221 +4 +2 +12 +4 +3 +15 +4 +5 +226 +7 +218 +7 +5 +4 +5 +18 +4 +5 +9 +4 +4 +2 +9 +18 +18 +9 +5 +6 +6 +3 +3 +7 +3 +5 +4 +4 +4 +12 +3 +6 +31 +5 +4 +7 +3 +6 +5 +6 +5 +11 +2 +2 +11 +11 +6 +7 +5 +8 +7 +10 +5 +23 +7 +4 +3 +5 +34 +2 +5 +23 +7 +3 +6 +8 +4 +4 +4 +2 +5 +3 +8 +5 +4 +8 +25 +2 +3 +17 +8 +3 +4 +8 +7 +3 +15 +6 +5 +7 +21 +9 +5 +6 +6 +5 +3 +2 +3 +10 +3 +6 +3 +14 +7 +4 +4 +8 +7 +8 +2 +6 +12 +4 +213 +6 +5 +21 +8 +2 +5 +23 +3 +11 +2 +3 +6 +25 +2 +3 +6 +7 +6 +6 +4 +4 +6 +3 +17 +9 +7 +6 +4 +3 +10 +7 +2 +3 +3 +3 +11 +8 +3 +7 +6 +4 +14 +36 +3 +4 +3 +3 +22 +13 +21 +4 +2 +7 +4 +4 +17 +15 +3 +7 +11 +2 +4 +7 +6 +209 +6 +3 +2 +2 +24 +4 +9 +4 +3 +3 +3 +29 +2 +2 +4 +3 +3 +5 +4 +6 +3 +3 +2 +4 diff -Nru charm-2.1.1/src/github.com/beorn7/perks/quantile/example_test.go charm-2.2.0/src/github.com/beorn7/perks/quantile/example_test.go --- charm-2.1.1/src/github.com/beorn7/perks/quantile/example_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/quantile/example_test.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,121 @@ +// +build go1.1 + +package quantile_test + +import ( + "bufio" + "fmt" + "log" + "os" + "strconv" + "time" + + "github.com/beorn7/perks/quantile" +) + +func Example_simple() { + ch := make(chan float64) + go sendFloats(ch) + + // Compute the 50th, 90th, and 99th percentile. + q := quantile.NewTargeted(map[float64]float64{ + 0.50: 0.005, + 0.90: 0.001, + 0.99: 0.0001, + }) + for v := range ch { + q.Insert(v) + } + + fmt.Println("perc50:", q.Query(0.50)) + fmt.Println("perc90:", q.Query(0.90)) + fmt.Println("perc99:", q.Query(0.99)) + fmt.Println("count:", q.Count()) + // Output: + // perc50: 5 + // perc90: 16 + // perc99: 223 + // count: 2388 +} + +func Example_mergeMultipleStreams() { + // Scenario: + // We have multiple database shards. On each shard, there is a process + // collecting query response times from the database logs and inserting + // them into a Stream (created via NewTargeted(0.90)), much like the + // Simple example. These processes expose a network interface for us to + // ask them to serialize and send us the results of their + // Stream.Samples so we may Merge and Query them. + // + // NOTES: + // * These sample sets are small, allowing us to get them + // across the network much faster than sending the entire list of data + // points. + // + // * For this to work correctly, we must supply the same quantiles + // a priori the process collecting the samples supplied to NewTargeted, + // even if we do not plan to query them all here. + ch := make(chan quantile.Samples) + getDBQuerySamples(ch) + q := quantile.NewTargeted(map[float64]float64{0.90: 0.001}) + for samples := range ch { + q.Merge(samples) + } + fmt.Println("perc90:", q.Query(0.90)) +} + +func Example_window() { + // Scenario: We want the 90th, 95th, and 99th percentiles for each + // minute. + + ch := make(chan float64) + go sendStreamValues(ch) + + tick := time.NewTicker(1 * time.Minute) + q := quantile.NewTargeted(map[float64]float64{ + 0.90: 0.001, + 0.95: 0.0005, + 0.99: 0.0001, + }) + for { + select { + case t := <-tick.C: + flushToDB(t, q.Samples()) + q.Reset() + case v := <-ch: + q.Insert(v) + } + } +} + +func sendStreamValues(ch chan float64) { + // Use your imagination +} + +func flushToDB(t time.Time, samples quantile.Samples) { + // Use your imagination +} + +// This is a stub for the above example. In reality this would hit the remote +// servers via http or something like it. +func getDBQuerySamples(ch chan quantile.Samples) {} + +func sendFloats(ch chan<- float64) { + f, err := os.Open("exampledata.txt") + if err != nil { + log.Fatal(err) + } + sc := bufio.NewScanner(f) + for sc.Scan() { + b := sc.Bytes() + v, err := strconv.ParseFloat(string(b), 64) + if err != nil { + log.Fatal(err) + } + ch <- v + } + if sc.Err() != nil { + log.Fatal(sc.Err()) + } + close(ch) +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/quantile/stream.go charm-2.2.0/src/github.com/beorn7/perks/quantile/stream.go --- charm-2.1.1/src/github.com/beorn7/perks/quantile/stream.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/quantile/stream.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,292 @@ +// Package quantile computes approximate quantiles over an unbounded data +// stream within low memory and CPU bounds. +// +// A small amount of accuracy is traded to achieve the above properties. +// +// Multiple streams can be merged before calling Query to generate a single set +// of results. This is meaningful when the streams represent the same type of +// data. See Merge and Samples. +// +// For more detailed information about the algorithm used, see: +// +// Effective Computation of Biased Quantiles over Data Streams +// +// http://www.cs.rutgers.edu/~muthu/bquant.pdf +package quantile + +import ( + "math" + "sort" +) + +// Sample holds an observed value and meta information for compression. JSON +// tags have been added for convenience. +type Sample struct { + Value float64 `json:",string"` + Width float64 `json:",string"` + Delta float64 `json:",string"` +} + +// Samples represents a slice of samples. It implements sort.Interface. +type Samples []Sample + +func (a Samples) Len() int { return len(a) } +func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value } +func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type invariant func(s *stream, r float64) float64 + +// NewLowBiased returns an initialized Stream for low-biased quantiles +// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but +// error guarantees can still be given even for the lower ranks of the data +// distribution. +// +// The provided epsilon is a relative error, i.e. the true quantile of a value +// returned by a query is guaranteed to be within (1±Epsilon)*Quantile. +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error +// properties. +func NewLowBiased(epsilon float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + return 2 * epsilon * r + } + return newStream(ƒ) +} + +// NewHighBiased returns an initialized Stream for high-biased quantiles +// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but +// error guarantees can still be given even for the higher ranks of the data +// distribution. +// +// The provided epsilon is a relative error, i.e. the true quantile of a value +// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile). +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error +// properties. +func NewHighBiased(epsilon float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + return 2 * epsilon * (s.n - r) + } + return newStream(ƒ) +} + +// NewTargeted returns an initialized Stream concerned with a particular set of +// quantile values that are supplied a priori. Knowing these a priori reduces +// space and computation time. The targets map maps the desired quantiles to +// their absolute errors, i.e. the true quantile of a value returned by a query +// is guaranteed to be within (Quantile±Epsilon). +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties. +func NewTargeted(targets map[float64]float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + var m = math.MaxFloat64 + var f float64 + for quantile, epsilon := range targets { + if quantile*s.n <= r { + f = (2 * epsilon * r) / quantile + } else { + f = (2 * epsilon * (s.n - r)) / (1 - quantile) + } + if f < m { + m = f + } + } + return m + } + return newStream(ƒ) +} + +// Stream computes quantiles for a stream of float64s. It is not thread-safe by +// design. Take care when using across multiple goroutines. +type Stream struct { + *stream + b Samples + sorted bool +} + +func newStream(ƒ invariant) *Stream { + x := &stream{ƒ: ƒ} + return &Stream{x, make(Samples, 0, 500), true} +} + +// Insert inserts v into the stream. +func (s *Stream) Insert(v float64) { + s.insert(Sample{Value: v, Width: 1}) +} + +func (s *Stream) insert(sample Sample) { + s.b = append(s.b, sample) + s.sorted = false + if len(s.b) == cap(s.b) { + s.flush() + } +} + +// Query returns the computed qth percentiles value. If s was created with +// NewTargeted, and q is not in the set of quantiles provided a priori, Query +// will return an unspecified result. +func (s *Stream) Query(q float64) float64 { + if !s.flushed() { + // Fast path when there hasn't been enough data for a flush; + // this also yields better accuracy for small sets of data. + l := len(s.b) + if l == 0 { + return 0 + } + i := int(float64(l) * q) + if i > 0 { + i -= 1 + } + s.maybeSort() + return s.b[i].Value + } + s.flush() + return s.stream.query(q) +} + +// Merge merges samples into the underlying streams samples. This is handy when +// merging multiple streams from separate threads, database shards, etc. +// +// ATTENTION: This method is broken and does not yield correct results. The +// underlying algorithm is not capable of merging streams correctly. +func (s *Stream) Merge(samples Samples) { + sort.Sort(samples) + s.stream.merge(samples) +} + +// Reset reinitializes and clears the list reusing the samples buffer memory. +func (s *Stream) Reset() { + s.stream.reset() + s.b = s.b[:0] +} + +// Samples returns stream samples held by s. +func (s *Stream) Samples() Samples { + if !s.flushed() { + return s.b + } + s.flush() + return s.stream.samples() +} + +// Count returns the total number of samples observed in the stream +// since initialization. +func (s *Stream) Count() int { + return len(s.b) + s.stream.count() +} + +func (s *Stream) flush() { + s.maybeSort() + s.stream.merge(s.b) + s.b = s.b[:0] +} + +func (s *Stream) maybeSort() { + if !s.sorted { + s.sorted = true + sort.Sort(s.b) + } +} + +func (s *Stream) flushed() bool { + return len(s.stream.l) > 0 +} + +type stream struct { + n float64 + l []Sample + ƒ invariant +} + +func (s *stream) reset() { + s.l = s.l[:0] + s.n = 0 +} + +func (s *stream) insert(v float64) { + s.merge(Samples{{v, 1, 0}}) +} + +func (s *stream) merge(samples Samples) { + // TODO(beorn7): This tries to merge not only individual samples, but + // whole summaries. The paper doesn't mention merging summaries at + // all. Unittests show that the merging is inaccurate. Find out how to + // do merges properly. + var r float64 + i := 0 + for _, sample := range samples { + for ; i < len(s.l); i++ { + c := s.l[i] + if c.Value > sample.Value { + // Insert at position i. + s.l = append(s.l, Sample{}) + copy(s.l[i+1:], s.l[i:]) + s.l[i] = Sample{ + sample.Value, + sample.Width, + math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1), + // TODO(beorn7): How to calculate delta correctly? + } + i++ + goto inserted + } + r += c.Width + } + s.l = append(s.l, Sample{sample.Value, sample.Width, 0}) + i++ + inserted: + s.n += sample.Width + r += sample.Width + } + s.compress() +} + +func (s *stream) count() int { + return int(s.n) +} + +func (s *stream) query(q float64) float64 { + t := math.Ceil(q * s.n) + t += math.Ceil(s.ƒ(s, t) / 2) + p := s.l[0] + var r float64 + for _, c := range s.l[1:] { + r += p.Width + if r+c.Width+c.Delta > t { + return p.Value + } + p = c + } + return p.Value +} + +func (s *stream) compress() { + if len(s.l) < 2 { + return + } + x := s.l[len(s.l)-1] + xi := len(s.l) - 1 + r := s.n - 1 - x.Width + + for i := len(s.l) - 2; i >= 0; i-- { + c := s.l[i] + if c.Width+x.Width+x.Delta <= s.ƒ(s, r) { + x.Width += c.Width + s.l[xi] = x + // Remove element at i. + copy(s.l[i:], s.l[i+1:]) + s.l = s.l[:len(s.l)-1] + xi -= 1 + } else { + x = c + xi = i + } + r -= c.Width + } +} + +func (s *stream) samples() Samples { + samples := make(Samples, len(s.l)) + copy(samples, s.l) + return samples +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/quantile/stream_test.go charm-2.2.0/src/github.com/beorn7/perks/quantile/stream_test.go --- charm-2.1.1/src/github.com/beorn7/perks/quantile/stream_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/quantile/stream_test.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,188 @@ +package quantile + +import ( + "math" + "math/rand" + "sort" + "testing" +) + +var ( + Targets = map[float64]float64{ + 0.01: 0.001, + 0.10: 0.01, + 0.50: 0.05, + 0.90: 0.01, + 0.99: 0.001, + } + TargetsSmallEpsilon = map[float64]float64{ + 0.01: 0.0001, + 0.10: 0.001, + 0.50: 0.005, + 0.90: 0.001, + 0.99: 0.0001, + } + LowQuantiles = []float64{0.01, 0.1, 0.5} + HighQuantiles = []float64{0.99, 0.9, 0.5} +) + +const RelativeEpsilon = 0.01 + +func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for quantile, epsilon := range Targets { + n := float64(len(a)) + k := int(quantile * n) + lower := int((quantile - epsilon) * n) + if lower < 1 { + lower = 1 + } + upper := int(math.Ceil((quantile + epsilon) * n)) + if upper > len(a) { + upper = len(a) + } + w, min, max := a[k-1], a[lower-1], a[upper-1] + if g := s.Query(quantile); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g) + } + } +} + +func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for _, qu := range LowQuantiles { + n := float64(len(a)) + k := int(qu * n) + + lowerRank := int((1 - RelativeEpsilon) * qu * n) + upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n)) + w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] + if g := s.Query(qu); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) + } + } +} + +func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for _, qu := range HighQuantiles { + n := float64(len(a)) + k := int(qu * n) + + lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n) + upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n)) + w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] + if g := s.Query(qu); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) + } + } +} + +func populateStream(s *Stream) []float64 { + a := make([]float64, 0, 1e5+100) + for i := 0; i < cap(a); i++ { + v := rand.NormFloat64() + // Add 5% asymmetric outliers. + if i%20 == 0 { + v = v*v + 1 + } + s.Insert(v) + a = append(a, v) + } + return a +} + +func TestTargetedQuery(t *testing.T) { + rand.Seed(42) + s := NewTargeted(Targets) + a := populateStream(s) + verifyPercsWithAbsoluteEpsilon(t, a, s) +} + +func TestLowBiasedQuery(t *testing.T) { + rand.Seed(42) + s := NewLowBiased(RelativeEpsilon) + a := populateStream(s) + verifyLowPercsWithRelativeEpsilon(t, a, s) +} + +func TestHighBiasedQuery(t *testing.T) { + rand.Seed(42) + s := NewHighBiased(RelativeEpsilon) + a := populateStream(s) + verifyHighPercsWithRelativeEpsilon(t, a, s) +} + +// BrokenTestTargetedMerge is broken, see Merge doc comment. +func BrokenTestTargetedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewTargeted(Targets) + s2 := NewTargeted(Targets) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyPercsWithAbsoluteEpsilon(t, a, s1) +} + +// BrokenTestLowBiasedMerge is broken, see Merge doc comment. +func BrokenTestLowBiasedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewLowBiased(RelativeEpsilon) + s2 := NewLowBiased(RelativeEpsilon) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyLowPercsWithRelativeEpsilon(t, a, s2) +} + +// BrokenTestHighBiasedMerge is broken, see Merge doc comment. +func BrokenTestHighBiasedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewHighBiased(RelativeEpsilon) + s2 := NewHighBiased(RelativeEpsilon) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyHighPercsWithRelativeEpsilon(t, a, s2) +} + +func TestUncompressed(t *testing.T) { + q := NewTargeted(Targets) + for i := 100; i > 0; i-- { + q.Insert(float64(i)) + } + if g := q.Count(); g != 100 { + t.Errorf("want count 100, got %d", g) + } + // Before compression, Query should have 100% accuracy. + for quantile := range Targets { + w := quantile * 100 + if g := q.Query(quantile); g != w { + t.Errorf("want %f, got %f", w, g) + } + } +} + +func TestUncompressedSamples(t *testing.T) { + q := NewTargeted(map[float64]float64{0.99: 0.001}) + for i := 1; i <= 100; i++ { + q.Insert(float64(i)) + } + if g := q.Samples().Len(); g != 100 { + t.Errorf("want count 100, got %d", g) + } +} + +func TestUncompressedOne(t *testing.T) { + q := NewTargeted(map[float64]float64{0.99: 0.01}) + q.Insert(3.14) + if g := q.Query(0.90); g != 3.14 { + t.Error("want PI, got", g) + } +} + +func TestDefaults(t *testing.T) { + if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 { + t.Errorf("want 0, got %f", g) + } +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/README.md charm-2.2.0/src/github.com/beorn7/perks/README.md --- charm-2.1.1/src/github.com/beorn7/perks/README.md 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/README.md 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,31 @@ +# Perks for Go (golang.org) + +Perks contains the Go package quantile that computes approximate quantiles over +an unbounded data stream within low memory and CPU bounds. + +For more information and examples, see: +http://godoc.org/github.com/bmizerany/perks + +A very special thank you and shout out to Graham Cormode (Rutgers University), +Flip Korn (AT&T Labs–Research), S. Muthukrishnan (Rutgers University), and +Divesh Srivastava (AT&T Labs–Research) for their research and publication of +[Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf) + +Thank you, also: +* Armon Dadgar (@armon) +* Andrew Gerrand (@nf) +* Brad Fitzpatrick (@bradfitz) +* Keith Rarick (@kr) + +FAQ: + +Q: Why not move the quantile package into the project root? +A: I want to add more packages to perks later. + +Copyright (C) 2013 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -Nru charm-2.1.1/src/github.com/beorn7/perks/topk/topk.go charm-2.2.0/src/github.com/beorn7/perks/topk/topk.go --- charm-2.1.1/src/github.com/beorn7/perks/topk/topk.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/topk/topk.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,90 @@ +package topk + +import ( + "sort" +) + +// http://www.cs.ucsb.edu/research/tech_reports/reports/2005-23.pdf + +type Element struct { + Value string + Count int +} + +type Samples []*Element + +func (sm Samples) Len() int { + return len(sm) +} + +func (sm Samples) Less(i, j int) bool { + return sm[i].Count < sm[j].Count +} + +func (sm Samples) Swap(i, j int) { + sm[i], sm[j] = sm[j], sm[i] +} + +type Stream struct { + k int + mon map[string]*Element + + // the minimum Element + min *Element +} + +func New(k int) *Stream { + s := new(Stream) + s.k = k + s.mon = make(map[string]*Element) + s.min = &Element{} + + // Track k+1 so that less frequenet items contended for that spot, + // resulting in k being more accurate. + return s +} + +func (s *Stream) Insert(x string) { + s.insert(&Element{x, 1}) +} + +func (s *Stream) Merge(sm Samples) { + for _, e := range sm { + s.insert(e) + } +} + +func (s *Stream) insert(in *Element) { + e := s.mon[in.Value] + if e != nil { + e.Count++ + } else { + if len(s.mon) < s.k+1 { + e = &Element{in.Value, in.Count} + s.mon[in.Value] = e + } else { + e = s.min + delete(s.mon, e.Value) + e.Value = in.Value + e.Count += in.Count + s.min = e + } + } + if e.Count < s.min.Count { + s.min = e + } +} + +func (s *Stream) Query() Samples { + var sm Samples + for _, e := range s.mon { + sm = append(sm, e) + } + sort.Sort(sort.Reverse(sm)) + + if len(sm) < s.k { + return sm + } + + return sm[:s.k] +} diff -Nru charm-2.1.1/src/github.com/beorn7/perks/topk/topk_test.go charm-2.2.0/src/github.com/beorn7/perks/topk/topk_test.go --- charm-2.1.1/src/github.com/beorn7/perks/topk/topk_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/beorn7/perks/topk/topk_test.go 2016-09-03 01:25:00.000000000 +0000 @@ -0,0 +1,57 @@ +package topk + +import ( + "fmt" + "math/rand" + "sort" + "testing" +) + +func TestTopK(t *testing.T) { + stream := New(10) + ss := []*Stream{New(10), New(10), New(10)} + m := make(map[string]int) + for _, s := range ss { + for i := 0; i < 1e6; i++ { + v := fmt.Sprintf("%x", int8(rand.ExpFloat64())) + s.Insert(v) + m[v]++ + } + stream.Merge(s.Query()) + } + + var sm Samples + for x, s := range m { + sm = append(sm, &Element{x, s}) + } + sort.Sort(sort.Reverse(sm)) + + g := stream.Query() + if len(g) != 10 { + t.Fatalf("got %d, want 10", len(g)) + } + for i, e := range g { + if sm[i].Value != e.Value { + t.Errorf("at %d: want %q, got %q", i, sm[i].Value, e.Value) + } + } +} + +func TestQuery(t *testing.T) { + queryTests := []struct { + value string + expected int + }{ + {"a", 1}, + {"b", 2}, + {"c", 2}, + } + + stream := New(2) + for _, tt := range queryTests { + stream.Insert(tt.value) + if n := len(stream.Query()); n != tt.expected { + t.Errorf("want %d, got %d", tt.expected, n) + } + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/AUTHORS charm-2.2.0/src/github.com/golang/protobuf/AUTHORS --- charm-2.1.1/src/github.com/golang/protobuf/AUTHORS 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/AUTHORS 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff -Nru charm-2.1.1/src/github.com/golang/protobuf/CONTRIBUTORS charm-2.2.0/src/github.com/golang/protobuf/CONTRIBUTORS --- charm-2.1.1/src/github.com/golang/protobuf/CONTRIBUTORS 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/CONTRIBUTORS 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff -Nru charm-2.1.1/src/github.com/golang/protobuf/LICENSE charm-2.2.0/src/github.com/golang/protobuf/LICENSE --- charm-2.1.1/src/github.com/golang/protobuf/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/LICENSE 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,31 @@ +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff -Nru charm-2.1.1/src/github.com/golang/protobuf/Makefile charm-2.2.0/src/github.com/golang/protobuf/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,52 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +all: install + +install: + go install ./proto + go install ./protoc-gen-go + +test: + go test ./proto + make -C protoc-gen-go/testdata test + +clean: + go clean ./... + +nuke: + go clean -i ./... + +regenerate: + make -C protoc-gen-go/descriptor regenerate + make -C protoc-gen-go/plugin regenerate + make -C proto/testdata regenerate diff -Nru charm-2.1.1/src/github.com/golang/protobuf/Make.protobuf charm-2.2.0/src/github.com/golang/protobuf/Make.protobuf --- charm-2.1.1/src/github.com/golang/protobuf/Make.protobuf 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/Make.protobuf 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,40 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Includable Makefile to add a rule for generating .pb.go files from .proto files +# (Google protocol buffer descriptions). +# Typical use if myproto.proto is a file in package mypackage in this directory: +# +# include $(GOROOT)/src/pkg/github.com/golang/protobuf/Make.protobuf + +%.pb.go: %.proto + protoc --go_out=. $< + diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/all_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/all_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/all_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/all_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,2083 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math" + "math/rand" + "reflect" + "runtime/debug" + "strings" + "testing" + "time" + + . "github.com/golang/protobuf/proto" + . "github.com/golang/protobuf/proto/testdata" +) + +var globalO *Buffer + +func old() *Buffer { + if globalO == nil { + globalO = NewBuffer(nil) + } + globalO.Reset() + return globalO +} + +func equalbytes(b1, b2 []byte, t *testing.T) { + if len(b1) != len(b2) { + t.Errorf("wrong lengths: 2*%d != %d", len(b1), len(b2)) + return + } + for i := 0; i < len(b1); i++ { + if b1[i] != b2[i] { + t.Errorf("bad byte[%d]:%x %x: %s %s", i, b1[i], b2[i], b1, b2) + } + } +} + +func initGoTestField() *GoTestField { + f := new(GoTestField) + f.Label = String("label") + f.Type = String("type") + return f +} + +// These are all structurally equivalent but the tag numbers differ. +// (It's remarkable that required, optional, and repeated all have +// 8 letters.) +func initGoTest_RequiredGroup() *GoTest_RequiredGroup { + return &GoTest_RequiredGroup{ + RequiredField: String("required"), + } +} + +func initGoTest_OptionalGroup() *GoTest_OptionalGroup { + return &GoTest_OptionalGroup{ + RequiredField: String("optional"), + } +} + +func initGoTest_RepeatedGroup() *GoTest_RepeatedGroup { + return &GoTest_RepeatedGroup{ + RequiredField: String("repeated"), + } +} + +func initGoTest(setdefaults bool) *GoTest { + pb := new(GoTest) + if setdefaults { + pb.F_BoolDefaulted = Bool(Default_GoTest_F_BoolDefaulted) + pb.F_Int32Defaulted = Int32(Default_GoTest_F_Int32Defaulted) + pb.F_Int64Defaulted = Int64(Default_GoTest_F_Int64Defaulted) + pb.F_Fixed32Defaulted = Uint32(Default_GoTest_F_Fixed32Defaulted) + pb.F_Fixed64Defaulted = Uint64(Default_GoTest_F_Fixed64Defaulted) + pb.F_Uint32Defaulted = Uint32(Default_GoTest_F_Uint32Defaulted) + pb.F_Uint64Defaulted = Uint64(Default_GoTest_F_Uint64Defaulted) + pb.F_FloatDefaulted = Float32(Default_GoTest_F_FloatDefaulted) + pb.F_DoubleDefaulted = Float64(Default_GoTest_F_DoubleDefaulted) + pb.F_StringDefaulted = String(Default_GoTest_F_StringDefaulted) + pb.F_BytesDefaulted = Default_GoTest_F_BytesDefaulted + pb.F_Sint32Defaulted = Int32(Default_GoTest_F_Sint32Defaulted) + pb.F_Sint64Defaulted = Int64(Default_GoTest_F_Sint64Defaulted) + } + + pb.Kind = GoTest_TIME.Enum() + pb.RequiredField = initGoTestField() + pb.F_BoolRequired = Bool(true) + pb.F_Int32Required = Int32(3) + pb.F_Int64Required = Int64(6) + pb.F_Fixed32Required = Uint32(32) + pb.F_Fixed64Required = Uint64(64) + pb.F_Uint32Required = Uint32(3232) + pb.F_Uint64Required = Uint64(6464) + pb.F_FloatRequired = Float32(3232) + pb.F_DoubleRequired = Float64(6464) + pb.F_StringRequired = String("string") + pb.F_BytesRequired = []byte("bytes") + pb.F_Sint32Required = Int32(-32) + pb.F_Sint64Required = Int64(-64) + pb.Requiredgroup = initGoTest_RequiredGroup() + + return pb +} + +func fail(msg string, b *bytes.Buffer, s string, t *testing.T) { + data := b.Bytes() + ld := len(data) + ls := len(s) / 2 + + fmt.Printf("fail %s ld=%d ls=%d\n", msg, ld, ls) + + // find the interesting spot - n + n := ls + if ld < ls { + n = ld + } + j := 0 + for i := 0; i < n; i++ { + bs := hex(s[j])*16 + hex(s[j+1]) + j += 2 + if data[i] == bs { + continue + } + n = i + break + } + l := n - 10 + if l < 0 { + l = 0 + } + h := n + 10 + + // find the interesting spot - n + fmt.Printf("is[%d]:", l) + for i := l; i < h; i++ { + if i >= ld { + fmt.Printf(" --") + continue + } + fmt.Printf(" %.2x", data[i]) + } + fmt.Printf("\n") + + fmt.Printf("sb[%d]:", l) + for i := l; i < h; i++ { + if i >= ls { + fmt.Printf(" --") + continue + } + bs := hex(s[j])*16 + hex(s[j+1]) + j += 2 + fmt.Printf(" %.2x", bs) + } + fmt.Printf("\n") + + t.Fail() + + // t.Errorf("%s: \ngood: %s\nbad: %x", msg, s, b.Bytes()) + // Print the output in a partially-decoded format; can + // be helpful when updating the test. It produces the output + // that is pasted, with minor edits, into the argument to verify(). + // data := b.Bytes() + // nesting := 0 + // for b.Len() > 0 { + // start := len(data) - b.Len() + // var u uint64 + // u, err := DecodeVarint(b) + // if err != nil { + // fmt.Printf("decode error on varint:", err) + // return + // } + // wire := u & 0x7 + // tag := u >> 3 + // switch wire { + // case WireVarint: + // v, err := DecodeVarint(b) + // if err != nil { + // fmt.Printf("decode error on varint:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" // field %d, encoding %d, value %d\n", + // data[start:len(data)-b.Len()], tag, wire, v) + // case WireFixed32: + // v, err := DecodeFixed32(b) + // if err != nil { + // fmt.Printf("decode error on fixed32:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" // field %d, encoding %d, value %d\n", + // data[start:len(data)-b.Len()], tag, wire, v) + // case WireFixed64: + // v, err := DecodeFixed64(b) + // if err != nil { + // fmt.Printf("decode error on fixed64:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" // field %d, encoding %d, value %d\n", + // data[start:len(data)-b.Len()], tag, wire, v) + // case WireBytes: + // nb, err := DecodeVarint(b) + // if err != nil { + // fmt.Printf("decode error on bytes:", err) + // return + // } + // after_tag := len(data) - b.Len() + // str := make([]byte, nb) + // _, err = b.Read(str) + // if err != nil { + // fmt.Printf("decode error on bytes:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" \"%x\" // field %d, encoding %d (FIELD)\n", + // data[start:after_tag], str, tag, wire) + // case WireStartGroup: + // nesting++ + // fmt.Printf("\t\t\"%x\"\t\t// start group field %d level %d\n", + // data[start:len(data)-b.Len()], tag, nesting) + // case WireEndGroup: + // fmt.Printf("\t\t\"%x\"\t\t// end group field %d level %d\n", + // data[start:len(data)-b.Len()], tag, nesting) + // nesting-- + // default: + // fmt.Printf("unrecognized wire type %d\n", wire) + // return + // } + // } +} + +func hex(c uint8) uint8 { + if '0' <= c && c <= '9' { + return c - '0' + } + if 'a' <= c && c <= 'f' { + return 10 + c - 'a' + } + if 'A' <= c && c <= 'F' { + return 10 + c - 'A' + } + return 0 +} + +func equal(b []byte, s string, t *testing.T) bool { + if 2*len(b) != len(s) { + // fail(fmt.Sprintf("wrong lengths: 2*%d != %d", len(b), len(s)), b, s, t) + fmt.Printf("wrong lengths: 2*%d != %d\n", len(b), len(s)) + return false + } + for i, j := 0, 0; i < len(b); i, j = i+1, j+2 { + x := hex(s[j])*16 + hex(s[j+1]) + if b[i] != x { + // fail(fmt.Sprintf("bad byte[%d]:%x %x", i, b[i], x), b, s, t) + fmt.Printf("bad byte[%d]:%x %x", i, b[i], x) + return false + } + } + return true +} + +func overify(t *testing.T, pb *GoTest, expected string) { + o := old() + err := o.Marshal(pb) + if err != nil { + fmt.Printf("overify marshal-1 err = %v", err) + o.DebugPrint("", o.Bytes()) + t.Fatalf("expected = %s", expected) + } + if !equal(o.Bytes(), expected, t) { + o.DebugPrint("overify neq 1", o.Bytes()) + t.Fatalf("expected = %s", expected) + } + + // Now test Unmarshal by recreating the original buffer. + pbd := new(GoTest) + err = o.Unmarshal(pbd) + if err != nil { + t.Fatalf("overify unmarshal err = %v", err) + o.DebugPrint("", o.Bytes()) + t.Fatalf("string = %s", expected) + } + o.Reset() + err = o.Marshal(pbd) + if err != nil { + t.Errorf("overify marshal-2 err = %v", err) + o.DebugPrint("", o.Bytes()) + t.Fatalf("string = %s", expected) + } + if !equal(o.Bytes(), expected, t) { + o.DebugPrint("overify neq 2", o.Bytes()) + t.Fatalf("string = %s", expected) + } +} + +// Simple tests for numeric encode/decode primitives (varint, etc.) +func TestNumericPrimitives(t *testing.T) { + for i := uint64(0); i < 1e6; i += 111 { + o := old() + if o.EncodeVarint(i) != nil { + t.Error("EncodeVarint") + break + } + x, e := o.DecodeVarint() + if e != nil { + t.Fatal("DecodeVarint") + } + if x != i { + t.Fatal("varint decode fail:", i, x) + } + + o = old() + if o.EncodeFixed32(i) != nil { + t.Fatal("encFixed32") + } + x, e = o.DecodeFixed32() + if e != nil { + t.Fatal("decFixed32") + } + if x != i { + t.Fatal("fixed32 decode fail:", i, x) + } + + o = old() + if o.EncodeFixed64(i*1234567) != nil { + t.Error("encFixed64") + break + } + x, e = o.DecodeFixed64() + if e != nil { + t.Error("decFixed64") + break + } + if x != i*1234567 { + t.Error("fixed64 decode fail:", i*1234567, x) + break + } + + o = old() + i32 := int32(i - 12345) + if o.EncodeZigzag32(uint64(i32)) != nil { + t.Fatal("EncodeZigzag32") + } + x, e = o.DecodeZigzag32() + if e != nil { + t.Fatal("DecodeZigzag32") + } + if x != uint64(uint32(i32)) { + t.Fatal("zigzag32 decode fail:", i32, x) + } + + o = old() + i64 := int64(i - 12345) + if o.EncodeZigzag64(uint64(i64)) != nil { + t.Fatal("EncodeZigzag64") + } + x, e = o.DecodeZigzag64() + if e != nil { + t.Fatal("DecodeZigzag64") + } + if x != uint64(i64) { + t.Fatal("zigzag64 decode fail:", i64, x) + } + } +} + +// fakeMarshaler is a simple struct implementing Marshaler and Message interfaces. +type fakeMarshaler struct { + b []byte + err error +} + +func (f fakeMarshaler) Marshal() ([]byte, error) { + return f.b, f.err +} + +func (f fakeMarshaler) String() string { + return fmt.Sprintf("Bytes: %v Error: %v", f.b, f.err) +} + +func (f fakeMarshaler) ProtoMessage() {} + +func (f fakeMarshaler) Reset() {} + +// Simple tests for proto messages that implement the Marshaler interface. +func TestMarshalerEncoding(t *testing.T) { + tests := []struct { + name string + m Message + want []byte + wantErr error + }{ + { + name: "Marshaler that fails", + m: fakeMarshaler{ + err: errors.New("some marshal err"), + b: []byte{5, 6, 7}, + }, + // Since there's an error, nothing should be written to buffer. + want: nil, + wantErr: errors.New("some marshal err"), + }, + { + name: "Marshaler that succeeds", + m: fakeMarshaler{ + b: []byte{0, 1, 2, 3, 4, 127, 255}, + }, + want: []byte{0, 1, 2, 3, 4, 127, 255}, + wantErr: nil, + }, + } + for _, test := range tests { + b := NewBuffer(nil) + err := b.Marshal(test.m) + if !reflect.DeepEqual(test.wantErr, err) { + t.Errorf("%s: got err %v wanted %v", test.name, err, test.wantErr) + } + if !reflect.DeepEqual(test.want, b.Bytes()) { + t.Errorf("%s: got bytes %v wanted %v", test.name, b.Bytes(), test.want) + } + } +} + +// Simple tests for bytes +func TestBytesPrimitives(t *testing.T) { + o := old() + bytes := []byte{'n', 'o', 'w', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 't', 'i', 'm', 'e'} + if o.EncodeRawBytes(bytes) != nil { + t.Error("EncodeRawBytes") + } + decb, e := o.DecodeRawBytes(false) + if e != nil { + t.Error("DecodeRawBytes") + } + equalbytes(bytes, decb, t) +} + +// Simple tests for strings +func TestStringPrimitives(t *testing.T) { + o := old() + s := "now is the time" + if o.EncodeStringBytes(s) != nil { + t.Error("enc_string") + } + decs, e := o.DecodeStringBytes() + if e != nil { + t.Error("dec_string") + } + if s != decs { + t.Error("string encode/decode fail:", s, decs) + } +} + +// Do we catch the "required bit not set" case? +func TestRequiredBit(t *testing.T) { + o := old() + pb := new(GoTest) + err := o.Marshal(pb) + if err == nil { + t.Error("did not catch missing required fields") + } else if strings.Index(err.Error(), "Kind") < 0 { + t.Error("wrong error type:", err) + } +} + +// Check that all fields are nil. +// Clearly silly, and a residue from a more interesting test with an earlier, +// different initialization property, but it once caught a compiler bug so +// it lives. +func checkInitialized(pb *GoTest, t *testing.T) { + if pb.F_BoolDefaulted != nil { + t.Error("New or Reset did not set boolean:", *pb.F_BoolDefaulted) + } + if pb.F_Int32Defaulted != nil { + t.Error("New or Reset did not set int32:", *pb.F_Int32Defaulted) + } + if pb.F_Int64Defaulted != nil { + t.Error("New or Reset did not set int64:", *pb.F_Int64Defaulted) + } + if pb.F_Fixed32Defaulted != nil { + t.Error("New or Reset did not set fixed32:", *pb.F_Fixed32Defaulted) + } + if pb.F_Fixed64Defaulted != nil { + t.Error("New or Reset did not set fixed64:", *pb.F_Fixed64Defaulted) + } + if pb.F_Uint32Defaulted != nil { + t.Error("New or Reset did not set uint32:", *pb.F_Uint32Defaulted) + } + if pb.F_Uint64Defaulted != nil { + t.Error("New or Reset did not set uint64:", *pb.F_Uint64Defaulted) + } + if pb.F_FloatDefaulted != nil { + t.Error("New or Reset did not set float:", *pb.F_FloatDefaulted) + } + if pb.F_DoubleDefaulted != nil { + t.Error("New or Reset did not set double:", *pb.F_DoubleDefaulted) + } + if pb.F_StringDefaulted != nil { + t.Error("New or Reset did not set string:", *pb.F_StringDefaulted) + } + if pb.F_BytesDefaulted != nil { + t.Error("New or Reset did not set bytes:", string(pb.F_BytesDefaulted)) + } + if pb.F_Sint32Defaulted != nil { + t.Error("New or Reset did not set int32:", *pb.F_Sint32Defaulted) + } + if pb.F_Sint64Defaulted != nil { + t.Error("New or Reset did not set int64:", *pb.F_Sint64Defaulted) + } +} + +// Does Reset() reset? +func TestReset(t *testing.T) { + pb := initGoTest(true) + // muck with some values + pb.F_BoolDefaulted = Bool(false) + pb.F_Int32Defaulted = Int32(237) + pb.F_Int64Defaulted = Int64(12346) + pb.F_Fixed32Defaulted = Uint32(32000) + pb.F_Fixed64Defaulted = Uint64(666) + pb.F_Uint32Defaulted = Uint32(323232) + pb.F_Uint64Defaulted = nil + pb.F_FloatDefaulted = nil + pb.F_DoubleDefaulted = Float64(0) + pb.F_StringDefaulted = String("gotcha") + pb.F_BytesDefaulted = []byte("asdfasdf") + pb.F_Sint32Defaulted = Int32(123) + pb.F_Sint64Defaulted = Int64(789) + pb.Reset() + checkInitialized(pb, t) +} + +// All required fields set, no defaults provided. +func TestEncodeDecode1(t *testing.T) { + pb := initGoTest(false) + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 0x20 + "714000000000000000"+ // field 14, encoding 1, value 0x40 + "78a019"+ // field 15, encoding 0, value 0xca0 = 3232 + "8001c032"+ // field 16, encoding 0, value 0x1940 = 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2, string "string" + "b304"+ // field 70, encoding 3, start group + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // field 70, encoding 4, end group + "aa0605"+"6279746573"+ // field 101, encoding 2, string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f") // field 103, encoding 0, 0x7f zigzag64 +} + +// All required fields set, defaults provided. +func TestEncodeDecode2(t *testing.T) { + pb := initGoTest(true) + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All default fields set to their default value by hand +func TestEncodeDecode3(t *testing.T) { + pb := initGoTest(false) + pb.F_BoolDefaulted = Bool(true) + pb.F_Int32Defaulted = Int32(32) + pb.F_Int64Defaulted = Int64(64) + pb.F_Fixed32Defaulted = Uint32(320) + pb.F_Fixed64Defaulted = Uint64(640) + pb.F_Uint32Defaulted = Uint32(3200) + pb.F_Uint64Defaulted = Uint64(6400) + pb.F_FloatDefaulted = Float32(314159) + pb.F_DoubleDefaulted = Float64(271828) + pb.F_StringDefaulted = String("hello, \"world!\"\n") + pb.F_BytesDefaulted = []byte("Bignose") + pb.F_Sint32Defaulted = Int32(-32) + pb.F_Sint64Defaulted = Int64(-64) + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All required fields set, defaults provided, all non-defaulted optional fields have values. +func TestEncodeDecode4(t *testing.T) { + pb := initGoTest(true) + pb.Table = String("hello") + pb.Param = Int32(7) + pb.OptionalField = initGoTestField() + pb.F_BoolOptional = Bool(true) + pb.F_Int32Optional = Int32(32) + pb.F_Int64Optional = Int64(64) + pb.F_Fixed32Optional = Uint32(3232) + pb.F_Fixed64Optional = Uint64(6464) + pb.F_Uint32Optional = Uint32(323232) + pb.F_Uint64Optional = Uint64(646464) + pb.F_FloatOptional = Float32(32.) + pb.F_DoubleOptional = Float64(64.) + pb.F_StringOptional = String("hello") + pb.F_BytesOptional = []byte("Bignose") + pb.F_Sint32Optional = Int32(-32) + pb.F_Sint64Optional = Int64(-64) + pb.Optionalgroup = initGoTest_OptionalGroup() + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "1205"+"68656c6c6f"+ // field 2, encoding 2, string "hello" + "1807"+ // field 3, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "320d"+"0a056c6162656c120474797065"+ // field 6, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "f00101"+ // field 30, encoding 0, value 1 + "f80120"+ // field 31, encoding 0, value 32 + "800240"+ // field 32, encoding 0, value 64 + "8d02a00c0000"+ // field 33, encoding 5, value 3232 + "91024019000000000000"+ // field 34, encoding 1, value 6464 + "9802a0dd13"+ // field 35, encoding 0, value 323232 + "a002c0ba27"+ // field 36, encoding 0, value 646464 + "ad0200000042"+ // field 37, encoding 5, value 32.0 + "b1020000000000005040"+ // field 38, encoding 1, value 64.0 + "ba0205"+"68656c6c6f"+ // field 39, encoding 2, string "hello" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "d305"+ // start group field 90 level 1 + "da0508"+"6f7074696f6e616c"+ // field 91, encoding 2, string "optional" + "d405"+ // end group field 90 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "ea1207"+"4269676e6f7365"+ // field 301, encoding 2, string "Bignose" + "f0123f"+ // field 302, encoding 0, value 63 + "f8127f"+ // field 303, encoding 0, value 127 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All required fields set, defaults provided, all repeated fields given two values. +func TestEncodeDecode5(t *testing.T) { + pb := initGoTest(true) + pb.RepeatedField = []*GoTestField{initGoTestField(), initGoTestField()} + pb.F_BoolRepeated = []bool{false, true} + pb.F_Int32Repeated = []int32{32, 33} + pb.F_Int64Repeated = []int64{64, 65} + pb.F_Fixed32Repeated = []uint32{3232, 3333} + pb.F_Fixed64Repeated = []uint64{6464, 6565} + pb.F_Uint32Repeated = []uint32{323232, 333333} + pb.F_Uint64Repeated = []uint64{646464, 656565} + pb.F_FloatRepeated = []float32{32., 33.} + pb.F_DoubleRepeated = []float64{64., 65.} + pb.F_StringRepeated = []string{"hello", "sailor"} + pb.F_BytesRepeated = [][]byte{[]byte("big"), []byte("nose")} + pb.F_Sint32Repeated = []int32{32, -32} + pb.F_Sint64Repeated = []int64{64, -64} + pb.Repeatedgroup = []*GoTest_RepeatedGroup{initGoTest_RepeatedGroup(), initGoTest_RepeatedGroup()} + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "2a0d"+"0a056c6162656c120474797065"+ // field 5, encoding 2 (GoTestField) + "2a0d"+"0a056c6162656c120474797065"+ // field 5, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "a00100"+ // field 20, encoding 0, value 0 + "a00101"+ // field 20, encoding 0, value 1 + "a80120"+ // field 21, encoding 0, value 32 + "a80121"+ // field 21, encoding 0, value 33 + "b00140"+ // field 22, encoding 0, value 64 + "b00141"+ // field 22, encoding 0, value 65 + "bd01a00c0000"+ // field 23, encoding 5, value 3232 + "bd01050d0000"+ // field 23, encoding 5, value 3333 + "c1014019000000000000"+ // field 24, encoding 1, value 6464 + "c101a519000000000000"+ // field 24, encoding 1, value 6565 + "c801a0dd13"+ // field 25, encoding 0, value 323232 + "c80195ac14"+ // field 25, encoding 0, value 333333 + "d001c0ba27"+ // field 26, encoding 0, value 646464 + "d001b58928"+ // field 26, encoding 0, value 656565 + "dd0100000042"+ // field 27, encoding 5, value 32.0 + "dd0100000442"+ // field 27, encoding 5, value 33.0 + "e1010000000000005040"+ // field 28, encoding 1, value 64.0 + "e1010000000000405040"+ // field 28, encoding 1, value 65.0 + "ea0105"+"68656c6c6f"+ // field 29, encoding 2, string "hello" + "ea0106"+"7361696c6f72"+ // field 29, encoding 2, string "sailor" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "8305"+ // start group field 80 level 1 + "8a0508"+"7265706561746564"+ // field 81, encoding 2, string "repeated" + "8405"+ // end group field 80 level 1 + "8305"+ // start group field 80 level 1 + "8a0508"+"7265706561746564"+ // field 81, encoding 2, string "repeated" + "8405"+ // end group field 80 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "ca0c03"+"626967"+ // field 201, encoding 2, string "big" + "ca0c04"+"6e6f7365"+ // field 201, encoding 2, string "nose" + "d00c40"+ // field 202, encoding 0, value 32 + "d00c3f"+ // field 202, encoding 0, value -32 + "d80c8001"+ // field 203, encoding 0, value 64 + "d80c7f"+ // field 203, encoding 0, value -64 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All required fields set, all packed repeated fields given two values. +func TestEncodeDecode6(t *testing.T) { + pb := initGoTest(false) + pb.F_BoolRepeatedPacked = []bool{false, true} + pb.F_Int32RepeatedPacked = []int32{32, 33} + pb.F_Int64RepeatedPacked = []int64{64, 65} + pb.F_Fixed32RepeatedPacked = []uint32{3232, 3333} + pb.F_Fixed64RepeatedPacked = []uint64{6464, 6565} + pb.F_Uint32RepeatedPacked = []uint32{323232, 333333} + pb.F_Uint64RepeatedPacked = []uint64{646464, 656565} + pb.F_FloatRepeatedPacked = []float32{32., 33.} + pb.F_DoubleRepeatedPacked = []float64{64., 65.} + pb.F_Sint32RepeatedPacked = []int32{32, -32} + pb.F_Sint64RepeatedPacked = []int64{64, -64} + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "9203020001"+ // field 50, encoding 2, 2 bytes, value 0, value 1 + "9a03022021"+ // field 51, encoding 2, 2 bytes, value 32, value 33 + "a203024041"+ // field 52, encoding 2, 2 bytes, value 64, value 65 + "aa0308"+ // field 53, encoding 2, 8 bytes + "a00c0000050d0000"+ // value 3232, value 3333 + "b20310"+ // field 54, encoding 2, 16 bytes + "4019000000000000a519000000000000"+ // value 6464, value 6565 + "ba0306"+ // field 55, encoding 2, 6 bytes + "a0dd1395ac14"+ // value 323232, value 333333 + "c20306"+ // field 56, encoding 2, 6 bytes + "c0ba27b58928"+ // value 646464, value 656565 + "ca0308"+ // field 57, encoding 2, 8 bytes + "0000004200000442"+ // value 32.0, value 33.0 + "d20310"+ // field 58, encoding 2, 16 bytes + "00000000000050400000000000405040"+ // value 64.0, value 65.0 + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "b21f02"+ // field 502, encoding 2, 2 bytes + "403f"+ // value 32, value -32 + "ba1f03"+ // field 503, encoding 2, 3 bytes + "80017f") // value 64, value -64 +} + +// Test that we can encode empty bytes fields. +func TestEncodeDecodeBytes1(t *testing.T) { + pb := initGoTest(false) + + // Create our bytes + pb.F_BytesRequired = []byte{} + pb.F_BytesRepeated = [][]byte{{}} + pb.F_BytesOptional = []byte{} + + d, err := Marshal(pb) + if err != nil { + t.Error(err) + } + + pbd := new(GoTest) + if err := Unmarshal(d, pbd); err != nil { + t.Error(err) + } + + if pbd.F_BytesRequired == nil || len(pbd.F_BytesRequired) != 0 { + t.Error("required empty bytes field is incorrect") + } + if pbd.F_BytesRepeated == nil || len(pbd.F_BytesRepeated) == 1 && pbd.F_BytesRepeated[0] == nil { + t.Error("repeated empty bytes field is incorrect") + } + if pbd.F_BytesOptional == nil || len(pbd.F_BytesOptional) != 0 { + t.Error("optional empty bytes field is incorrect") + } +} + +// Test that we encode nil-valued fields of a repeated bytes field correctly. +// Since entries in a repeated field cannot be nil, nil must mean empty value. +func TestEncodeDecodeBytes2(t *testing.T) { + pb := initGoTest(false) + + // Create our bytes + pb.F_BytesRepeated = [][]byte{nil} + + d, err := Marshal(pb) + if err != nil { + t.Error(err) + } + + pbd := new(GoTest) + if err := Unmarshal(d, pbd); err != nil { + t.Error(err) + } + + if len(pbd.F_BytesRepeated) != 1 || pbd.F_BytesRepeated[0] == nil { + t.Error("Unexpected value for repeated bytes field") + } +} + +// All required fields set, defaults provided, all repeated fields given two values. +func TestSkippingUnrecognizedFields(t *testing.T) { + o := old() + pb := initGoTestField() + + // Marshal it normally. + o.Marshal(pb) + + // Now new a GoSkipTest record. + skip := &GoSkipTest{ + SkipInt32: Int32(32), + SkipFixed32: Uint32(3232), + SkipFixed64: Uint64(6464), + SkipString: String("skipper"), + Skipgroup: &GoSkipTest_SkipGroup{ + GroupInt32: Int32(75), + GroupString: String("wxyz"), + }, + } + + // Marshal it into same buffer. + o.Marshal(skip) + + pbd := new(GoTestField) + o.Unmarshal(pbd) + + // The __unrecognized field should be a marshaling of GoSkipTest + skipd := new(GoSkipTest) + + o.SetBuf(pbd.XXX_unrecognized) + o.Unmarshal(skipd) + + if *skipd.SkipInt32 != *skip.SkipInt32 { + t.Error("skip int32", skipd.SkipInt32) + } + if *skipd.SkipFixed32 != *skip.SkipFixed32 { + t.Error("skip fixed32", skipd.SkipFixed32) + } + if *skipd.SkipFixed64 != *skip.SkipFixed64 { + t.Error("skip fixed64", skipd.SkipFixed64) + } + if *skipd.SkipString != *skip.SkipString { + t.Error("skip string", *skipd.SkipString) + } + if *skipd.Skipgroup.GroupInt32 != *skip.Skipgroup.GroupInt32 { + t.Error("skip group int32", skipd.Skipgroup.GroupInt32) + } + if *skipd.Skipgroup.GroupString != *skip.Skipgroup.GroupString { + t.Error("skip group string", *skipd.Skipgroup.GroupString) + } +} + +// Check that unrecognized fields of a submessage are preserved. +func TestSubmessageUnrecognizedFields(t *testing.T) { + nm := &NewMessage{ + Nested: &NewMessage_Nested{ + Name: String("Nigel"), + FoodGroup: String("carbs"), + }, + } + b, err := Marshal(nm) + if err != nil { + t.Fatalf("Marshal of NewMessage: %v", err) + } + + // Unmarshal into an OldMessage. + om := new(OldMessage) + if err := Unmarshal(b, om); err != nil { + t.Fatalf("Unmarshal to OldMessage: %v", err) + } + exp := &OldMessage{ + Nested: &OldMessage_Nested{ + Name: String("Nigel"), + // normal protocol buffer users should not do this + XXX_unrecognized: []byte("\x12\x05carbs"), + }, + } + if !Equal(om, exp) { + t.Errorf("om = %v, want %v", om, exp) + } + + // Clone the OldMessage. + om = Clone(om).(*OldMessage) + if !Equal(om, exp) { + t.Errorf("Clone(om) = %v, want %v", om, exp) + } + + // Marshal the OldMessage, then unmarshal it into an empty NewMessage. + if b, err = Marshal(om); err != nil { + t.Fatalf("Marshal of OldMessage: %v", err) + } + t.Logf("Marshal(%v) -> %q", om, b) + nm2 := new(NewMessage) + if err := Unmarshal(b, nm2); err != nil { + t.Fatalf("Unmarshal to NewMessage: %v", err) + } + if !Equal(nm, nm2) { + t.Errorf("NewMessage round-trip: %v => %v", nm, nm2) + } +} + +// Check that an int32 field can be upgraded to an int64 field. +func TestNegativeInt32(t *testing.T) { + om := &OldMessage{ + Num: Int32(-1), + } + b, err := Marshal(om) + if err != nil { + t.Fatalf("Marshal of OldMessage: %v", err) + } + + // Check the size. It should be 11 bytes; + // 1 for the field/wire type, and 10 for the negative number. + if len(b) != 11 { + t.Errorf("%v marshaled as %q, wanted 11 bytes", om, b) + } + + // Unmarshal into a NewMessage. + nm := new(NewMessage) + if err := Unmarshal(b, nm); err != nil { + t.Fatalf("Unmarshal to NewMessage: %v", err) + } + want := &NewMessage{ + Num: Int64(-1), + } + if !Equal(nm, want) { + t.Errorf("nm = %v, want %v", nm, want) + } +} + +// Check that we can grow an array (repeated field) to have many elements. +// This test doesn't depend only on our encoding; for variety, it makes sure +// we create, encode, and decode the correct contents explicitly. It's therefore +// a bit messier. +// This test also uses (and hence tests) the Marshal/Unmarshal functions +// instead of the methods. +func TestBigRepeated(t *testing.T) { + pb := initGoTest(true) + + // Create the arrays + const N = 50 // Internally the library starts much smaller. + pb.Repeatedgroup = make([]*GoTest_RepeatedGroup, N) + pb.F_Sint64Repeated = make([]int64, N) + pb.F_Sint32Repeated = make([]int32, N) + pb.F_BytesRepeated = make([][]byte, N) + pb.F_StringRepeated = make([]string, N) + pb.F_DoubleRepeated = make([]float64, N) + pb.F_FloatRepeated = make([]float32, N) + pb.F_Uint64Repeated = make([]uint64, N) + pb.F_Uint32Repeated = make([]uint32, N) + pb.F_Fixed64Repeated = make([]uint64, N) + pb.F_Fixed32Repeated = make([]uint32, N) + pb.F_Int64Repeated = make([]int64, N) + pb.F_Int32Repeated = make([]int32, N) + pb.F_BoolRepeated = make([]bool, N) + pb.RepeatedField = make([]*GoTestField, N) + + // Fill in the arrays with checkable values. + igtf := initGoTestField() + igtrg := initGoTest_RepeatedGroup() + for i := 0; i < N; i++ { + pb.Repeatedgroup[i] = igtrg + pb.F_Sint64Repeated[i] = int64(i) + pb.F_Sint32Repeated[i] = int32(i) + s := fmt.Sprint(i) + pb.F_BytesRepeated[i] = []byte(s) + pb.F_StringRepeated[i] = s + pb.F_DoubleRepeated[i] = float64(i) + pb.F_FloatRepeated[i] = float32(i) + pb.F_Uint64Repeated[i] = uint64(i) + pb.F_Uint32Repeated[i] = uint32(i) + pb.F_Fixed64Repeated[i] = uint64(i) + pb.F_Fixed32Repeated[i] = uint32(i) + pb.F_Int64Repeated[i] = int64(i) + pb.F_Int32Repeated[i] = int32(i) + pb.F_BoolRepeated[i] = i%2 == 0 + pb.RepeatedField[i] = igtf + } + + // Marshal. + buf, _ := Marshal(pb) + + // Now test Unmarshal by recreating the original buffer. + pbd := new(GoTest) + Unmarshal(buf, pbd) + + // Check the checkable values + for i := uint64(0); i < N; i++ { + if pbd.Repeatedgroup[i] == nil { // TODO: more checking? + t.Error("pbd.Repeatedgroup bad") + } + var x uint64 + x = uint64(pbd.F_Sint64Repeated[i]) + if x != i { + t.Error("pbd.F_Sint64Repeated bad", x, i) + } + x = uint64(pbd.F_Sint32Repeated[i]) + if x != i { + t.Error("pbd.F_Sint32Repeated bad", x, i) + } + s := fmt.Sprint(i) + equalbytes(pbd.F_BytesRepeated[i], []byte(s), t) + if pbd.F_StringRepeated[i] != s { + t.Error("pbd.F_Sint32Repeated bad", pbd.F_StringRepeated[i], i) + } + x = uint64(pbd.F_DoubleRepeated[i]) + if x != i { + t.Error("pbd.F_DoubleRepeated bad", x, i) + } + x = uint64(pbd.F_FloatRepeated[i]) + if x != i { + t.Error("pbd.F_FloatRepeated bad", x, i) + } + x = pbd.F_Uint64Repeated[i] + if x != i { + t.Error("pbd.F_Uint64Repeated bad", x, i) + } + x = uint64(pbd.F_Uint32Repeated[i]) + if x != i { + t.Error("pbd.F_Uint32Repeated bad", x, i) + } + x = pbd.F_Fixed64Repeated[i] + if x != i { + t.Error("pbd.F_Fixed64Repeated bad", x, i) + } + x = uint64(pbd.F_Fixed32Repeated[i]) + if x != i { + t.Error("pbd.F_Fixed32Repeated bad", x, i) + } + x = uint64(pbd.F_Int64Repeated[i]) + if x != i { + t.Error("pbd.F_Int64Repeated bad", x, i) + } + x = uint64(pbd.F_Int32Repeated[i]) + if x != i { + t.Error("pbd.F_Int32Repeated bad", x, i) + } + if pbd.F_BoolRepeated[i] != (i%2 == 0) { + t.Error("pbd.F_BoolRepeated bad", x, i) + } + if pbd.RepeatedField[i] == nil { // TODO: more checking? + t.Error("pbd.RepeatedField bad") + } + } +} + +// Verify we give a useful message when decoding to the wrong structure type. +func TestTypeMismatch(t *testing.T) { + pb1 := initGoTest(true) + + // Marshal + o := old() + o.Marshal(pb1) + + // Now Unmarshal it to the wrong type. + pb2 := initGoTestField() + err := o.Unmarshal(pb2) + if err == nil { + t.Error("expected error, got no error") + } else if !strings.Contains(err.Error(), "bad wiretype") { + t.Error("expected bad wiretype error, got", err) + } +} + +func encodeDecode(t *testing.T, in, out Message, msg string) { + buf, err := Marshal(in) + if err != nil { + t.Fatalf("failed marshaling %v: %v", msg, err) + } + if err := Unmarshal(buf, out); err != nil { + t.Fatalf("failed unmarshaling %v: %v", msg, err) + } +} + +func TestPackedNonPackedDecoderSwitching(t *testing.T) { + np, p := new(NonPackedTest), new(PackedTest) + + // non-packed -> packed + np.A = []int32{0, 1, 1, 2, 3, 5} + encodeDecode(t, np, p, "non-packed -> packed") + if !reflect.DeepEqual(np.A, p.B) { + t.Errorf("failed non-packed -> packed; np.A=%+v, p.B=%+v", np.A, p.B) + } + + // packed -> non-packed + np.Reset() + p.B = []int32{3, 1, 4, 1, 5, 9} + encodeDecode(t, p, np, "packed -> non-packed") + if !reflect.DeepEqual(p.B, np.A) { + t.Errorf("failed packed -> non-packed; p.B=%+v, np.A=%+v", p.B, np.A) + } +} + +func TestProto1RepeatedGroup(t *testing.T) { + pb := &MessageList{ + Message: []*MessageList_Message{ + { + Name: String("blah"), + Count: Int32(7), + }, + // NOTE: pb.Message[1] is a nil + nil, + }, + } + + o := old() + err := o.Marshal(pb) + if err == nil || !strings.Contains(err.Error(), "repeated field Message has nil") { + t.Fatalf("unexpected or no error when marshaling: %v", err) + } +} + +// Test that enums work. Checks for a bug introduced by making enums +// named types instead of int32: newInt32FromUint64 would crash with +// a type mismatch in reflect.PointTo. +func TestEnum(t *testing.T) { + pb := new(GoEnum) + pb.Foo = FOO_FOO1.Enum() + o := old() + if err := o.Marshal(pb); err != nil { + t.Fatal("error encoding enum:", err) + } + pb1 := new(GoEnum) + if err := o.Unmarshal(pb1); err != nil { + t.Fatal("error decoding enum:", err) + } + if *pb1.Foo != FOO_FOO1 { + t.Error("expected 7 but got ", *pb1.Foo) + } +} + +// Enum types have String methods. Check that enum fields can be printed. +// We don't care what the value actually is, just as long as it doesn't crash. +func TestPrintingNilEnumFields(t *testing.T) { + pb := new(GoEnum) + fmt.Sprintf("%+v", pb) +} + +// Verify that absent required fields cause Marshal/Unmarshal to return errors. +func TestRequiredFieldEnforcement(t *testing.T) { + pb := new(GoTestField) + _, err := Marshal(pb) + if err == nil { + t.Error("marshal: expected error, got nil") + } else if strings.Index(err.Error(), "Label") < 0 { + t.Errorf("marshal: bad error type: %v", err) + } + + // A slightly sneaky, yet valid, proto. It encodes the same required field twice, + // so simply counting the required fields is insufficient. + // field 1, encoding 2, value "hi" + buf := []byte("\x0A\x02hi\x0A\x02hi") + err = Unmarshal(buf, pb) + if err == nil { + t.Error("unmarshal: expected error, got nil") + } else if strings.Index(err.Error(), "{Unknown}") < 0 { + t.Errorf("unmarshal: bad error type: %v", err) + } +} + +func TestTypedNilMarshal(t *testing.T) { + // A typed nil should return ErrNil and not crash. + _, err := Marshal((*GoEnum)(nil)) + if err != ErrNil { + t.Errorf("Marshal: got err %v, want ErrNil", err) + } +} + +// A type that implements the Marshaler interface, but is not nillable. +type nonNillableInt uint64 + +func (nni nonNillableInt) Marshal() ([]byte, error) { + return EncodeVarint(uint64(nni)), nil +} + +type NNIMessage struct { + nni nonNillableInt +} + +func (*NNIMessage) Reset() {} +func (*NNIMessage) String() string { return "" } +func (*NNIMessage) ProtoMessage() {} + +// A type that implements the Marshaler interface and is nillable. +type nillableMessage struct { + x uint64 +} + +func (nm *nillableMessage) Marshal() ([]byte, error) { + return EncodeVarint(nm.x), nil +} + +type NMMessage struct { + nm *nillableMessage +} + +func (*NMMessage) Reset() {} +func (*NMMessage) String() string { return "" } +func (*NMMessage) ProtoMessage() {} + +// Verify a type that uses the Marshaler interface, but has a nil pointer. +func TestNilMarshaler(t *testing.T) { + // Try a struct with a Marshaler field that is nil. + // It should be directly marshable. + nmm := new(NMMessage) + if _, err := Marshal(nmm); err != nil { + t.Error("unexpected error marshaling nmm: ", err) + } + + // Try a struct with a Marshaler field that is not nillable. + nnim := new(NNIMessage) + nnim.nni = 7 + var _ Marshaler = nnim.nni // verify it is truly a Marshaler + if _, err := Marshal(nnim); err != nil { + t.Error("unexpected error marshaling nnim: ", err) + } +} + +func TestAllSetDefaults(t *testing.T) { + // Exercise SetDefaults with all scalar field types. + m := &Defaults{ + // NaN != NaN, so override that here. + F_Nan: Float32(1.7), + } + expected := &Defaults{ + F_Bool: Bool(true), + F_Int32: Int32(32), + F_Int64: Int64(64), + F_Fixed32: Uint32(320), + F_Fixed64: Uint64(640), + F_Uint32: Uint32(3200), + F_Uint64: Uint64(6400), + F_Float: Float32(314159), + F_Double: Float64(271828), + F_String: String(`hello, "world!"` + "\n"), + F_Bytes: []byte("Bignose"), + F_Sint32: Int32(-32), + F_Sint64: Int64(-64), + F_Enum: Defaults_GREEN.Enum(), + F_Pinf: Float32(float32(math.Inf(1))), + F_Ninf: Float32(float32(math.Inf(-1))), + F_Nan: Float32(1.7), + StrZero: String(""), + } + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("SetDefaults failed\n got %v\nwant %v", m, expected) + } +} + +func TestSetDefaultsWithSetField(t *testing.T) { + // Check that a set value is not overridden. + m := &Defaults{ + F_Int32: Int32(12), + } + SetDefaults(m) + if v := m.GetF_Int32(); v != 12 { + t.Errorf("m.FInt32 = %v, want 12", v) + } +} + +func TestSetDefaultsWithSubMessage(t *testing.T) { + m := &OtherMessage{ + Key: Int64(123), + Inner: &InnerMessage{ + Host: String("gopher"), + }, + } + expected := &OtherMessage{ + Key: Int64(123), + Inner: &InnerMessage{ + Host: String("gopher"), + Port: Int32(4000), + }, + } + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("\n got %v\nwant %v", m, expected) + } +} + +func TestSetDefaultsWithRepeatedSubMessage(t *testing.T) { + m := &MyMessage{ + RepInner: []*InnerMessage{{}}, + } + expected := &MyMessage{ + RepInner: []*InnerMessage{{ + Port: Int32(4000), + }}, + } + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("\n got %v\nwant %v", m, expected) + } +} + +func TestSetDefaultWithRepeatedNonMessage(t *testing.T) { + m := &MyMessage{ + Pet: []string{"turtle", "wombat"}, + } + expected := Clone(m) + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("\n got %v\nwant %v", m, expected) + } +} + +func TestMaximumTagNumber(t *testing.T) { + m := &MaxTag{ + LastField: String("natural goat essence"), + } + buf, err := Marshal(m) + if err != nil { + t.Fatalf("proto.Marshal failed: %v", err) + } + m2 := new(MaxTag) + if err := Unmarshal(buf, m2); err != nil { + t.Fatalf("proto.Unmarshal failed: %v", err) + } + if got, want := m2.GetLastField(), *m.LastField; got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestJSON(t *testing.T) { + m := &MyMessage{ + Count: Int32(4), + Pet: []string{"bunny", "kitty"}, + Inner: &InnerMessage{ + Host: String("cauchy"), + }, + Bikeshed: MyMessage_GREEN.Enum(), + } + const expected = `{"count":4,"pet":["bunny","kitty"],"inner":{"host":"cauchy"},"bikeshed":1}` + + b, err := json.Marshal(m) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + s := string(b) + if s != expected { + t.Errorf("got %s\nwant %s", s, expected) + } + + received := new(MyMessage) + if err := json.Unmarshal(b, received); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if !Equal(received, m) { + t.Fatalf("got %s, want %s", received, m) + } + + // Test unmarshalling of JSON with symbolic enum name. + const old = `{"count":4,"pet":["bunny","kitty"],"inner":{"host":"cauchy"},"bikeshed":"GREEN"}` + received.Reset() + if err := json.Unmarshal([]byte(old), received); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if !Equal(received, m) { + t.Fatalf("got %s, want %s", received, m) + } +} + +func TestBadWireType(t *testing.T) { + b := []byte{7<<3 | 6} // field 7, wire type 6 + pb := new(OtherMessage) + if err := Unmarshal(b, pb); err == nil { + t.Errorf("Unmarshal did not fail") + } else if !strings.Contains(err.Error(), "unknown wire type") { + t.Errorf("wrong error: %v", err) + } +} + +func TestBytesWithInvalidLength(t *testing.T) { + // If a byte sequence has an invalid (negative) length, Unmarshal should not panic. + b := []byte{2<<3 | WireBytes, 0xff, 0xff, 0xff, 0xff, 0xff, 0} + Unmarshal(b, new(MyMessage)) +} + +func TestLengthOverflow(t *testing.T) { + // Overflowing a length should not panic. + b := []byte{2<<3 | WireBytes, 1, 1, 3<<3 | WireBytes, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x01} + Unmarshal(b, new(MyMessage)) +} + +func TestVarintOverflow(t *testing.T) { + // Overflowing a 64-bit length should not be allowed. + b := []byte{1<<3 | WireVarint, 0x01, 3<<3 | WireBytes, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01} + if err := Unmarshal(b, new(MyMessage)); err == nil { + t.Fatalf("Overflowed uint64 length without error") + } +} + +func TestUnmarshalFuzz(t *testing.T) { + const N = 1000 + seed := time.Now().UnixNano() + t.Logf("RNG seed is %d", seed) + rng := rand.New(rand.NewSource(seed)) + buf := make([]byte, 20) + for i := 0; i < N; i++ { + for j := range buf { + buf[j] = byte(rng.Intn(256)) + } + fuzzUnmarshal(t, buf) + } +} + +func TestMergeMessages(t *testing.T) { + pb := &MessageList{Message: []*MessageList_Message{{Name: String("x"), Count: Int32(1)}}} + data, err := Marshal(pb) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + pb1 := new(MessageList) + if err := Unmarshal(data, pb1); err != nil { + t.Fatalf("first Unmarshal: %v", err) + } + if err := Unmarshal(data, pb1); err != nil { + t.Fatalf("second Unmarshal: %v", err) + } + if len(pb1.Message) != 1 { + t.Errorf("two Unmarshals produced %d Messages, want 1", len(pb1.Message)) + } + + pb2 := new(MessageList) + if err := UnmarshalMerge(data, pb2); err != nil { + t.Fatalf("first UnmarshalMerge: %v", err) + } + if err := UnmarshalMerge(data, pb2); err != nil { + t.Fatalf("second UnmarshalMerge: %v", err) + } + if len(pb2.Message) != 2 { + t.Errorf("two UnmarshalMerges produced %d Messages, want 2", len(pb2.Message)) + } +} + +func TestExtensionMarshalOrder(t *testing.T) { + m := &MyMessage{Count: Int(123)} + if err := SetExtension(m, E_Ext_More, &Ext{Data: String("alpha")}); err != nil { + t.Fatalf("SetExtension: %v", err) + } + if err := SetExtension(m, E_Ext_Text, String("aleph")); err != nil { + t.Fatalf("SetExtension: %v", err) + } + if err := SetExtension(m, E_Ext_Number, Int32(1)); err != nil { + t.Fatalf("SetExtension: %v", err) + } + + // Serialize m several times, and check we get the same bytes each time. + var orig []byte + for i := 0; i < 100; i++ { + b, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if i == 0 { + orig = b + continue + } + if !bytes.Equal(b, orig) { + t.Errorf("Bytes differ on attempt #%d", i) + } + } +} + +// Many extensions, because small maps might not iterate differently on each iteration. +var exts = []*ExtensionDesc{ + E_X201, + E_X202, + E_X203, + E_X204, + E_X205, + E_X206, + E_X207, + E_X208, + E_X209, + E_X210, + E_X211, + E_X212, + E_X213, + E_X214, + E_X215, + E_X216, + E_X217, + E_X218, + E_X219, + E_X220, + E_X221, + E_X222, + E_X223, + E_X224, + E_X225, + E_X226, + E_X227, + E_X228, + E_X229, + E_X230, + E_X231, + E_X232, + E_X233, + E_X234, + E_X235, + E_X236, + E_X237, + E_X238, + E_X239, + E_X240, + E_X241, + E_X242, + E_X243, + E_X244, + E_X245, + E_X246, + E_X247, + E_X248, + E_X249, + E_X250, +} + +func TestMessageSetMarshalOrder(t *testing.T) { + m := &MyMessageSet{} + for _, x := range exts { + if err := SetExtension(m, x, &Empty{}); err != nil { + t.Fatalf("SetExtension: %v", err) + } + } + + buf, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + // Serialize m several times, and check we get the same bytes each time. + for i := 0; i < 10; i++ { + b1, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if !bytes.Equal(b1, buf) { + t.Errorf("Bytes differ on re-Marshal #%d", i) + } + + m2 := &MyMessageSet{} + if err := Unmarshal(buf, m2); err != nil { + t.Errorf("Unmarshal: %v", err) + } + b2, err := Marshal(m2) + if err != nil { + t.Errorf("re-Marshal: %v", err) + } + if !bytes.Equal(b2, buf) { + t.Errorf("Bytes differ on round-trip #%d", i) + } + } +} + +func TestUnmarshalMergesMessages(t *testing.T) { + // If a nested message occurs twice in the input, + // the fields should be merged when decoding. + a := &OtherMessage{ + Key: Int64(123), + Inner: &InnerMessage{ + Host: String("polhode"), + Port: Int32(1234), + }, + } + aData, err := Marshal(a) + if err != nil { + t.Fatalf("Marshal(a): %v", err) + } + b := &OtherMessage{ + Weight: Float32(1.2), + Inner: &InnerMessage{ + Host: String("herpolhode"), + Connected: Bool(true), + }, + } + bData, err := Marshal(b) + if err != nil { + t.Fatalf("Marshal(b): %v", err) + } + want := &OtherMessage{ + Key: Int64(123), + Weight: Float32(1.2), + Inner: &InnerMessage{ + Host: String("herpolhode"), + Port: Int32(1234), + Connected: Bool(true), + }, + } + got := new(OtherMessage) + if err := Unmarshal(append(aData, bData...), got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !Equal(got, want) { + t.Errorf("\n got %v\nwant %v", got, want) + } +} + +func TestEncodingSizes(t *testing.T) { + tests := []struct { + m Message + n int + }{ + {&Defaults{F_Int32: Int32(math.MaxInt32)}, 6}, + {&Defaults{F_Int32: Int32(math.MinInt32)}, 11}, + {&Defaults{F_Uint32: Uint32(uint32(math.MaxInt32) + 1)}, 6}, + {&Defaults{F_Uint32: Uint32(math.MaxUint32)}, 6}, + } + for _, test := range tests { + b, err := Marshal(test.m) + if err != nil { + t.Errorf("Marshal(%v): %v", test.m, err) + continue + } + if len(b) != test.n { + t.Errorf("Marshal(%v) yielded %d bytes, want %d bytes", test.m, len(b), test.n) + } + } +} + +func TestRequiredNotSetError(t *testing.T) { + pb := initGoTest(false) + pb.RequiredField.Label = nil + pb.F_Int32Required = nil + pb.F_Int64Required = nil + + expected := "0807" + // field 1, encoding 0, value 7 + "2206" + "120474797065" + // field 4, encoding 2 (GoTestField) + "5001" + // field 10, encoding 0, value 1 + "6d20000000" + // field 13, encoding 5, value 0x20 + "714000000000000000" + // field 14, encoding 1, value 0x40 + "78a019" + // field 15, encoding 0, value 0xca0 = 3232 + "8001c032" + // field 16, encoding 0, value 0x1940 = 6464 + "8d0100004a45" + // field 17, encoding 5, value 3232.0 + "9101000000000040b940" + // field 18, encoding 1, value 6464.0 + "9a0106" + "737472696e67" + // field 19, encoding 2, string "string" + "b304" + // field 70, encoding 3, start group + "ba0408" + "7265717569726564" + // field 71, encoding 2, string "required" + "b404" + // field 70, encoding 4, end group + "aa0605" + "6279746573" + // field 101, encoding 2, string "bytes" + "b0063f" + // field 102, encoding 0, 0x3f zigzag32 + "b8067f" // field 103, encoding 0, 0x7f zigzag64 + + o := old() + bytes, err := Marshal(pb) + if _, ok := err.(*RequiredNotSetError); !ok { + fmt.Printf("marshal-1 err = %v, want *RequiredNotSetError", err) + o.DebugPrint("", bytes) + t.Fatalf("expected = %s", expected) + } + if strings.Index(err.Error(), "RequiredField.Label") < 0 { + t.Errorf("marshal-1 wrong err msg: %v", err) + } + if !equal(bytes, expected, t) { + o.DebugPrint("neq 1", bytes) + t.Fatalf("expected = %s", expected) + } + + // Now test Unmarshal by recreating the original buffer. + pbd := new(GoTest) + err = Unmarshal(bytes, pbd) + if _, ok := err.(*RequiredNotSetError); !ok { + t.Fatalf("unmarshal err = %v, want *RequiredNotSetError", err) + o.DebugPrint("", bytes) + t.Fatalf("string = %s", expected) + } + if strings.Index(err.Error(), "RequiredField.{Unknown}") < 0 { + t.Errorf("unmarshal wrong err msg: %v", err) + } + bytes, err = Marshal(pbd) + if _, ok := err.(*RequiredNotSetError); !ok { + t.Errorf("marshal-2 err = %v, want *RequiredNotSetError", err) + o.DebugPrint("", bytes) + t.Fatalf("string = %s", expected) + } + if strings.Index(err.Error(), "RequiredField.Label") < 0 { + t.Errorf("marshal-2 wrong err msg: %v", err) + } + if !equal(bytes, expected, t) { + o.DebugPrint("neq 2", bytes) + t.Fatalf("string = %s", expected) + } +} + +func fuzzUnmarshal(t *testing.T, data []byte) { + defer func() { + if e := recover(); e != nil { + t.Errorf("These bytes caused a panic: %+v", data) + t.Logf("Stack:\n%s", debug.Stack()) + t.FailNow() + } + }() + + pb := new(MyMessage) + Unmarshal(data, pb) +} + +func TestMapFieldMarshal(t *testing.T) { + m := &MessageWithMap{ + NameMapping: map[int32]string{ + 1: "Rob", + 4: "Ian", + 8: "Dave", + }, + } + b, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + // b should be the concatenation of these three byte sequences in some order. + parts := []string{ + "\n\a\b\x01\x12\x03Rob", + "\n\a\b\x04\x12\x03Ian", + "\n\b\b\x08\x12\x04Dave", + } + ok := false + for i := range parts { + for j := range parts { + if j == i { + continue + } + for k := range parts { + if k == i || k == j { + continue + } + try := parts[i] + parts[j] + parts[k] + if bytes.Equal(b, []byte(try)) { + ok = true + break + } + } + } + } + if !ok { + t.Fatalf("Incorrect Marshal output.\n got %q\nwant %q (or a permutation of that)", b, parts[0]+parts[1]+parts[2]) + } + t.Logf("FYI b: %q", b) + + (new(Buffer)).DebugPrint("Dump of b", b) +} + +func TestMapFieldRoundTrips(t *testing.T) { + m := &MessageWithMap{ + NameMapping: map[int32]string{ + 1: "Rob", + 4: "Ian", + 8: "Dave", + }, + MsgMapping: map[int64]*FloatingPoint{ + 0x7001: &FloatingPoint{F: Float64(2.0)}, + }, + ByteMapping: map[bool][]byte{ + false: []byte("that's not right!"), + true: []byte("aye, 'tis true!"), + }, + } + b, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + t.Logf("FYI b: %q", b) + m2 := new(MessageWithMap) + if err := Unmarshal(b, m2); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + for _, pair := range [][2]interface{}{ + {m.NameMapping, m2.NameMapping}, + {m.MsgMapping, m2.MsgMapping}, + {m.ByteMapping, m2.ByteMapping}, + } { + if !reflect.DeepEqual(pair[0], pair[1]) { + t.Errorf("Map did not survive a round trip.\ninitial: %v\n final: %v", pair[0], pair[1]) + } + } +} + +func TestMapFieldWithNil(t *testing.T) { + m := &MessageWithMap{ + MsgMapping: map[int64]*FloatingPoint{ + 1: nil, + }, + } + b, err := Marshal(m) + if err == nil { + t.Fatalf("Marshal of bad map should have failed, got these bytes: %v", b) + } +} + +// Benchmarks + +func testMsg() *GoTest { + pb := initGoTest(true) + const N = 1000 // Internally the library starts much smaller. + pb.F_Int32Repeated = make([]int32, N) + pb.F_DoubleRepeated = make([]float64, N) + for i := 0; i < N; i++ { + pb.F_Int32Repeated[i] = int32(i) + pb.F_DoubleRepeated[i] = float64(i) + } + return pb +} + +func bytesMsg() *GoTest { + pb := initGoTest(true) + buf := make([]byte, 4000) + for i := range buf { + buf[i] = byte(i) + } + pb.F_BytesDefaulted = buf + return pb +} + +func benchmarkMarshal(b *testing.B, pb Message, marshal func(Message) ([]byte, error)) { + d, _ := marshal(pb) + b.SetBytes(int64(len(d))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + marshal(pb) + } +} + +func benchmarkBufferMarshal(b *testing.B, pb Message) { + p := NewBuffer(nil) + benchmarkMarshal(b, pb, func(pb0 Message) ([]byte, error) { + p.Reset() + err := p.Marshal(pb0) + return p.Bytes(), err + }) +} + +func benchmarkSize(b *testing.B, pb Message) { + benchmarkMarshal(b, pb, func(pb0 Message) ([]byte, error) { + Size(pb) + return nil, nil + }) +} + +func newOf(pb Message) Message { + in := reflect.ValueOf(pb) + if in.IsNil() { + return pb + } + return reflect.New(in.Type().Elem()).Interface().(Message) +} + +func benchmarkUnmarshal(b *testing.B, pb Message, unmarshal func([]byte, Message) error) { + d, _ := Marshal(pb) + b.SetBytes(int64(len(d))) + pbd := newOf(pb) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + unmarshal(d, pbd) + } +} + +func benchmarkBufferUnmarshal(b *testing.B, pb Message) { + p := NewBuffer(nil) + benchmarkUnmarshal(b, pb, func(d []byte, pb0 Message) error { + p.SetBuf(d) + return p.Unmarshal(pb0) + }) +} + +// Benchmark{Marshal,BufferMarshal,Size,Unmarshal,BufferUnmarshal}{,Bytes} + +func BenchmarkMarshal(b *testing.B) { + benchmarkMarshal(b, testMsg(), Marshal) +} + +func BenchmarkBufferMarshal(b *testing.B) { + benchmarkBufferMarshal(b, testMsg()) +} + +func BenchmarkSize(b *testing.B) { + benchmarkSize(b, testMsg()) +} + +func BenchmarkUnmarshal(b *testing.B) { + benchmarkUnmarshal(b, testMsg(), Unmarshal) +} + +func BenchmarkBufferUnmarshal(b *testing.B) { + benchmarkBufferUnmarshal(b, testMsg()) +} + +func BenchmarkMarshalBytes(b *testing.B) { + benchmarkMarshal(b, bytesMsg(), Marshal) +} + +func BenchmarkBufferMarshalBytes(b *testing.B) { + benchmarkBufferMarshal(b, bytesMsg()) +} + +func BenchmarkSizeBytes(b *testing.B) { + benchmarkSize(b, bytesMsg()) +} + +func BenchmarkUnmarshalBytes(b *testing.B) { + benchmarkUnmarshal(b, bytesMsg(), Unmarshal) +} + +func BenchmarkBufferUnmarshalBytes(b *testing.B) { + benchmarkBufferUnmarshal(b, bytesMsg()) +} + +func BenchmarkUnmarshalUnrecognizedFields(b *testing.B) { + b.StopTimer() + pb := initGoTestField() + skip := &GoSkipTest{ + SkipInt32: Int32(32), + SkipFixed32: Uint32(3232), + SkipFixed64: Uint64(6464), + SkipString: String("skipper"), + Skipgroup: &GoSkipTest_SkipGroup{ + GroupInt32: Int32(75), + GroupString: String("wxyz"), + }, + } + + pbd := new(GoTestField) + p := NewBuffer(nil) + p.Marshal(pb) + p.Marshal(skip) + p2 := NewBuffer(nil) + + b.StartTimer() + for i := 0; i < b.N; i++ { + p2.SetBuf(p.Bytes()) + p2.Unmarshal(pbd) + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/clone.go charm-2.2.0/src/github.com/golang/protobuf/proto/clone.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/clone.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/clone.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,197 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Protocol buffer deep copy and merge. +// TODO: MessageSet and RawMessage. + +package proto + +import ( + "log" + "reflect" + "strings" +) + +// Clone returns a deep copy of a protocol buffer. +func Clone(pb Message) Message { + in := reflect.ValueOf(pb) + if in.IsNil() { + return pb + } + + out := reflect.New(in.Type().Elem()) + // out is empty so a merge is a deep copy. + mergeStruct(out.Elem(), in.Elem()) + return out.Interface().(Message) +} + +// Merge merges src into dst. +// Required and optional fields that are set in src will be set to that value in dst. +// Elements of repeated fields will be appended. +// Merge panics if src and dst are not the same type, or if dst is nil. +func Merge(dst, src Message) { + in := reflect.ValueOf(src) + out := reflect.ValueOf(dst) + if out.IsNil() { + panic("proto: nil destination") + } + if in.Type() != out.Type() { + // Explicit test prior to mergeStruct so that mistyped nils will fail + panic("proto: type mismatch") + } + if in.IsNil() { + // Merging nil into non-nil is a quiet no-op + return + } + mergeStruct(out.Elem(), in.Elem()) +} + +func mergeStruct(out, in reflect.Value) { + for i := 0; i < in.NumField(); i++ { + f := in.Type().Field(i) + if strings.HasPrefix(f.Name, "XXX_") { + continue + } + mergeAny(out.Field(i), in.Field(i)) + } + + if emIn, ok := in.Addr().Interface().(extendableProto); ok { + emOut := out.Addr().Interface().(extendableProto) + mergeExtension(emOut.ExtensionMap(), emIn.ExtensionMap()) + } + + uf := in.FieldByName("XXX_unrecognized") + if !uf.IsValid() { + return + } + uin := uf.Bytes() + if len(uin) > 0 { + out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...)) + } +} + +func mergeAny(out, in reflect.Value) { + if in.Type() == protoMessageType { + if !in.IsNil() { + if out.IsNil() { + out.Set(reflect.ValueOf(Clone(in.Interface().(Message)))) + } else { + Merge(out.Interface().(Message), in.Interface().(Message)) + } + } + return + } + switch in.Kind() { + case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, + reflect.String, reflect.Uint32, reflect.Uint64: + out.Set(in) + case reflect.Map: + if in.Len() == 0 { + return + } + if out.IsNil() { + out.Set(reflect.MakeMap(in.Type())) + } + // For maps with value types of *T or []byte we need to deep copy each value. + elemKind := in.Type().Elem().Kind() + for _, key := range in.MapKeys() { + var val reflect.Value + switch elemKind { + case reflect.Ptr: + val = reflect.New(in.Type().Elem().Elem()) + mergeAny(val, in.MapIndex(key)) + case reflect.Slice: + val = in.MapIndex(key) + val = reflect.ValueOf(append([]byte{}, val.Bytes()...)) + default: + val = in.MapIndex(key) + } + out.SetMapIndex(key, val) + } + case reflect.Ptr: + if in.IsNil() { + return + } + if out.IsNil() { + out.Set(reflect.New(in.Elem().Type())) + } + mergeAny(out.Elem(), in.Elem()) + case reflect.Slice: + if in.IsNil() { + return + } + if in.Type().Elem().Kind() == reflect.Uint8 { + // []byte is a scalar bytes field, not a repeated field. + // Make a deep copy. + // Append to []byte{} instead of []byte(nil) so that we never end up + // with a nil result. + out.SetBytes(append([]byte{}, in.Bytes()...)) + return + } + n := in.Len() + if out.IsNil() { + out.Set(reflect.MakeSlice(in.Type(), 0, n)) + } + switch in.Type().Elem().Kind() { + case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, + reflect.String, reflect.Uint32, reflect.Uint64: + out.Set(reflect.AppendSlice(out, in)) + default: + for i := 0; i < n; i++ { + x := reflect.Indirect(reflect.New(in.Type().Elem())) + mergeAny(x, in.Index(i)) + out.Set(reflect.Append(out, x)) + } + } + case reflect.Struct: + mergeStruct(out, in) + default: + // unknown type, so not a protocol buffer + log.Printf("proto: don't know how to copy %v", in) + } +} + +func mergeExtension(out, in map[int32]Extension) { + for extNum, eIn := range in { + eOut := Extension{desc: eIn.desc} + if eIn.value != nil { + v := reflect.New(reflect.TypeOf(eIn.value)).Elem() + mergeAny(v, reflect.ValueOf(eIn.value)) + eOut.value = v.Interface() + } + if eIn.enc != nil { + eOut.enc = make([]byte, len(eIn.enc)) + copy(eOut.enc, eIn.enc) + } + + out[extNum] = eOut + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/clone_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/clone_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/clone_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/clone_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,227 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "testing" + + "github.com/golang/protobuf/proto" + + pb "github.com/golang/protobuf/proto/testdata" +) + +var cloneTestMessage = &pb.MyMessage{ + Count: proto.Int32(42), + Name: proto.String("Dave"), + Pet: []string{"bunny", "kitty", "horsey"}, + Inner: &pb.InnerMessage{ + Host: proto.String("niles"), + Port: proto.Int32(9099), + Connected: proto.Bool(true), + }, + Others: []*pb.OtherMessage{ + { + Value: []byte("some bytes"), + }, + }, + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(6), + }, + RepBytes: [][]byte{[]byte("sham"), []byte("wow")}, +} + +func init() { + ext := &pb.Ext{ + Data: proto.String("extension"), + } + if err := proto.SetExtension(cloneTestMessage, pb.E_Ext_More, ext); err != nil { + panic("SetExtension: " + err.Error()) + } +} + +func TestClone(t *testing.T) { + m := proto.Clone(cloneTestMessage).(*pb.MyMessage) + if !proto.Equal(m, cloneTestMessage) { + t.Errorf("Clone(%v) = %v", cloneTestMessage, m) + } + + // Verify it was a deep copy. + *m.Inner.Port++ + if proto.Equal(m, cloneTestMessage) { + t.Error("Mutating clone changed the original") + } + // Byte fields and repeated fields should be copied. + if &m.Pet[0] == &cloneTestMessage.Pet[0] { + t.Error("Pet: repeated field not copied") + } + if &m.Others[0] == &cloneTestMessage.Others[0] { + t.Error("Others: repeated field not copied") + } + if &m.Others[0].Value[0] == &cloneTestMessage.Others[0].Value[0] { + t.Error("Others[0].Value: bytes field not copied") + } + if &m.RepBytes[0] == &cloneTestMessage.RepBytes[0] { + t.Error("RepBytes: repeated field not copied") + } + if &m.RepBytes[0][0] == &cloneTestMessage.RepBytes[0][0] { + t.Error("RepBytes[0]: bytes field not copied") + } +} + +func TestCloneNil(t *testing.T) { + var m *pb.MyMessage + if c := proto.Clone(m); !proto.Equal(m, c) { + t.Errorf("Clone(%v) = %v", m, c) + } +} + +var mergeTests = []struct { + src, dst, want proto.Message +}{ + { + src: &pb.MyMessage{ + Count: proto.Int32(42), + }, + dst: &pb.MyMessage{ + Name: proto.String("Dave"), + }, + want: &pb.MyMessage{ + Count: proto.Int32(42), + Name: proto.String("Dave"), + }, + }, + { + src: &pb.MyMessage{ + Inner: &pb.InnerMessage{ + Host: proto.String("hey"), + Connected: proto.Bool(true), + }, + Pet: []string{"horsey"}, + Others: []*pb.OtherMessage{ + { + Value: []byte("some bytes"), + }, + }, + }, + dst: &pb.MyMessage{ + Inner: &pb.InnerMessage{ + Host: proto.String("niles"), + Port: proto.Int32(9099), + }, + Pet: []string{"bunny", "kitty"}, + Others: []*pb.OtherMessage{ + { + Key: proto.Int64(31415926535), + }, + { + // Explicitly test a src=nil field + Inner: nil, + }, + }, + }, + want: &pb.MyMessage{ + Inner: &pb.InnerMessage{ + Host: proto.String("hey"), + Connected: proto.Bool(true), + Port: proto.Int32(9099), + }, + Pet: []string{"bunny", "kitty", "horsey"}, + Others: []*pb.OtherMessage{ + { + Key: proto.Int64(31415926535), + }, + {}, + { + Value: []byte("some bytes"), + }, + }, + }, + }, + { + src: &pb.MyMessage{ + RepBytes: [][]byte{[]byte("wow")}, + }, + dst: &pb.MyMessage{ + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(6), + }, + RepBytes: [][]byte{[]byte("sham")}, + }, + want: &pb.MyMessage{ + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(6), + }, + RepBytes: [][]byte{[]byte("sham"), []byte("wow")}, + }, + }, + // Check that a scalar bytes field replaces rather than appends. + { + src: &pb.OtherMessage{Value: []byte("foo")}, + dst: &pb.OtherMessage{Value: []byte("bar")}, + want: &pb.OtherMessage{Value: []byte("foo")}, + }, + { + src: &pb.MessageWithMap{ + NameMapping: map[int32]string{6: "Nigel"}, + MsgMapping: map[int64]*pb.FloatingPoint{ + 0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)}, + }, + ByteMapping: map[bool][]byte{true: []byte("wowsa")}, + }, + dst: &pb.MessageWithMap{ + NameMapping: map[int32]string{ + 6: "Bruce", // should be overwritten + 7: "Andrew", + }, + }, + want: &pb.MessageWithMap{ + NameMapping: map[int32]string{ + 6: "Nigel", + 7: "Andrew", + }, + MsgMapping: map[int64]*pb.FloatingPoint{ + 0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)}, + }, + ByteMapping: map[bool][]byte{true: []byte("wowsa")}, + }, + }, +} + +func TestMerge(t *testing.T) { + for _, m := range mergeTests { + got := proto.Clone(m.dst) + proto.Merge(got, m.src) + if !proto.Equal(got, m.want) { + t.Errorf("Merge(%v, %v)\n got %v\nwant %v\n", m.dst, m.src, got, m.want) + } + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/decode.go charm-2.2.0/src/github.com/golang/protobuf/proto/decode.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/decode.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/decode.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,827 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Routines for decoding protocol buffer data to construct in-memory representations. + */ + +import ( + "errors" + "fmt" + "io" + "os" + "reflect" +) + +// errOverflow is returned when an integer is too large to be represented. +var errOverflow = errors.New("proto: integer overflow") + +// The fundamental decoders that interpret bytes on the wire. +// Those that take integer types all return uint64 and are +// therefore of type valueDecoder. + +// DecodeVarint reads a varint-encoded integer from the slice. +// It returns the integer and the number of bytes consumed, or +// zero if there is not enough. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +func DecodeVarint(buf []byte) (x uint64, n int) { + // x, n already 0 + for shift := uint(0); shift < 64; shift += 7 { + if n >= len(buf) { + return 0, 0 + } + b := uint64(buf[n]) + n++ + x |= (b & 0x7F) << shift + if (b & 0x80) == 0 { + return x, n + } + } + + // The number is too large to represent in a 64-bit value. + return 0, 0 +} + +// DecodeVarint reads a varint-encoded integer from the Buffer. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +func (p *Buffer) DecodeVarint() (x uint64, err error) { + // x, err already 0 + + i := p.index + l := len(p.buf) + + for shift := uint(0); shift < 64; shift += 7 { + if i >= l { + err = io.ErrUnexpectedEOF + return + } + b := p.buf[i] + i++ + x |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + p.index = i + return + } + } + + // The number is too large to represent in a 64-bit value. + err = errOverflow + return +} + +// DecodeFixed64 reads a 64-bit integer from the Buffer. +// This is the format for the +// fixed64, sfixed64, and double protocol buffer types. +func (p *Buffer) DecodeFixed64() (x uint64, err error) { + // x, err already 0 + i := p.index + 8 + if i < 0 || i > len(p.buf) { + err = io.ErrUnexpectedEOF + return + } + p.index = i + + x = uint64(p.buf[i-8]) + x |= uint64(p.buf[i-7]) << 8 + x |= uint64(p.buf[i-6]) << 16 + x |= uint64(p.buf[i-5]) << 24 + x |= uint64(p.buf[i-4]) << 32 + x |= uint64(p.buf[i-3]) << 40 + x |= uint64(p.buf[i-2]) << 48 + x |= uint64(p.buf[i-1]) << 56 + return +} + +// DecodeFixed32 reads a 32-bit integer from the Buffer. +// This is the format for the +// fixed32, sfixed32, and float protocol buffer types. +func (p *Buffer) DecodeFixed32() (x uint64, err error) { + // x, err already 0 + i := p.index + 4 + if i < 0 || i > len(p.buf) { + err = io.ErrUnexpectedEOF + return + } + p.index = i + + x = uint64(p.buf[i-4]) + x |= uint64(p.buf[i-3]) << 8 + x |= uint64(p.buf[i-2]) << 16 + x |= uint64(p.buf[i-1]) << 24 + return +} + +// DecodeZigzag64 reads a zigzag-encoded 64-bit integer +// from the Buffer. +// This is the format used for the sint64 protocol buffer type. +func (p *Buffer) DecodeZigzag64() (x uint64, err error) { + x, err = p.DecodeVarint() + if err != nil { + return + } + x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63) + return +} + +// DecodeZigzag32 reads a zigzag-encoded 32-bit integer +// from the Buffer. +// This is the format used for the sint32 protocol buffer type. +func (p *Buffer) DecodeZigzag32() (x uint64, err error) { + x, err = p.DecodeVarint() + if err != nil { + return + } + x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31)) + return +} + +// These are not ValueDecoders: they produce an array of bytes or a string. +// bytes, embedded messages + +// DecodeRawBytes reads a count-delimited byte buffer from the Buffer. +// This is the format used for the bytes protocol buffer +// type and for embedded messages. +func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) { + n, err := p.DecodeVarint() + if err != nil { + return nil, err + } + + nb := int(n) + if nb < 0 { + return nil, fmt.Errorf("proto: bad byte length %d", nb) + } + end := p.index + nb + if end < p.index || end > len(p.buf) { + return nil, io.ErrUnexpectedEOF + } + + if !alloc { + // todo: check if can get more uses of alloc=false + buf = p.buf[p.index:end] + p.index += nb + return + } + + buf = make([]byte, nb) + copy(buf, p.buf[p.index:]) + p.index += nb + return +} + +// DecodeStringBytes reads an encoded string from the Buffer. +// This is the format used for the proto2 string type. +func (p *Buffer) DecodeStringBytes() (s string, err error) { + buf, err := p.DecodeRawBytes(false) + if err != nil { + return + } + return string(buf), nil +} + +// Skip the next item in the buffer. Its wire type is decoded and presented as an argument. +// If the protocol buffer has extensions, and the field matches, add it as an extension. +// Otherwise, if the XXX_unrecognized field exists, append the skipped data there. +func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error { + oi := o.index + + err := o.skip(t, tag, wire) + if err != nil { + return err + } + + if !unrecField.IsValid() { + return nil + } + + ptr := structPointer_Bytes(base, unrecField) + + // Add the skipped field to struct field + obuf := o.buf + + o.buf = *ptr + o.EncodeVarint(uint64(tag<<3 | wire)) + *ptr = append(o.buf, obuf[oi:o.index]...) + + o.buf = obuf + + return nil +} + +// Skip the next item in the buffer. Its wire type is decoded and presented as an argument. +func (o *Buffer) skip(t reflect.Type, tag, wire int) error { + + var u uint64 + var err error + + switch wire { + case WireVarint: + _, err = o.DecodeVarint() + case WireFixed64: + _, err = o.DecodeFixed64() + case WireBytes: + _, err = o.DecodeRawBytes(false) + case WireFixed32: + _, err = o.DecodeFixed32() + case WireStartGroup: + for { + u, err = o.DecodeVarint() + if err != nil { + break + } + fwire := int(u & 0x7) + if fwire == WireEndGroup { + break + } + ftag := int(u >> 3) + err = o.skip(t, ftag, fwire) + if err != nil { + break + } + } + default: + err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t) + } + return err +} + +// Unmarshaler is the interface representing objects that can +// unmarshal themselves. The method should reset the receiver before +// decoding starts. The argument points to data that may be +// overwritten, so implementations should not keep references to the +// buffer. +type Unmarshaler interface { + Unmarshal([]byte) error +} + +// Unmarshal parses the protocol buffer representation in buf and places the +// decoded result in pb. If the struct underlying pb does not match +// the data in buf, the results can be unpredictable. +// +// Unmarshal resets pb before starting to unmarshal, so any +// existing data in pb is always removed. Use UnmarshalMerge +// to preserve and append to existing data. +func Unmarshal(buf []byte, pb Message) error { + pb.Reset() + return UnmarshalMerge(buf, pb) +} + +// UnmarshalMerge parses the protocol buffer representation in buf and +// writes the decoded result to pb. If the struct underlying pb does not match +// the data in buf, the results can be unpredictable. +// +// UnmarshalMerge merges into existing data in pb. +// Most code should use Unmarshal instead. +func UnmarshalMerge(buf []byte, pb Message) error { + // If the object can unmarshal itself, let it. + if u, ok := pb.(Unmarshaler); ok { + return u.Unmarshal(buf) + } + return NewBuffer(buf).Unmarshal(pb) +} + +// Unmarshal parses the protocol buffer representation in the +// Buffer and places the decoded result in pb. If the struct +// underlying pb does not match the data in the buffer, the results can be +// unpredictable. +func (p *Buffer) Unmarshal(pb Message) error { + // If the object can unmarshal itself, let it. + if u, ok := pb.(Unmarshaler); ok { + err := u.Unmarshal(p.buf[p.index:]) + p.index = len(p.buf) + return err + } + + typ, base, err := getbase(pb) + if err != nil { + return err + } + + err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base) + + if collectStats { + stats.Decode++ + } + + return err +} + +// unmarshalType does the work of unmarshaling a structure. +func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error { + var state errorState + required, reqFields := prop.reqCount, uint64(0) + + var err error + for err == nil && o.index < len(o.buf) { + oi := o.index + var u uint64 + u, err = o.DecodeVarint() + if err != nil { + break + } + wire := int(u & 0x7) + if wire == WireEndGroup { + if is_group { + return nil // input is satisfied + } + return fmt.Errorf("proto: %s: wiretype end group for non-group", st) + } + tag := int(u >> 3) + if tag <= 0 { + return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire) + } + fieldnum, ok := prop.decoderTags.get(tag) + if !ok { + // Maybe it's an extension? + if prop.extendable { + if e := structPointer_Interface(base, st).(extendableProto); isExtensionField(e, int32(tag)) { + if err = o.skip(st, tag, wire); err == nil { + ext := e.ExtensionMap()[int32(tag)] // may be missing + ext.enc = append(ext.enc, o.buf[oi:o.index]...) + e.ExtensionMap()[int32(tag)] = ext + } + continue + } + } + err = o.skipAndSave(st, tag, wire, base, prop.unrecField) + continue + } + p := prop.Prop[fieldnum] + + if p.dec == nil { + fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name) + continue + } + dec := p.dec + if wire != WireStartGroup && wire != p.WireType { + if wire == WireBytes && p.packedDec != nil { + // a packable field + dec = p.packedDec + } else { + err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType) + continue + } + } + decErr := dec(o, p, base) + if decErr != nil && !state.shouldContinue(decErr, p) { + err = decErr + } + if err == nil && p.Required { + // Successfully decoded a required field. + if tag <= 64 { + // use bitmap for fields 1-64 to catch field reuse. + var mask uint64 = 1 << uint64(tag-1) + if reqFields&mask == 0 { + // new required field + reqFields |= mask + required-- + } + } else { + // This is imprecise. It can be fooled by a required field + // with a tag > 64 that is encoded twice; that's very rare. + // A fully correct implementation would require allocating + // a data structure, which we would like to avoid. + required-- + } + } + } + if err == nil { + if is_group { + return io.ErrUnexpectedEOF + } + if state.err != nil { + return state.err + } + if required > 0 { + // Not enough information to determine the exact field. If we use extra + // CPU, we could determine the field only if the missing required field + // has a tag <= 64 and we check reqFields. + return &RequiredNotSetError{"{Unknown}"} + } + } + return err +} + +// Individual type decoders +// For each, +// u is the decoded value, +// v is a pointer to the field (pointer) in the struct + +// Sizes of the pools to allocate inside the Buffer. +// The goal is modest amortization and allocation +// on at least 16-byte boundaries. +const ( + boolPoolSize = 16 + uint32PoolSize = 8 + uint64PoolSize = 4 +) + +// Decode a bool. +func (o *Buffer) dec_bool(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + if len(o.bools) == 0 { + o.bools = make([]bool, boolPoolSize) + } + o.bools[0] = u != 0 + *structPointer_Bool(base, p.field) = &o.bools[0] + o.bools = o.bools[1:] + return nil +} + +func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + *structPointer_BoolVal(base, p.field) = u != 0 + return nil +} + +// Decode an int32. +func (o *Buffer) dec_int32(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word32_Set(structPointer_Word32(base, p.field), o, uint32(u)) + return nil +} + +func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u)) + return nil +} + +// Decode an int64. +func (o *Buffer) dec_int64(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word64_Set(structPointer_Word64(base, p.field), o, u) + return nil +} + +func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word64Val_Set(structPointer_Word64Val(base, p.field), o, u) + return nil +} + +// Decode a string. +func (o *Buffer) dec_string(p *Properties, base structPointer) error { + s, err := o.DecodeStringBytes() + if err != nil { + return err + } + *structPointer_String(base, p.field) = &s + return nil +} + +func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error { + s, err := o.DecodeStringBytes() + if err != nil { + return err + } + *structPointer_StringVal(base, p.field) = s + return nil +} + +// Decode a slice of bytes ([]byte). +func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error { + b, err := o.DecodeRawBytes(true) + if err != nil { + return err + } + *structPointer_Bytes(base, p.field) = b + return nil +} + +// Decode a slice of bools ([]bool). +func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + v := structPointer_BoolSlice(base, p.field) + *v = append(*v, u != 0) + return nil +} + +// Decode a slice of bools ([]bool) in packed format. +func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error { + v := structPointer_BoolSlice(base, p.field) + + nn, err := o.DecodeVarint() + if err != nil { + return err + } + nb := int(nn) // number of bytes of encoded bools + + y := *v + for i := 0; i < nb; i++ { + u, err := p.valDec(o) + if err != nil { + return err + } + y = append(y, u != 0) + } + + *v = y + return nil +} + +// Decode a slice of int32s ([]int32). +func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + structPointer_Word32Slice(base, p.field).Append(uint32(u)) + return nil +} + +// Decode a slice of int32s ([]int32) in packed format. +func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error { + v := structPointer_Word32Slice(base, p.field) + + nn, err := o.DecodeVarint() + if err != nil { + return err + } + nb := int(nn) // number of bytes of encoded int32s + + fin := o.index + nb + if fin < o.index { + return errOverflow + } + for o.index < fin { + u, err := p.valDec(o) + if err != nil { + return err + } + v.Append(uint32(u)) + } + return nil +} + +// Decode a slice of int64s ([]int64). +func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + + structPointer_Word64Slice(base, p.field).Append(u) + return nil +} + +// Decode a slice of int64s ([]int64) in packed format. +func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error { + v := structPointer_Word64Slice(base, p.field) + + nn, err := o.DecodeVarint() + if err != nil { + return err + } + nb := int(nn) // number of bytes of encoded int64s + + fin := o.index + nb + if fin < o.index { + return errOverflow + } + for o.index < fin { + u, err := p.valDec(o) + if err != nil { + return err + } + v.Append(u) + } + return nil +} + +// Decode a slice of strings ([]string). +func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error { + s, err := o.DecodeStringBytes() + if err != nil { + return err + } + v := structPointer_StringSlice(base, p.field) + *v = append(*v, s) + return nil +} + +// Decode a slice of slice of bytes ([][]byte). +func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error { + b, err := o.DecodeRawBytes(true) + if err != nil { + return err + } + v := structPointer_BytesSlice(base, p.field) + *v = append(*v, b) + return nil +} + +// Decode a map field. +func (o *Buffer) dec_new_map(p *Properties, base structPointer) error { + raw, err := o.DecodeRawBytes(false) + if err != nil { + return err + } + oi := o.index // index at the end of this map entry + o.index -= len(raw) // move buffer back to start of map entry + + mptr := structPointer_Map(base, p.field, p.mtype) // *map[K]V + if mptr.Elem().IsNil() { + mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem())) + } + v := mptr.Elem() // map[K]V + + // Prepare addressable doubly-indirect placeholders for the key and value types. + // See enc_new_map for why. + keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K + keybase := toStructPointer(keyptr.Addr()) // **K + + var valbase structPointer + var valptr reflect.Value + switch p.mtype.Elem().Kind() { + case reflect.Slice: + // []byte + var dummy []byte + valptr = reflect.ValueOf(&dummy) // *[]byte + valbase = toStructPointer(valptr) // *[]byte + case reflect.Ptr: + // message; valptr is **Msg; need to allocate the intermediate pointer + valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V + valptr.Set(reflect.New(valptr.Type().Elem())) + valbase = toStructPointer(valptr) + default: + // everything else + valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V + valbase = toStructPointer(valptr.Addr()) // **V + } + + // Decode. + // This parses a restricted wire format, namely the encoding of a message + // with two fields. See enc_new_map for the format. + for o.index < oi { + // tagcode for key and value properties are always a single byte + // because they have tags 1 and 2. + tagcode := o.buf[o.index] + o.index++ + switch tagcode { + case p.mkeyprop.tagcode[0]: + if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil { + return err + } + case p.mvalprop.tagcode[0]: + if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil { + return err + } + default: + // TODO: Should we silently skip this instead? + return fmt.Errorf("proto: bad map data tag %d", raw[0]) + } + } + keyelem, valelem := keyptr.Elem(), valptr.Elem() + if !keyelem.IsValid() || !valelem.IsValid() { + // We did not decode the key or the value in the map entry. + // Either way, it's an invalid map entry. + return fmt.Errorf("proto: bad map data: missing key/val") + } + + v.SetMapIndex(keyelem, valelem) + return nil +} + +// Decode a group. +func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error { + bas := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(bas) { + // allocate new nested message + bas = toStructPointer(reflect.New(p.stype)) + structPointer_SetStructPointer(base, p.field, bas) + } + return o.unmarshalType(p.stype, p.sprop, true, bas) +} + +// Decode an embedded message. +func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) { + raw, e := o.DecodeRawBytes(false) + if e != nil { + return e + } + + bas := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(bas) { + // allocate new nested message + bas = toStructPointer(reflect.New(p.stype)) + structPointer_SetStructPointer(base, p.field, bas) + } + + // If the object can unmarshal itself, let it. + if p.isUnmarshaler { + iv := structPointer_Interface(bas, p.stype) + return iv.(Unmarshaler).Unmarshal(raw) + } + + obuf := o.buf + oi := o.index + o.buf = raw + o.index = 0 + + err = o.unmarshalType(p.stype, p.sprop, false, bas) + o.buf = obuf + o.index = oi + + return err +} + +// Decode a slice of embedded messages. +func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error { + return o.dec_slice_struct(p, false, base) +} + +// Decode a slice of embedded groups. +func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error { + return o.dec_slice_struct(p, true, base) +} + +// Decode a slice of structs ([]*struct). +func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error { + v := reflect.New(p.stype) + bas := toStructPointer(v) + structPointer_StructPointerSlice(base, p.field).Append(bas) + + if is_group { + err := o.unmarshalType(p.stype, p.sprop, is_group, bas) + return err + } + + raw, err := o.DecodeRawBytes(false) + if err != nil { + return err + } + + // If the object can unmarshal itself, let it. + if p.isUnmarshaler { + iv := v.Interface() + return iv.(Unmarshaler).Unmarshal(raw) + } + + obuf := o.buf + oi := o.index + o.buf = raw + o.index = 0 + + err = o.unmarshalType(p.stype, p.sprop, is_group, bas) + + o.buf = obuf + o.index = oi + + return err +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/encode.go charm-2.2.0/src/github.com/golang/protobuf/proto/encode.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/encode.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/encode.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,1293 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Routines for encoding data into the wire format for protocol buffers. + */ + +import ( + "errors" + "fmt" + "reflect" + "sort" +) + +// RequiredNotSetError is the error returned if Marshal is called with +// a protocol buffer struct whose required fields have not +// all been initialized. It is also the error returned if Unmarshal is +// called with an encoded protocol buffer that does not include all the +// required fields. +// +// When printed, RequiredNotSetError reports the first unset required field in a +// message. If the field cannot be precisely determined, it is reported as +// "{Unknown}". +type RequiredNotSetError struct { + field string +} + +func (e *RequiredNotSetError) Error() string { + return fmt.Sprintf("proto: required field %q not set", e.field) +} + +var ( + // errRepeatedHasNil is the error returned if Marshal is called with + // a struct with a repeated field containing a nil element. + errRepeatedHasNil = errors.New("proto: repeated field has nil element") + + // ErrNil is the error returned if Marshal is called with nil. + ErrNil = errors.New("proto: Marshal called with nil") +) + +// The fundamental encoders that put bytes on the wire. +// Those that take integer types all accept uint64 and are +// therefore of type valueEncoder. + +const maxVarintBytes = 10 // maximum length of a varint + +// EncodeVarint returns the varint encoding of x. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +// Not used by the package itself, but helpful to clients +// wishing to use the same encoding. +func EncodeVarint(x uint64) []byte { + var buf [maxVarintBytes]byte + var n int + for n = 0; x > 127; n++ { + buf[n] = 0x80 | uint8(x&0x7F) + x >>= 7 + } + buf[n] = uint8(x) + n++ + return buf[0:n] +} + +// EncodeVarint writes a varint-encoded integer to the Buffer. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +func (p *Buffer) EncodeVarint(x uint64) error { + for x >= 1<<7 { + p.buf = append(p.buf, uint8(x&0x7f|0x80)) + x >>= 7 + } + p.buf = append(p.buf, uint8(x)) + return nil +} + +func sizeVarint(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} + +// EncodeFixed64 writes a 64-bit integer to the Buffer. +// This is the format for the +// fixed64, sfixed64, and double protocol buffer types. +func (p *Buffer) EncodeFixed64(x uint64) error { + p.buf = append(p.buf, + uint8(x), + uint8(x>>8), + uint8(x>>16), + uint8(x>>24), + uint8(x>>32), + uint8(x>>40), + uint8(x>>48), + uint8(x>>56)) + return nil +} + +func sizeFixed64(x uint64) int { + return 8 +} + +// EncodeFixed32 writes a 32-bit integer to the Buffer. +// This is the format for the +// fixed32, sfixed32, and float protocol buffer types. +func (p *Buffer) EncodeFixed32(x uint64) error { + p.buf = append(p.buf, + uint8(x), + uint8(x>>8), + uint8(x>>16), + uint8(x>>24)) + return nil +} + +func sizeFixed32(x uint64) int { + return 4 +} + +// EncodeZigzag64 writes a zigzag-encoded 64-bit integer +// to the Buffer. +// This is the format used for the sint64 protocol buffer type. +func (p *Buffer) EncodeZigzag64(x uint64) error { + // use signed number to get arithmetic right shift. + return p.EncodeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} + +func sizeZigzag64(x uint64) int { + return sizeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} + +// EncodeZigzag32 writes a zigzag-encoded 32-bit integer +// to the Buffer. +// This is the format used for the sint32 protocol buffer type. +func (p *Buffer) EncodeZigzag32(x uint64) error { + // use signed number to get arithmetic right shift. + return p.EncodeVarint(uint64((uint32(x) << 1) ^ uint32((int32(x) >> 31)))) +} + +func sizeZigzag32(x uint64) int { + return sizeVarint(uint64((uint32(x) << 1) ^ uint32((int32(x) >> 31)))) +} + +// EncodeRawBytes writes a count-delimited byte buffer to the Buffer. +// This is the format used for the bytes protocol buffer +// type and for embedded messages. +func (p *Buffer) EncodeRawBytes(b []byte) error { + p.EncodeVarint(uint64(len(b))) + p.buf = append(p.buf, b...) + return nil +} + +func sizeRawBytes(b []byte) int { + return sizeVarint(uint64(len(b))) + + len(b) +} + +// EncodeStringBytes writes an encoded string to the Buffer. +// This is the format used for the proto2 string type. +func (p *Buffer) EncodeStringBytes(s string) error { + p.EncodeVarint(uint64(len(s))) + p.buf = append(p.buf, s...) + return nil +} + +func sizeStringBytes(s string) int { + return sizeVarint(uint64(len(s))) + + len(s) +} + +// Marshaler is the interface representing objects that can marshal themselves. +type Marshaler interface { + Marshal() ([]byte, error) +} + +// Marshal takes the protocol buffer +// and encodes it into the wire format, returning the data. +func Marshal(pb Message) ([]byte, error) { + // Can the object marshal itself? + if m, ok := pb.(Marshaler); ok { + return m.Marshal() + } + p := NewBuffer(nil) + err := p.Marshal(pb) + var state errorState + if err != nil && !state.shouldContinue(err, nil) { + return nil, err + } + if p.buf == nil && err == nil { + // Return a non-nil slice on success. + return []byte{}, nil + } + return p.buf, err +} + +// Marshal takes the protocol buffer +// and encodes it into the wire format, writing the result to the +// Buffer. +func (p *Buffer) Marshal(pb Message) error { + // Can the object marshal itself? + if m, ok := pb.(Marshaler); ok { + data, err := m.Marshal() + if err != nil { + return err + } + p.buf = append(p.buf, data...) + return nil + } + + t, base, err := getbase(pb) + if structPointer_IsNil(base) { + return ErrNil + } + if err == nil { + err = p.enc_struct(GetProperties(t.Elem()), base) + } + + if collectStats { + stats.Encode++ + } + + return err +} + +// Size returns the encoded size of a protocol buffer. +func Size(pb Message) (n int) { + // Can the object marshal itself? If so, Size is slow. + // TODO: add Size to Marshaler, or add a Sizer interface. + if m, ok := pb.(Marshaler); ok { + b, _ := m.Marshal() + return len(b) + } + + t, base, err := getbase(pb) + if structPointer_IsNil(base) { + return 0 + } + if err == nil { + n = size_struct(GetProperties(t.Elem()), base) + } + + if collectStats { + stats.Size++ + } + + return +} + +// Individual type encoders. + +// Encode a bool. +func (o *Buffer) enc_bool(p *Properties, base structPointer) error { + v := *structPointer_Bool(base, p.field) + if v == nil { + return ErrNil + } + x := 0 + if *v { + x = 1 + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func (o *Buffer) enc_proto3_bool(p *Properties, base structPointer) error { + v := *structPointer_BoolVal(base, p.field) + if !v { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, 1) + return nil +} + +func size_bool(p *Properties, base structPointer) int { + v := *structPointer_Bool(base, p.field) + if v == nil { + return 0 + } + return len(p.tagcode) + 1 // each bool takes exactly one byte +} + +func size_proto3_bool(p *Properties, base structPointer) int { + v := *structPointer_BoolVal(base, p.field) + if !v { + return 0 + } + return len(p.tagcode) + 1 // each bool takes exactly one byte +} + +// Encode an int32. +func (o *Buffer) enc_int32(p *Properties, base structPointer) error { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return ErrNil + } + x := int32(word32_Get(v)) // permit sign extension to use full 64-bit range + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func (o *Buffer) enc_proto3_int32(p *Properties, base structPointer) error { + v := structPointer_Word32Val(base, p.field) + x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range + if x == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func size_int32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return 0 + } + x := int32(word32_Get(v)) // permit sign extension to use full 64-bit range + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + +func size_proto3_int32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32Val(base, p.field) + x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range + if x == 0 { + return 0 + } + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + +// Encode a uint32. +// Exactly the same as int32, except for no sign extension. +func (o *Buffer) enc_uint32(p *Properties, base structPointer) error { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return ErrNil + } + x := word32_Get(v) + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func (o *Buffer) enc_proto3_uint32(p *Properties, base structPointer) error { + v := structPointer_Word32Val(base, p.field) + x := word32Val_Get(v) + if x == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func size_uint32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return 0 + } + x := word32_Get(v) + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + +func size_proto3_uint32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32Val(base, p.field) + x := word32Val_Get(v) + if x == 0 { + return 0 + } + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + +// Encode an int64. +func (o *Buffer) enc_int64(p *Properties, base structPointer) error { + v := structPointer_Word64(base, p.field) + if word64_IsNil(v) { + return ErrNil + } + x := word64_Get(v) + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, x) + return nil +} + +func (o *Buffer) enc_proto3_int64(p *Properties, base structPointer) error { + v := structPointer_Word64Val(base, p.field) + x := word64Val_Get(v) + if x == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, x) + return nil +} + +func size_int64(p *Properties, base structPointer) (n int) { + v := structPointer_Word64(base, p.field) + if word64_IsNil(v) { + return 0 + } + x := word64_Get(v) + n += len(p.tagcode) + n += p.valSize(x) + return +} + +func size_proto3_int64(p *Properties, base structPointer) (n int) { + v := structPointer_Word64Val(base, p.field) + x := word64Val_Get(v) + if x == 0 { + return 0 + } + n += len(p.tagcode) + n += p.valSize(x) + return +} + +// Encode a string. +func (o *Buffer) enc_string(p *Properties, base structPointer) error { + v := *structPointer_String(base, p.field) + if v == nil { + return ErrNil + } + x := *v + o.buf = append(o.buf, p.tagcode...) + o.EncodeStringBytes(x) + return nil +} + +func (o *Buffer) enc_proto3_string(p *Properties, base structPointer) error { + v := *structPointer_StringVal(base, p.field) + if v == "" { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeStringBytes(v) + return nil +} + +func size_string(p *Properties, base structPointer) (n int) { + v := *structPointer_String(base, p.field) + if v == nil { + return 0 + } + x := *v + n += len(p.tagcode) + n += sizeStringBytes(x) + return +} + +func size_proto3_string(p *Properties, base structPointer) (n int) { + v := *structPointer_StringVal(base, p.field) + if v == "" { + return 0 + } + n += len(p.tagcode) + n += sizeStringBytes(v) + return +} + +// All protocol buffer fields are nillable, but be careful. +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + } + return false +} + +// Encode a message struct. +func (o *Buffer) enc_struct_message(p *Properties, base structPointer) error { + var state errorState + structp := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(structp) { + return ErrNil + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, err := m.Marshal() + if err != nil && !state.shouldContinue(err, nil) { + return err + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(data) + return nil + } + + o.buf = append(o.buf, p.tagcode...) + return o.enc_len_struct(p.sprop, structp, &state) +} + +func size_struct_message(p *Properties, base structPointer) int { + structp := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(structp) { + return 0 + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, _ := m.Marshal() + n0 := len(p.tagcode) + n1 := sizeRawBytes(data) + return n0 + n1 + } + + n0 := len(p.tagcode) + n1 := size_struct(p.sprop, structp) + n2 := sizeVarint(uint64(n1)) // size of encoded length + return n0 + n1 + n2 +} + +// Encode a group struct. +func (o *Buffer) enc_struct_group(p *Properties, base structPointer) error { + var state errorState + b := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(b) { + return ErrNil + } + + o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) + err := o.enc_struct(p.sprop, b) + if err != nil && !state.shouldContinue(err, nil) { + return err + } + o.EncodeVarint(uint64((p.Tag << 3) | WireEndGroup)) + return state.err +} + +func size_struct_group(p *Properties, base structPointer) (n int) { + b := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(b) { + return 0 + } + + n += sizeVarint(uint64((p.Tag << 3) | WireStartGroup)) + n += size_struct(p.sprop, b) + n += sizeVarint(uint64((p.Tag << 3) | WireEndGroup)) + return +} + +// Encode a slice of bools ([]bool). +func (o *Buffer) enc_slice_bool(p *Properties, base structPointer) error { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return ErrNil + } + for _, x := range s { + o.buf = append(o.buf, p.tagcode...) + v := uint64(0) + if x { + v = 1 + } + p.valEnc(o, v) + } + return nil +} + +func size_slice_bool(p *Properties, base structPointer) int { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return 0 + } + return l * (len(p.tagcode) + 1) // each bool takes exactly one byte +} + +// Encode a slice of bools ([]bool) in packed format. +func (o *Buffer) enc_slice_packed_bool(p *Properties, base structPointer) error { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(l)) // each bool takes exactly one byte + for _, x := range s { + v := uint64(0) + if x { + v = 1 + } + p.valEnc(o, v) + } + return nil +} + +func size_slice_packed_bool(p *Properties, base structPointer) (n int) { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return 0 + } + n += len(p.tagcode) + n += sizeVarint(uint64(l)) + n += l // each bool takes exactly one byte + return +} + +// Encode a slice of bytes ([]byte). +func (o *Buffer) enc_slice_byte(p *Properties, base structPointer) error { + s := *structPointer_Bytes(base, p.field) + if s == nil { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(s) + return nil +} + +func (o *Buffer) enc_proto3_slice_byte(p *Properties, base structPointer) error { + s := *structPointer_Bytes(base, p.field) + if len(s) == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(s) + return nil +} + +func size_slice_byte(p *Properties, base structPointer) (n int) { + s := *structPointer_Bytes(base, p.field) + if s == nil { + return 0 + } + n += len(p.tagcode) + n += sizeRawBytes(s) + return +} + +func size_proto3_slice_byte(p *Properties, base structPointer) (n int) { + s := *structPointer_Bytes(base, p.field) + if len(s) == 0 { + return 0 + } + n += len(p.tagcode) + n += sizeRawBytes(s) + return +} + +// Encode a slice of int32s ([]int32). +func (o *Buffer) enc_slice_int32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + p.valEnc(o, uint64(x)) + } + return nil +} + +func size_slice_int32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + n += len(p.tagcode) + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + n += p.valSize(uint64(x)) + } + return +} + +// Encode a slice of int32s ([]int32) in packed format. +func (o *Buffer) enc_slice_packed_int32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + // TODO: Reuse a Buffer. + buf := NewBuffer(nil) + for i := 0; i < l; i++ { + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + p.valEnc(buf, uint64(x)) + } + + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(len(buf.buf))) + o.buf = append(o.buf, buf.buf...) + return nil +} + +func size_slice_packed_int32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + var bufSize int + for i := 0; i < l; i++ { + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + bufSize += p.valSize(uint64(x)) + } + + n += len(p.tagcode) + n += sizeVarint(uint64(bufSize)) + n += bufSize + return +} + +// Encode a slice of uint32s ([]uint32). +// Exactly the same as int32, except for no sign extension. +func (o *Buffer) enc_slice_uint32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + x := s.Index(i) + p.valEnc(o, uint64(x)) + } + return nil +} + +func size_slice_uint32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + n += len(p.tagcode) + x := s.Index(i) + n += p.valSize(uint64(x)) + } + return +} + +// Encode a slice of uint32s ([]uint32) in packed format. +// Exactly the same as int32, except for no sign extension. +func (o *Buffer) enc_slice_packed_uint32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + // TODO: Reuse a Buffer. + buf := NewBuffer(nil) + for i := 0; i < l; i++ { + p.valEnc(buf, uint64(s.Index(i))) + } + + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(len(buf.buf))) + o.buf = append(o.buf, buf.buf...) + return nil +} + +func size_slice_packed_uint32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + var bufSize int + for i := 0; i < l; i++ { + bufSize += p.valSize(uint64(s.Index(i))) + } + + n += len(p.tagcode) + n += sizeVarint(uint64(bufSize)) + n += bufSize + return +} + +// Encode a slice of int64s ([]int64). +func (o *Buffer) enc_slice_int64(p *Properties, base structPointer) error { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, s.Index(i)) + } + return nil +} + +func size_slice_int64(p *Properties, base structPointer) (n int) { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + n += len(p.tagcode) + n += p.valSize(s.Index(i)) + } + return +} + +// Encode a slice of int64s ([]int64) in packed format. +func (o *Buffer) enc_slice_packed_int64(p *Properties, base structPointer) error { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + // TODO: Reuse a Buffer. + buf := NewBuffer(nil) + for i := 0; i < l; i++ { + p.valEnc(buf, s.Index(i)) + } + + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(len(buf.buf))) + o.buf = append(o.buf, buf.buf...) + return nil +} + +func size_slice_packed_int64(p *Properties, base structPointer) (n int) { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + var bufSize int + for i := 0; i < l; i++ { + bufSize += p.valSize(s.Index(i)) + } + + n += len(p.tagcode) + n += sizeVarint(uint64(bufSize)) + n += bufSize + return +} + +// Encode a slice of slice of bytes ([][]byte). +func (o *Buffer) enc_slice_slice_byte(p *Properties, base structPointer) error { + ss := *structPointer_BytesSlice(base, p.field) + l := len(ss) + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(ss[i]) + } + return nil +} + +func size_slice_slice_byte(p *Properties, base structPointer) (n int) { + ss := *structPointer_BytesSlice(base, p.field) + l := len(ss) + if l == 0 { + return 0 + } + n += l * len(p.tagcode) + for i := 0; i < l; i++ { + n += sizeRawBytes(ss[i]) + } + return +} + +// Encode a slice of strings ([]string). +func (o *Buffer) enc_slice_string(p *Properties, base structPointer) error { + ss := *structPointer_StringSlice(base, p.field) + l := len(ss) + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + o.EncodeStringBytes(ss[i]) + } + return nil +} + +func size_slice_string(p *Properties, base structPointer) (n int) { + ss := *structPointer_StringSlice(base, p.field) + l := len(ss) + n += l * len(p.tagcode) + for i := 0; i < l; i++ { + n += sizeStringBytes(ss[i]) + } + return +} + +// Encode a slice of message structs ([]*struct). +func (o *Buffer) enc_slice_struct_message(p *Properties, base structPointer) error { + var state errorState + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + + for i := 0; i < l; i++ { + structp := s.Index(i) + if structPointer_IsNil(structp) { + return errRepeatedHasNil + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, err := m.Marshal() + if err != nil && !state.shouldContinue(err, nil) { + return err + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(data) + continue + } + + o.buf = append(o.buf, p.tagcode...) + err := o.enc_len_struct(p.sprop, structp, &state) + if err != nil && !state.shouldContinue(err, nil) { + if err == ErrNil { + return errRepeatedHasNil + } + return err + } + } + return state.err +} + +func size_slice_struct_message(p *Properties, base structPointer) (n int) { + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + n += l * len(p.tagcode) + for i := 0; i < l; i++ { + structp := s.Index(i) + if structPointer_IsNil(structp) { + return // return the size up to this point + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, _ := m.Marshal() + n += len(p.tagcode) + n += sizeRawBytes(data) + continue + } + + n0 := size_struct(p.sprop, structp) + n1 := sizeVarint(uint64(n0)) // size of encoded length + n += n0 + n1 + } + return +} + +// Encode a slice of group structs ([]*struct). +func (o *Buffer) enc_slice_struct_group(p *Properties, base structPointer) error { + var state errorState + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + + for i := 0; i < l; i++ { + b := s.Index(i) + if structPointer_IsNil(b) { + return errRepeatedHasNil + } + + o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) + + err := o.enc_struct(p.sprop, b) + + if err != nil && !state.shouldContinue(err, nil) { + if err == ErrNil { + return errRepeatedHasNil + } + return err + } + + o.EncodeVarint(uint64((p.Tag << 3) | WireEndGroup)) + } + return state.err +} + +func size_slice_struct_group(p *Properties, base structPointer) (n int) { + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + + n += l * sizeVarint(uint64((p.Tag<<3)|WireStartGroup)) + n += l * sizeVarint(uint64((p.Tag<<3)|WireEndGroup)) + for i := 0; i < l; i++ { + b := s.Index(i) + if structPointer_IsNil(b) { + return // return size up to this point + } + + n += size_struct(p.sprop, b) + } + return +} + +// Encode an extension map. +func (o *Buffer) enc_map(p *Properties, base structPointer) error { + v := *structPointer_ExtMap(base, p.field) + if err := encodeExtensionMap(v); err != nil { + return err + } + // Fast-path for common cases: zero or one extensions. + if len(v) <= 1 { + for _, e := range v { + o.buf = append(o.buf, e.enc...) + } + return nil + } + + // Sort keys to provide a deterministic encoding. + keys := make([]int, 0, len(v)) + for k := range v { + keys = append(keys, int(k)) + } + sort.Ints(keys) + + for _, k := range keys { + o.buf = append(o.buf, v[int32(k)].enc...) + } + return nil +} + +func size_map(p *Properties, base structPointer) int { + v := *structPointer_ExtMap(base, p.field) + return sizeExtensionMap(v) +} + +// Encode a map field. +func (o *Buffer) enc_new_map(p *Properties, base structPointer) error { + var state errorState // XXX: or do we need to plumb this through? + + /* + A map defined as + map map_field = N; + is encoded in the same way as + message MapFieldEntry { + key_type key = 1; + value_type value = 2; + } + repeated MapFieldEntry map_field = N; + */ + + v := structPointer_Map(base, p.field, p.mtype).Elem() // map[K]V + if v.Len() == 0 { + return nil + } + + keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype) + + enc := func() error { + if err := p.mkeyprop.enc(o, p.mkeyprop, keybase); err != nil { + return err + } + if err := p.mvalprop.enc(o, p.mvalprop, valbase); err != nil { + return err + } + return nil + } + + keys := v.MapKeys() + sort.Sort(mapKeys(keys)) + for _, key := range keys { + val := v.MapIndex(key) + + // The only illegal map entry values are nil message pointers. + if val.Kind() == reflect.Ptr && val.IsNil() { + return errors.New("proto: map has nil element") + } + + keycopy.Set(key) + valcopy.Set(val) + + o.buf = append(o.buf, p.tagcode...) + if err := o.enc_len_thing(enc, &state); err != nil { + return err + } + } + return nil +} + +func size_new_map(p *Properties, base structPointer) int { + v := structPointer_Map(base, p.field, p.mtype).Elem() // map[K]V + + keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype) + + n := 0 + for _, key := range v.MapKeys() { + val := v.MapIndex(key) + keycopy.Set(key) + valcopy.Set(val) + + // Tag codes for key and val are the responsibility of the sub-sizer. + keysize := p.mkeyprop.size(p.mkeyprop, keybase) + valsize := p.mvalprop.size(p.mvalprop, valbase) + entry := keysize + valsize + // Add on tag code and length of map entry itself. + n += len(p.tagcode) + sizeVarint(uint64(entry)) + entry + } + return n +} + +// mapEncodeScratch returns a new reflect.Value matching the map's value type, +// and a structPointer suitable for passing to an encoder or sizer. +func mapEncodeScratch(mapType reflect.Type) (keycopy, valcopy reflect.Value, keybase, valbase structPointer) { + // Prepare addressable doubly-indirect placeholders for the key and value types. + // This is needed because the element-type encoders expect **T, but the map iteration produces T. + + keycopy = reflect.New(mapType.Key()).Elem() // addressable K + keyptr := reflect.New(reflect.PtrTo(keycopy.Type())).Elem() // addressable *K + keyptr.Set(keycopy.Addr()) // + keybase = toStructPointer(keyptr.Addr()) // **K + + // Value types are more varied and require special handling. + switch mapType.Elem().Kind() { + case reflect.Slice: + // []byte + var dummy []byte + valcopy = reflect.ValueOf(&dummy).Elem() // addressable []byte + valbase = toStructPointer(valcopy.Addr()) + case reflect.Ptr: + // message; the generated field type is map[K]*Msg (so V is *Msg), + // so we only need one level of indirection. + valcopy = reflect.New(mapType.Elem()).Elem() // addressable V + valbase = toStructPointer(valcopy.Addr()) + default: + // everything else + valcopy = reflect.New(mapType.Elem()).Elem() // addressable V + valptr := reflect.New(reflect.PtrTo(valcopy.Type())).Elem() // addressable *V + valptr.Set(valcopy.Addr()) // + valbase = toStructPointer(valptr.Addr()) // **V + } + return +} + +// Encode a struct. +func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error { + var state errorState + // Encode fields in tag order so that decoders may use optimizations + // that depend on the ordering. + // https://developers.google.com/protocol-buffers/docs/encoding#order + for _, i := range prop.order { + p := prop.Prop[i] + if p.enc != nil { + err := p.enc(o, p, base) + if err != nil { + if err == ErrNil { + if p.Required && state.err == nil { + state.err = &RequiredNotSetError{p.Name} + } + } else if err == errRepeatedHasNil { + // Give more context to nil values in repeated fields. + return errors.New("repeated field " + p.OrigName + " has nil element") + } else if !state.shouldContinue(err, p) { + return err + } + } + } + } + + // Add unrecognized fields at the end. + if prop.unrecField.IsValid() { + v := *structPointer_Bytes(base, prop.unrecField) + if len(v) > 0 { + o.buf = append(o.buf, v...) + } + } + + return state.err +} + +func size_struct(prop *StructProperties, base structPointer) (n int) { + for _, i := range prop.order { + p := prop.Prop[i] + if p.size != nil { + n += p.size(p, base) + } + } + + // Add unrecognized fields at the end. + if prop.unrecField.IsValid() { + v := *structPointer_Bytes(base, prop.unrecField) + n += len(v) + } + + return +} + +var zeroes [20]byte // longer than any conceivable sizeVarint + +// Encode a struct, preceded by its encoded length (as a varint). +func (o *Buffer) enc_len_struct(prop *StructProperties, base structPointer, state *errorState) error { + return o.enc_len_thing(func() error { return o.enc_struct(prop, base) }, state) +} + +// Encode something, preceded by its encoded length (as a varint). +func (o *Buffer) enc_len_thing(enc func() error, state *errorState) error { + iLen := len(o.buf) + o.buf = append(o.buf, 0, 0, 0, 0) // reserve four bytes for length + iMsg := len(o.buf) + err := enc() + if err != nil && !state.shouldContinue(err, nil) { + return err + } + lMsg := len(o.buf) - iMsg + lLen := sizeVarint(uint64(lMsg)) + switch x := lLen - (iMsg - iLen); { + case x > 0: // actual length is x bytes larger than the space we reserved + // Move msg x bytes right. + o.buf = append(o.buf, zeroes[:x]...) + copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg]) + case x < 0: // actual length is x bytes smaller than the space we reserved + // Move msg x bytes left. + copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg]) + o.buf = o.buf[:len(o.buf)+x] // x is negative + } + // Encode the length in the reserved space. + o.buf = o.buf[:iLen] + o.EncodeVarint(uint64(lMsg)) + o.buf = o.buf[:len(o.buf)+lMsg] + return state.err +} + +// errorState maintains the first error that occurs and updates that error +// with additional context. +type errorState struct { + err error +} + +// shouldContinue reports whether encoding should continue upon encountering the +// given error. If the error is RequiredNotSetError, shouldContinue returns true +// and, if this is the first appearance of that error, remembers it for future +// reporting. +// +// If prop is not nil, it may update any error with additional context about the +// field with the error. +func (s *errorState) shouldContinue(err error, prop *Properties) bool { + // Ignore unset required fields. + reqNotSet, ok := err.(*RequiredNotSetError) + if !ok { + return false + } + if s.err == nil { + if prop != nil { + err = &RequiredNotSetError{prop.Name + "." + reqNotSet.field} + } + s.err = err + } + return true +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/equal.go charm-2.2.0/src/github.com/golang/protobuf/proto/equal.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/equal.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/equal.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,256 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Protocol buffer comparison. +// TODO: MessageSet. + +package proto + +import ( + "bytes" + "log" + "reflect" + "strings" +) + +/* +Equal returns true iff protocol buffers a and b are equal. +The arguments must both be pointers to protocol buffer structs. + +Equality is defined in this way: + - Two messages are equal iff they are the same type, + corresponding fields are equal, unknown field sets + are equal, and extensions sets are equal. + - Two set scalar fields are equal iff their values are equal. + If the fields are of a floating-point type, remember that + NaN != x for all x, including NaN. + - Two repeated fields are equal iff their lengths are the same, + and their corresponding elements are equal (a "bytes" field, + although represented by []byte, is not a repeated field) + - Two unset fields are equal. + - Two unknown field sets are equal if their current + encoded state is equal. + - Two extension sets are equal iff they have corresponding + elements that are pairwise equal. + - Every other combination of things are not equal. + +The return value is undefined if a and b are not protocol buffers. +*/ +func Equal(a, b Message) bool { + if a == nil || b == nil { + return a == b + } + v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b) + if v1.Type() != v2.Type() { + return false + } + if v1.Kind() == reflect.Ptr { + if v1.IsNil() { + return v2.IsNil() + } + if v2.IsNil() { + return false + } + v1, v2 = v1.Elem(), v2.Elem() + } + if v1.Kind() != reflect.Struct { + return false + } + return equalStruct(v1, v2) +} + +// v1 and v2 are known to have the same type. +func equalStruct(v1, v2 reflect.Value) bool { + for i := 0; i < v1.NumField(); i++ { + f := v1.Type().Field(i) + if strings.HasPrefix(f.Name, "XXX_") { + continue + } + f1, f2 := v1.Field(i), v2.Field(i) + if f.Type.Kind() == reflect.Ptr { + if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 { + // both unset + continue + } else if n1 != n2 { + // set/unset mismatch + return false + } + b1, ok := f1.Interface().(raw) + if ok { + b2 := f2.Interface().(raw) + // RawMessage + if !bytes.Equal(b1.Bytes(), b2.Bytes()) { + return false + } + continue + } + f1, f2 = f1.Elem(), f2.Elem() + } + if !equalAny(f1, f2) { + return false + } + } + + if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() { + em2 := v2.FieldByName("XXX_extensions") + if !equalExtensions(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) { + return false + } + } + + uf := v1.FieldByName("XXX_unrecognized") + if !uf.IsValid() { + return true + } + + u1 := uf.Bytes() + u2 := v2.FieldByName("XXX_unrecognized").Bytes() + if !bytes.Equal(u1, u2) { + return false + } + + return true +} + +// v1 and v2 are known to have the same type. +func equalAny(v1, v2 reflect.Value) bool { + if v1.Type() == protoMessageType { + m1, _ := v1.Interface().(Message) + m2, _ := v2.Interface().(Message) + return Equal(m1, m2) + } + switch v1.Kind() { + case reflect.Bool: + return v1.Bool() == v2.Bool() + case reflect.Float32, reflect.Float64: + return v1.Float() == v2.Float() + case reflect.Int32, reflect.Int64: + return v1.Int() == v2.Int() + case reflect.Map: + if v1.Len() != v2.Len() { + return false + } + for _, key := range v1.MapKeys() { + val2 := v2.MapIndex(key) + if !val2.IsValid() { + // This key was not found in the second map. + return false + } + if !equalAny(v1.MapIndex(key), val2) { + return false + } + } + return true + case reflect.Ptr: + return equalAny(v1.Elem(), v2.Elem()) + case reflect.Slice: + if v1.Type().Elem().Kind() == reflect.Uint8 { + // short circuit: []byte + if v1.IsNil() != v2.IsNil() { + return false + } + return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte)) + } + + if v1.Len() != v2.Len() { + return false + } + for i := 0; i < v1.Len(); i++ { + if !equalAny(v1.Index(i), v2.Index(i)) { + return false + } + } + return true + case reflect.String: + return v1.Interface().(string) == v2.Interface().(string) + case reflect.Struct: + return equalStruct(v1, v2) + case reflect.Uint32, reflect.Uint64: + return v1.Uint() == v2.Uint() + } + + // unknown type, so not a protocol buffer + log.Printf("proto: don't know how to compare %v", v1) + return false +} + +// base is the struct type that the extensions are based on. +// em1 and em2 are extension maps. +func equalExtensions(base reflect.Type, em1, em2 map[int32]Extension) bool { + if len(em1) != len(em2) { + return false + } + + for extNum, e1 := range em1 { + e2, ok := em2[extNum] + if !ok { + return false + } + + m1, m2 := e1.value, e2.value + + if m1 != nil && m2 != nil { + // Both are unencoded. + if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) { + return false + } + continue + } + + // At least one is encoded. To do a semantically correct comparison + // we need to unmarshal them first. + var desc *ExtensionDesc + if m := extensionMaps[base]; m != nil { + desc = m[extNum] + } + if desc == nil { + log.Printf("proto: don't know how to compare extension %d of %v", extNum, base) + continue + } + var err error + if m1 == nil { + m1, err = decodeExtension(e1.enc, desc) + } + if m2 == nil && err == nil { + m2, err = decodeExtension(e2.enc, desc) + } + if err != nil { + // The encoded form is invalid. + log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err) + return false + } + if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) { + return false + } + } + + return true +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/equal_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/equal_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/equal_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/equal_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,191 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "testing" + + . "github.com/golang/protobuf/proto" + pb "github.com/golang/protobuf/proto/testdata" +) + +// Four identical base messages. +// The init function adds extensions to some of them. +var messageWithoutExtension = &pb.MyMessage{Count: Int32(7)} +var messageWithExtension1a = &pb.MyMessage{Count: Int32(7)} +var messageWithExtension1b = &pb.MyMessage{Count: Int32(7)} +var messageWithExtension2 = &pb.MyMessage{Count: Int32(7)} + +// Two messages with non-message extensions. +var messageWithInt32Extension1 = &pb.MyMessage{Count: Int32(8)} +var messageWithInt32Extension2 = &pb.MyMessage{Count: Int32(8)} + +func init() { + ext1 := &pb.Ext{Data: String("Kirk")} + ext2 := &pb.Ext{Data: String("Picard")} + + // messageWithExtension1a has ext1, but never marshals it. + if err := SetExtension(messageWithExtension1a, pb.E_Ext_More, ext1); err != nil { + panic("SetExtension on 1a failed: " + err.Error()) + } + + // messageWithExtension1b is the unmarshaled form of messageWithExtension1a. + if err := SetExtension(messageWithExtension1b, pb.E_Ext_More, ext1); err != nil { + panic("SetExtension on 1b failed: " + err.Error()) + } + buf, err := Marshal(messageWithExtension1b) + if err != nil { + panic("Marshal of 1b failed: " + err.Error()) + } + messageWithExtension1b.Reset() + if err := Unmarshal(buf, messageWithExtension1b); err != nil { + panic("Unmarshal of 1b failed: " + err.Error()) + } + + // messageWithExtension2 has ext2. + if err := SetExtension(messageWithExtension2, pb.E_Ext_More, ext2); err != nil { + panic("SetExtension on 2 failed: " + err.Error()) + } + + if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(23)); err != nil { + panic("SetExtension on Int32-1 failed: " + err.Error()) + } + if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(24)); err != nil { + panic("SetExtension on Int32-2 failed: " + err.Error()) + } +} + +var EqualTests = []struct { + desc string + a, b Message + exp bool +}{ + {"different types", &pb.GoEnum{}, &pb.GoTestField{}, false}, + {"equal empty", &pb.GoEnum{}, &pb.GoEnum{}, true}, + {"nil vs nil", nil, nil, true}, + {"typed nil vs typed nil", (*pb.GoEnum)(nil), (*pb.GoEnum)(nil), true}, + {"typed nil vs empty", (*pb.GoEnum)(nil), &pb.GoEnum{}, false}, + {"different typed nil", (*pb.GoEnum)(nil), (*pb.GoTestField)(nil), false}, + + {"one set field, one unset field", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{}, false}, + {"one set field zero, one unset field", &pb.GoTest{Param: Int32(0)}, &pb.GoTest{}, false}, + {"different set fields", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("bar")}, false}, + {"equal set", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("foo")}, true}, + + {"repeated, one set", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{}, false}, + {"repeated, different length", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{F_Int32Repeated: []int32{2}}, false}, + {"repeated, different value", &pb.GoTest{F_Int32Repeated: []int32{2}}, &pb.GoTest{F_Int32Repeated: []int32{3}}, false}, + {"repeated, equal", &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, true}, + {"repeated, nil equal nil", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: nil}, true}, + {"repeated, nil equal empty", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: []int32{}}, true}, + {"repeated, empty equal nil", &pb.GoTest{F_Int32Repeated: []int32{}}, &pb.GoTest{F_Int32Repeated: nil}, true}, + + { + "nested, different", + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("foo")}}, + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("bar")}}, + false, + }, + { + "nested, equal", + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}}, + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}}, + true, + }, + + {"bytes", &pb.OtherMessage{Value: []byte("foo")}, &pb.OtherMessage{Value: []byte("foo")}, true}, + {"bytes, empty", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: []byte{}}, true}, + {"bytes, empty vs nil", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: nil}, false}, + { + "repeated bytes", + &pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}}, + &pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}}, + true, + }, + + {"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false}, + {"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true}, + {"extension vs. different extension", messageWithExtension1a, messageWithExtension2, false}, + + {"int32 extension vs. itself", messageWithInt32Extension1, messageWithInt32Extension1, true}, + {"int32 extension vs. a different int32", messageWithInt32Extension1, messageWithInt32Extension2, false}, + + { + "message with group", + &pb.MyMessage{ + Count: Int32(1), + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: Int32(5), + }, + }, + &pb.MyMessage{ + Count: Int32(1), + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: Int32(5), + }, + }, + true, + }, + + { + "map same", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + true, + }, + { + "map different entry", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{2: "Rob"}}, + false, + }, + { + "map different key only", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{2: "Ken"}}, + false, + }, + { + "map different value only", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob"}}, + false, + }, +} + +func TestEqual(t *testing.T) { + for _, tc := range EqualTests { + if res := Equal(tc.a, tc.b); res != tc.exp { + t.Errorf("%v: Equal(%v, %v) = %v, want %v", tc.desc, tc.a, tc.b, res, tc.exp) + } + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/extensions.go charm-2.2.0/src/github.com/golang/protobuf/proto/extensions.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/extensions.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/extensions.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,400 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Types and routines for supporting protocol buffer extensions. + */ + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "sync" +) + +// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message. +var ErrMissingExtension = errors.New("proto: missing extension") + +// ExtensionRange represents a range of message extensions for a protocol buffer. +// Used in code generated by the protocol compiler. +type ExtensionRange struct { + Start, End int32 // both inclusive +} + +// extendableProto is an interface implemented by any protocol buffer that may be extended. +type extendableProto interface { + Message + ExtensionRangeArray() []ExtensionRange + ExtensionMap() map[int32]Extension +} + +var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem() + +// ExtensionDesc represents an extension specification. +// Used in generated code from the protocol compiler. +type ExtensionDesc struct { + ExtendedType Message // nil pointer to the type that is being extended + ExtensionType interface{} // nil pointer to the extension type + Field int32 // field number + Name string // fully-qualified name of extension, for text formatting + Tag string // protobuf tag style +} + +func (ed *ExtensionDesc) repeated() bool { + t := reflect.TypeOf(ed.ExtensionType) + return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 +} + +// Extension represents an extension in a message. +type Extension struct { + // When an extension is stored in a message using SetExtension + // only desc and value are set. When the message is marshaled + // enc will be set to the encoded form of the message. + // + // When a message is unmarshaled and contains extensions, each + // extension will have only enc set. When such an extension is + // accessed using GetExtension (or GetExtensions) desc and value + // will be set. + desc *ExtensionDesc + value interface{} + enc []byte +} + +// SetRawExtension is for testing only. +func SetRawExtension(base extendableProto, id int32, b []byte) { + base.ExtensionMap()[id] = Extension{enc: b} +} + +// isExtensionField returns true iff the given field number is in an extension range. +func isExtensionField(pb extendableProto, field int32) bool { + for _, er := range pb.ExtensionRangeArray() { + if er.Start <= field && field <= er.End { + return true + } + } + return false +} + +// checkExtensionTypes checks that the given extension is valid for pb. +func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error { + // Check the extended type. + if a, b := reflect.TypeOf(pb), reflect.TypeOf(extension.ExtendedType); a != b { + return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String()) + } + // Check the range. + if !isExtensionField(pb, extension.Field) { + return errors.New("proto: bad extension number; not in declared ranges") + } + return nil +} + +// extPropKey is sufficient to uniquely identify an extension. +type extPropKey struct { + base reflect.Type + field int32 +} + +var extProp = struct { + sync.RWMutex + m map[extPropKey]*Properties +}{ + m: make(map[extPropKey]*Properties), +} + +func extensionProperties(ed *ExtensionDesc) *Properties { + key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field} + + extProp.RLock() + if prop, ok := extProp.m[key]; ok { + extProp.RUnlock() + return prop + } + extProp.RUnlock() + + extProp.Lock() + defer extProp.Unlock() + // Check again. + if prop, ok := extProp.m[key]; ok { + return prop + } + + prop := new(Properties) + prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil) + extProp.m[key] = prop + return prop +} + +// encodeExtensionMap encodes any unmarshaled (unencoded) extensions in m. +func encodeExtensionMap(m map[int32]Extension) error { + for k, e := range m { + if e.value == nil || e.desc == nil { + // Extension is only in its encoded form. + continue + } + + // We don't skip extensions that have an encoded form set, + // because the extension value may have been mutated after + // the last time this function was called. + + et := reflect.TypeOf(e.desc.ExtensionType) + props := extensionProperties(e.desc) + + p := NewBuffer(nil) + // If e.value has type T, the encoder expects a *struct{ X T }. + // Pass a *T with a zero field and hope it all works out. + x := reflect.New(et) + x.Elem().Set(reflect.ValueOf(e.value)) + if err := props.enc(p, props, toStructPointer(x)); err != nil { + return err + } + e.enc = p.buf + m[k] = e + } + return nil +} + +func sizeExtensionMap(m map[int32]Extension) (n int) { + for _, e := range m { + if e.value == nil || e.desc == nil { + // Extension is only in its encoded form. + n += len(e.enc) + continue + } + + // We don't skip extensions that have an encoded form set, + // because the extension value may have been mutated after + // the last time this function was called. + + et := reflect.TypeOf(e.desc.ExtensionType) + props := extensionProperties(e.desc) + + // If e.value has type T, the encoder expects a *struct{ X T }. + // Pass a *T with a zero field and hope it all works out. + x := reflect.New(et) + x.Elem().Set(reflect.ValueOf(e.value)) + n += props.size(props, toStructPointer(x)) + } + return +} + +// HasExtension returns whether the given extension is present in pb. +func HasExtension(pb extendableProto, extension *ExtensionDesc) bool { + // TODO: Check types, field numbers, etc.? + _, ok := pb.ExtensionMap()[extension.Field] + return ok +} + +// ClearExtension removes the given extension from pb. +func ClearExtension(pb extendableProto, extension *ExtensionDesc) { + // TODO: Check types, field numbers, etc.? + delete(pb.ExtensionMap(), extension.Field) +} + +// GetExtension parses and returns the given extension of pb. +// If the extension is not present and has no default value it returns ErrMissingExtension. +func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) { + if err := checkExtensionTypes(pb, extension); err != nil { + return nil, err + } + + emap := pb.ExtensionMap() + e, ok := emap[extension.Field] + if !ok { + // defaultExtensionValue returns the default value or + // ErrMissingExtension if there is no default. + return defaultExtensionValue(extension) + } + + if e.value != nil { + // Already decoded. Check the descriptor, though. + if e.desc != extension { + // This shouldn't happen. If it does, it means that + // GetExtension was called twice with two different + // descriptors with the same field number. + return nil, errors.New("proto: descriptor conflict") + } + return e.value, nil + } + + v, err := decodeExtension(e.enc, extension) + if err != nil { + return nil, err + } + + // Remember the decoded version and drop the encoded version. + // That way it is safe to mutate what we return. + e.value = v + e.desc = extension + e.enc = nil + emap[extension.Field] = e + return e.value, nil +} + +// defaultExtensionValue returns the default value for extension. +// If no default for an extension is defined ErrMissingExtension is returned. +func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) { + t := reflect.TypeOf(extension.ExtensionType) + props := extensionProperties(extension) + + sf, _, err := fieldDefault(t, props) + if err != nil { + return nil, err + } + + if sf == nil || sf.value == nil { + // There is no default value. + return nil, ErrMissingExtension + } + + if t.Kind() != reflect.Ptr { + // We do not need to return a Ptr, we can directly return sf.value. + return sf.value, nil + } + + // We need to return an interface{} that is a pointer to sf.value. + value := reflect.New(t).Elem() + value.Set(reflect.New(value.Type().Elem())) + if sf.kind == reflect.Int32 { + // We may have an int32 or an enum, but the underlying data is int32. + // Since we can't set an int32 into a non int32 reflect.value directly + // set it as a int32. + value.Elem().SetInt(int64(sf.value.(int32))) + } else { + value.Elem().Set(reflect.ValueOf(sf.value)) + } + return value.Interface(), nil +} + +// decodeExtension decodes an extension encoded in b. +func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) { + o := NewBuffer(b) + + t := reflect.TypeOf(extension.ExtensionType) + rep := extension.repeated() + + props := extensionProperties(extension) + + // t is a pointer to a struct, pointer to basic type or a slice. + // Allocate a "field" to store the pointer/slice itself; the + // pointer/slice will be stored here. We pass + // the address of this field to props.dec. + // This passes a zero field and a *t and lets props.dec + // interpret it as a *struct{ x t }. + value := reflect.New(t).Elem() + + for { + // Discard wire type and field number varint. It isn't needed. + if _, err := o.DecodeVarint(); err != nil { + return nil, err + } + + if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil { + return nil, err + } + + if !rep || o.index >= len(o.buf) { + break + } + } + return value.Interface(), nil +} + +// GetExtensions returns a slice of the extensions present in pb that are also listed in es. +// The returned slice has the same length as es; missing extensions will appear as nil elements. +func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) { + epb, ok := pb.(extendableProto) + if !ok { + err = errors.New("proto: not an extendable proto") + return + } + extensions = make([]interface{}, len(es)) + for i, e := range es { + extensions[i], err = GetExtension(epb, e) + if err == ErrMissingExtension { + err = nil + } + if err != nil { + return + } + } + return +} + +// SetExtension sets the specified extension of pb to the specified value. +func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{}) error { + if err := checkExtensionTypes(pb, extension); err != nil { + return err + } + typ := reflect.TypeOf(extension.ExtensionType) + if typ != reflect.TypeOf(value) { + return errors.New("proto: bad extension value type") + } + // nil extension values need to be caught early, because the + // encoder can't distinguish an ErrNil due to a nil extension + // from an ErrNil due to a missing field. Extensions are + // always optional, so the encoder would just swallow the error + // and drop all the extensions from the encoded message. + if reflect.ValueOf(value).IsNil() { + return fmt.Errorf("proto: SetExtension called with nil value of type %T", value) + } + + pb.ExtensionMap()[extension.Field] = Extension{desc: extension, value: value} + return nil +} + +// A global registry of extensions. +// The generated code will register the generated descriptors by calling RegisterExtension. + +var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc) + +// RegisterExtension is called from the generated code. +func RegisterExtension(desc *ExtensionDesc) { + st := reflect.TypeOf(desc.ExtendedType).Elem() + m := extensionMaps[st] + if m == nil { + m = make(map[int32]*ExtensionDesc) + extensionMaps[st] = m + } + if _, ok := m[desc.Field]; ok { + panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field))) + } + m[desc.Field] = desc +} + +// RegisteredExtensions returns a map of the registered extensions of a +// protocol buffer struct, indexed by the extension number. +// The argument pb should be a nil pointer to the struct type. +func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc { + return extensionMaps[reflect.TypeOf(pb).Elem()] +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/extensions_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/extensions_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/extensions_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/extensions_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,292 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "fmt" + "reflect" + "testing" + + "github.com/golang/protobuf/proto" + pb "github.com/golang/protobuf/proto/testdata" +) + +func TestGetExtensionsWithMissingExtensions(t *testing.T) { + msg := &pb.MyMessage{} + ext1 := &pb.Ext{} + if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil { + t.Fatalf("Could not set ext1: %s", ext1) + } + exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{ + pb.E_Ext_More, + pb.E_Ext_Text, + }) + if err != nil { + t.Fatalf("GetExtensions() failed: %s", err) + } + if exts[0] != ext1 { + t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0]) + } + if exts[1] != nil { + t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1]) + } +} + +func TestGetExtensionStability(t *testing.T) { + check := func(m *pb.MyMessage) bool { + ext1, err := proto.GetExtension(m, pb.E_Ext_More) + if err != nil { + t.Fatalf("GetExtension() failed: %s", err) + } + ext2, err := proto.GetExtension(m, pb.E_Ext_More) + if err != nil { + t.Fatalf("GetExtension() failed: %s", err) + } + return ext1 == ext2 + } + msg := &pb.MyMessage{Count: proto.Int32(4)} + ext0 := &pb.Ext{} + if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil { + t.Fatalf("Could not set ext1: %s", ext0) + } + if !check(msg) { + t.Errorf("GetExtension() not stable before marshaling") + } + bb, err := proto.Marshal(msg) + if err != nil { + t.Fatalf("Marshal() failed: %s", err) + } + msg1 := &pb.MyMessage{} + err = proto.Unmarshal(bb, msg1) + if err != nil { + t.Fatalf("Unmarshal() failed: %s", err) + } + if !check(msg1) { + t.Errorf("GetExtension() not stable after unmarshaling") + } +} + +func TestGetExtensionDefaults(t *testing.T) { + var setFloat64 float64 = 1 + var setFloat32 float32 = 2 + var setInt32 int32 = 3 + var setInt64 int64 = 4 + var setUint32 uint32 = 5 + var setUint64 uint64 = 6 + var setBool = true + var setBool2 = false + var setString = "Goodnight string" + var setBytes = []byte("Goodnight bytes") + var setEnum = pb.DefaultsMessage_TWO + + type testcase struct { + ext *proto.ExtensionDesc // Extension we are testing. + want interface{} // Expected value of extension, or nil (meaning that GetExtension will fail). + def interface{} // Expected value of extension after ClearExtension(). + } + tests := []testcase{ + {pb.E_NoDefaultDouble, setFloat64, nil}, + {pb.E_NoDefaultFloat, setFloat32, nil}, + {pb.E_NoDefaultInt32, setInt32, nil}, + {pb.E_NoDefaultInt64, setInt64, nil}, + {pb.E_NoDefaultUint32, setUint32, nil}, + {pb.E_NoDefaultUint64, setUint64, nil}, + {pb.E_NoDefaultSint32, setInt32, nil}, + {pb.E_NoDefaultSint64, setInt64, nil}, + {pb.E_NoDefaultFixed32, setUint32, nil}, + {pb.E_NoDefaultFixed64, setUint64, nil}, + {pb.E_NoDefaultSfixed32, setInt32, nil}, + {pb.E_NoDefaultSfixed64, setInt64, nil}, + {pb.E_NoDefaultBool, setBool, nil}, + {pb.E_NoDefaultBool, setBool2, nil}, + {pb.E_NoDefaultString, setString, nil}, + {pb.E_NoDefaultBytes, setBytes, nil}, + {pb.E_NoDefaultEnum, setEnum, nil}, + {pb.E_DefaultDouble, setFloat64, float64(3.1415)}, + {pb.E_DefaultFloat, setFloat32, float32(3.14)}, + {pb.E_DefaultInt32, setInt32, int32(42)}, + {pb.E_DefaultInt64, setInt64, int64(43)}, + {pb.E_DefaultUint32, setUint32, uint32(44)}, + {pb.E_DefaultUint64, setUint64, uint64(45)}, + {pb.E_DefaultSint32, setInt32, int32(46)}, + {pb.E_DefaultSint64, setInt64, int64(47)}, + {pb.E_DefaultFixed32, setUint32, uint32(48)}, + {pb.E_DefaultFixed64, setUint64, uint64(49)}, + {pb.E_DefaultSfixed32, setInt32, int32(50)}, + {pb.E_DefaultSfixed64, setInt64, int64(51)}, + {pb.E_DefaultBool, setBool, true}, + {pb.E_DefaultBool, setBool2, true}, + {pb.E_DefaultString, setString, "Hello, string"}, + {pb.E_DefaultBytes, setBytes, []byte("Hello, bytes")}, + {pb.E_DefaultEnum, setEnum, pb.DefaultsMessage_ONE}, + } + + checkVal := func(test testcase, msg *pb.DefaultsMessage, valWant interface{}) error { + val, err := proto.GetExtension(msg, test.ext) + if err != nil { + if valWant != nil { + return fmt.Errorf("GetExtension(): %s", err) + } + if want := proto.ErrMissingExtension; err != want { + return fmt.Errorf("Unexpected error: got %v, want %v", err, want) + } + return nil + } + + // All proto2 extension values are either a pointer to a value or a slice of values. + ty := reflect.TypeOf(val) + tyWant := reflect.TypeOf(test.ext.ExtensionType) + if got, want := ty, tyWant; got != want { + return fmt.Errorf("unexpected reflect.TypeOf(): got %v want %v", got, want) + } + tye := ty.Elem() + tyeWant := tyWant.Elem() + if got, want := tye, tyeWant; got != want { + return fmt.Errorf("unexpected reflect.TypeOf().Elem(): got %v want %v", got, want) + } + + // Check the name of the type of the value. + // If it is an enum it will be type int32 with the name of the enum. + if got, want := tye.Name(), tye.Name(); got != want { + return fmt.Errorf("unexpected reflect.TypeOf().Elem().Name(): got %v want %v", got, want) + } + + // Check that value is what we expect. + // If we have a pointer in val, get the value it points to. + valExp := val + if ty.Kind() == reflect.Ptr { + valExp = reflect.ValueOf(val).Elem().Interface() + } + if got, want := valExp, valWant; !reflect.DeepEqual(got, want) { + return fmt.Errorf("unexpected reflect.DeepEqual(): got %v want %v", got, want) + } + + return nil + } + + setTo := func(test testcase) interface{} { + setTo := reflect.ValueOf(test.want) + if typ := reflect.TypeOf(test.ext.ExtensionType); typ.Kind() == reflect.Ptr { + setTo = reflect.New(typ).Elem() + setTo.Set(reflect.New(setTo.Type().Elem())) + setTo.Elem().Set(reflect.ValueOf(test.want)) + } + return setTo.Interface() + } + + for _, test := range tests { + msg := &pb.DefaultsMessage{} + name := test.ext.Name + + // Check the initial value. + if err := checkVal(test, msg, test.def); err != nil { + t.Errorf("%s: %v", name, err) + } + + // Set the per-type value and check value. + name = fmt.Sprintf("%s (set to %T %v)", name, test.want, test.want) + if err := proto.SetExtension(msg, test.ext, setTo(test)); err != nil { + t.Errorf("%s: SetExtension(): %v", name, err) + continue + } + if err := checkVal(test, msg, test.want); err != nil { + t.Errorf("%s: %v", name, err) + continue + } + + // Set and check the value. + name += " (cleared)" + proto.ClearExtension(msg, test.ext) + if err := checkVal(test, msg, test.def); err != nil { + t.Errorf("%s: %v", name, err) + } + } +} + +func TestExtensionsRoundTrip(t *testing.T) { + msg := &pb.MyMessage{} + ext1 := &pb.Ext{ + Data: proto.String("hi"), + } + ext2 := &pb.Ext{ + Data: proto.String("there"), + } + exists := proto.HasExtension(msg, pb.E_Ext_More) + if exists { + t.Error("Extension More present unexpectedly") + } + if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil { + t.Error(err) + } + if err := proto.SetExtension(msg, pb.E_Ext_More, ext2); err != nil { + t.Error(err) + } + e, err := proto.GetExtension(msg, pb.E_Ext_More) + if err != nil { + t.Error(err) + } + x, ok := e.(*pb.Ext) + if !ok { + t.Errorf("e has type %T, expected testdata.Ext", e) + } else if *x.Data != "there" { + t.Errorf("SetExtension failed to overwrite, got %+v, not 'there'", x) + } + proto.ClearExtension(msg, pb.E_Ext_More) + if _, err = proto.GetExtension(msg, pb.E_Ext_More); err != proto.ErrMissingExtension { + t.Errorf("got %v, expected ErrMissingExtension", e) + } + if _, err := proto.GetExtension(msg, pb.E_X215); err == nil { + t.Error("expected bad extension error, got nil") + } + if err := proto.SetExtension(msg, pb.E_X215, 12); err == nil { + t.Error("expected extension err") + } + if err := proto.SetExtension(msg, pb.E_Ext_More, 12); err == nil { + t.Error("expected some sort of type mismatch error, got nil") + } +} + +func TestNilExtension(t *testing.T) { + msg := &pb.MyMessage{ + Count: proto.Int32(1), + } + if err := proto.SetExtension(msg, pb.E_Ext_Text, proto.String("hello")); err != nil { + t.Fatal(err) + } + if err := proto.SetExtension(msg, pb.E_Ext_More, (*pb.Ext)(nil)); err == nil { + t.Error("expected SetExtension to fail due to a nil extension") + } else if want := "proto: SetExtension called with nil value of type *testdata.Ext"; err.Error() != want { + t.Errorf("expected error %v, got %v", want, err) + } + // Note: if the behavior of Marshal is ever changed to ignore nil extensions, update + // this test to verify that E_Ext_Text is properly propagated through marshal->unmarshal. +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/lib.go charm-2.2.0/src/github.com/golang/protobuf/proto/lib.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/lib.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/lib.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,796 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* +Package proto converts data structures to and from the wire format of +protocol buffers. It works in concert with the Go source code generated +for .proto files by the protocol compiler. + +A summary of the properties of the protocol buffer interface +for a protocol buffer variable v: + + - Names are turned from camel_case to CamelCase for export. + - There are no methods on v to set fields; just treat + them as structure fields. + - There are getters that return a field's value if set, + and return the field's default value if unset. + The getters work even if the receiver is a nil message. + - The zero value for a struct is its correct initialization state. + All desired fields must be set before marshaling. + - A Reset() method will restore a protobuf struct to its zero state. + - Non-repeated fields are pointers to the values; nil means unset. + That is, optional or required field int32 f becomes F *int32. + - Repeated fields are slices. + - Helper functions are available to aid the setting of fields. + msg.Foo = proto.String("hello") // set field + - Constants are defined to hold the default values of all fields that + have them. They have the form Default_StructName_FieldName. + Because the getter methods handle defaulted values, + direct use of these constants should be rare. + - Enums are given type names and maps from names to values. + Enum values are prefixed by the enclosing message's name, or by the + enum's type name if it is a top-level enum. Enum types have a String + method, and a Enum method to assist in message construction. + - Nested messages, groups and enums have type names prefixed with the name of + the surrounding message type. + - Extensions are given descriptor names that start with E_, + followed by an underscore-delimited list of the nested messages + that contain it (if any) followed by the CamelCased name of the + extension field itself. HasExtension, ClearExtension, GetExtension + and SetExtension are functions for manipulating extensions. + - Marshal and Unmarshal are functions to encode and decode the wire format. + +The simplest way to describe this is to see an example. +Given file test.proto, containing + + package example; + + enum FOO { X = 17; } + + message Test { + required string label = 1; + optional int32 type = 2 [default=77]; + repeated int64 reps = 3; + optional group OptionalGroup = 4 { + required string RequiredField = 5; + } + } + +The resulting file, test.pb.go, is: + + package example + + import proto "github.com/golang/protobuf/proto" + import math "math" + + type FOO int32 + const ( + FOO_X FOO = 17 + ) + var FOO_name = map[int32]string{ + 17: "X", + } + var FOO_value = map[string]int32{ + "X": 17, + } + + func (x FOO) Enum() *FOO { + p := new(FOO) + *p = x + return p + } + func (x FOO) String() string { + return proto.EnumName(FOO_name, int32(x)) + } + func (x *FOO) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FOO_value, data) + if err != nil { + return err + } + *x = FOO(value) + return nil + } + + type Test struct { + Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` + Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` + Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` + Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` + } + func (m *Test) Reset() { *m = Test{} } + func (m *Test) String() string { return proto.CompactTextString(m) } + func (*Test) ProtoMessage() {} + const Default_Test_Type int32 = 77 + + func (m *Test) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" + } + + func (m *Test) GetType() int32 { + if m != nil && m.Type != nil { + return *m.Type + } + return Default_Test_Type + } + + func (m *Test) GetOptionalgroup() *Test_OptionalGroup { + if m != nil { + return m.Optionalgroup + } + return nil + } + + type Test_OptionalGroup struct { + RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` + } + func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } + func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } + + func (m *Test_OptionalGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" + } + + func init() { + proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + } + +To create and play with a Test object: + +package main + + import ( + "log" + + "github.com/golang/protobuf/proto" + pb "./example.pb" + ) + + func main() { + test := &pb.Test{ + Label: proto.String("hello"), + Type: proto.Int32(17), + Optionalgroup: &pb.Test_OptionalGroup{ + RequiredField: proto.String("good bye"), + }, + } + data, err := proto.Marshal(test) + if err != nil { + log.Fatal("marshaling error: ", err) + } + newTest := &pb.Test{} + err = proto.Unmarshal(data, newTest) + if err != nil { + log.Fatal("unmarshaling error: ", err) + } + // Now test and newTest contain the same data. + if test.GetLabel() != newTest.GetLabel() { + log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) + } + // etc. + } +*/ +package proto + +import ( + "encoding/json" + "fmt" + "log" + "reflect" + "strconv" + "sync" +) + +// Message is implemented by generated protocol buffer messages. +type Message interface { + Reset() + String() string + ProtoMessage() +} + +// Stats records allocation details about the protocol buffer encoders +// and decoders. Useful for tuning the library itself. +type Stats struct { + Emalloc uint64 // mallocs in encode + Dmalloc uint64 // mallocs in decode + Encode uint64 // number of encodes + Decode uint64 // number of decodes + Chit uint64 // number of cache hits + Cmiss uint64 // number of cache misses + Size uint64 // number of sizes +} + +// Set to true to enable stats collection. +const collectStats = false + +var stats Stats + +// GetStats returns a copy of the global Stats structure. +func GetStats() Stats { return stats } + +// A Buffer is a buffer manager for marshaling and unmarshaling +// protocol buffers. It may be reused between invocations to +// reduce memory usage. It is not necessary to use a Buffer; +// the global functions Marshal and Unmarshal create a +// temporary Buffer and are fine for most applications. +type Buffer struct { + buf []byte // encode/decode byte stream + index int // write point + + // pools of basic types to amortize allocation. + bools []bool + uint32s []uint32 + uint64s []uint64 + + // extra pools, only used with pointer_reflect.go + int32s []int32 + int64s []int64 + float32s []float32 + float64s []float64 +} + +// NewBuffer allocates a new Buffer and initializes its internal data to +// the contents of the argument slice. +func NewBuffer(e []byte) *Buffer { + return &Buffer{buf: e} +} + +// Reset resets the Buffer, ready for marshaling a new protocol buffer. +func (p *Buffer) Reset() { + p.buf = p.buf[0:0] // for reading/writing + p.index = 0 // for reading +} + +// SetBuf replaces the internal buffer with the slice, +// ready for unmarshaling the contents of the slice. +func (p *Buffer) SetBuf(s []byte) { + p.buf = s + p.index = 0 +} + +// Bytes returns the contents of the Buffer. +func (p *Buffer) Bytes() []byte { return p.buf } + +/* + * Helper routines for simplifying the creation of optional fields of basic type. + */ + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { + return &v +} + +// Int32 is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it. +func Int32(v int32) *int32 { + return &v +} + +// Int is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it, but unlike Int32 +// its argument value is an int. +func Int(v int) *int32 { + p := new(int32) + *p = int32(v) + return p +} + +// Int64 is a helper routine that allocates a new int64 value +// to store v and returns a pointer to it. +func Int64(v int64) *int64 { + return &v +} + +// Float32 is a helper routine that allocates a new float32 value +// to store v and returns a pointer to it. +func Float32(v float32) *float32 { + return &v +} + +// Float64 is a helper routine that allocates a new float64 value +// to store v and returns a pointer to it. +func Float64(v float64) *float64 { + return &v +} + +// Uint32 is a helper routine that allocates a new uint32 value +// to store v and returns a pointer to it. +func Uint32(v uint32) *uint32 { + return &v +} + +// Uint64 is a helper routine that allocates a new uint64 value +// to store v and returns a pointer to it. +func Uint64(v uint64) *uint64 { + return &v +} + +// String is a helper routine that allocates a new string value +// to store v and returns a pointer to it. +func String(v string) *string { + return &v +} + +// EnumName is a helper function to simplify printing protocol buffer enums +// by name. Given an enum map and a value, it returns a useful string. +func EnumName(m map[int32]string, v int32) string { + s, ok := m[v] + if ok { + return s + } + return strconv.Itoa(int(v)) +} + +// UnmarshalJSONEnum is a helper function to simplify recovering enum int values +// from their JSON-encoded representation. Given a map from the enum's symbolic +// names to its int values, and a byte buffer containing the JSON-encoded +// value, it returns an int32 that can be cast to the enum type by the caller. +// +// The function can deal with both JSON representations, numeric and symbolic. +func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) { + if data[0] == '"' { + // New style: enums are strings. + var repr string + if err := json.Unmarshal(data, &repr); err != nil { + return -1, err + } + val, ok := m[repr] + if !ok { + return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr) + } + return val, nil + } + // Old style: enums are ints. + var val int32 + if err := json.Unmarshal(data, &val); err != nil { + return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName) + } + return val, nil +} + +// DebugPrint dumps the encoded data in b in a debugging format with a header +// including the string s. Used in testing but made available for general debugging. +func (p *Buffer) DebugPrint(s string, b []byte) { + var u uint64 + + obuf := p.buf + index := p.index + p.buf = b + p.index = 0 + depth := 0 + + fmt.Printf("\n--- %s ---\n", s) + +out: + for { + for i := 0; i < depth; i++ { + fmt.Print(" ") + } + + index := p.index + if index == len(p.buf) { + break + } + + op, err := p.DecodeVarint() + if err != nil { + fmt.Printf("%3d: fetching op err %v\n", index, err) + break out + } + tag := op >> 3 + wire := op & 7 + + switch wire { + default: + fmt.Printf("%3d: t=%3d unknown wire=%d\n", + index, tag, wire) + break out + + case WireBytes: + var r []byte + + r, err = p.DecodeRawBytes(false) + if err != nil { + break out + } + fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r)) + if len(r) <= 6 { + for i := 0; i < len(r); i++ { + fmt.Printf(" %.2x", r[i]) + } + } else { + for i := 0; i < 3; i++ { + fmt.Printf(" %.2x", r[i]) + } + fmt.Printf(" ..") + for i := len(r) - 3; i < len(r); i++ { + fmt.Printf(" %.2x", r[i]) + } + } + fmt.Printf("\n") + + case WireFixed32: + u, err = p.DecodeFixed32() + if err != nil { + fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u) + + case WireFixed64: + u, err = p.DecodeFixed64() + if err != nil { + fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u) + break + + case WireVarint: + u, err = p.DecodeVarint() + if err != nil { + fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u) + + case WireStartGroup: + if err != nil { + fmt.Printf("%3d: t=%3d start err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d start\n", index, tag) + depth++ + + case WireEndGroup: + depth-- + if err != nil { + fmt.Printf("%3d: t=%3d end err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d end\n", index, tag) + } + } + + if depth != 0 { + fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth) + } + fmt.Printf("\n") + + p.buf = obuf + p.index = index +} + +// SetDefaults sets unset protocol buffer fields to their default values. +// It only modifies fields that are both unset and have defined defaults. +// It recursively sets default values in any non-nil sub-messages. +func SetDefaults(pb Message) { + setDefaults(reflect.ValueOf(pb), true, false) +} + +// v is a pointer to a struct. +func setDefaults(v reflect.Value, recur, zeros bool) { + v = v.Elem() + + defaultMu.RLock() + dm, ok := defaults[v.Type()] + defaultMu.RUnlock() + if !ok { + dm = buildDefaultMessage(v.Type()) + defaultMu.Lock() + defaults[v.Type()] = dm + defaultMu.Unlock() + } + + for _, sf := range dm.scalars { + f := v.Field(sf.index) + if !f.IsNil() { + // field already set + continue + } + dv := sf.value + if dv == nil && !zeros { + // no explicit default, and don't want to set zeros + continue + } + fptr := f.Addr().Interface() // **T + // TODO: Consider batching the allocations we do here. + switch sf.kind { + case reflect.Bool: + b := new(bool) + if dv != nil { + *b = dv.(bool) + } + *(fptr.(**bool)) = b + case reflect.Float32: + f := new(float32) + if dv != nil { + *f = dv.(float32) + } + *(fptr.(**float32)) = f + case reflect.Float64: + f := new(float64) + if dv != nil { + *f = dv.(float64) + } + *(fptr.(**float64)) = f + case reflect.Int32: + // might be an enum + if ft := f.Type(); ft != int32PtrType { + // enum + f.Set(reflect.New(ft.Elem())) + if dv != nil { + f.Elem().SetInt(int64(dv.(int32))) + } + } else { + // int32 field + i := new(int32) + if dv != nil { + *i = dv.(int32) + } + *(fptr.(**int32)) = i + } + case reflect.Int64: + i := new(int64) + if dv != nil { + *i = dv.(int64) + } + *(fptr.(**int64)) = i + case reflect.String: + s := new(string) + if dv != nil { + *s = dv.(string) + } + *(fptr.(**string)) = s + case reflect.Uint8: + // exceptional case: []byte + var b []byte + if dv != nil { + db := dv.([]byte) + b = make([]byte, len(db)) + copy(b, db) + } else { + b = []byte{} + } + *(fptr.(*[]byte)) = b + case reflect.Uint32: + u := new(uint32) + if dv != nil { + *u = dv.(uint32) + } + *(fptr.(**uint32)) = u + case reflect.Uint64: + u := new(uint64) + if dv != nil { + *u = dv.(uint64) + } + *(fptr.(**uint64)) = u + default: + log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind) + } + } + + for _, ni := range dm.nested { + f := v.Field(ni) + // f is *T or []*T or map[T]*T + switch f.Kind() { + case reflect.Ptr: + if f.IsNil() { + continue + } + setDefaults(f, recur, zeros) + + case reflect.Slice: + for i := 0; i < f.Len(); i++ { + e := f.Index(i) + if e.IsNil() { + continue + } + setDefaults(e, recur, zeros) + } + + case reflect.Map: + for _, k := range f.MapKeys() { + e := f.MapIndex(k) + if e.IsNil() { + continue + } + setDefaults(e, recur, zeros) + } + } + } +} + +var ( + // defaults maps a protocol buffer struct type to a slice of the fields, + // with its scalar fields set to their proto-declared non-zero default values. + defaultMu sync.RWMutex + defaults = make(map[reflect.Type]defaultMessage) + + int32PtrType = reflect.TypeOf((*int32)(nil)) +) + +// defaultMessage represents information about the default values of a message. +type defaultMessage struct { + scalars []scalarField + nested []int // struct field index of nested messages +} + +type scalarField struct { + index int // struct field index + kind reflect.Kind // element type (the T in *T or []T) + value interface{} // the proto-declared default value, or nil +} + +// t is a struct type. +func buildDefaultMessage(t reflect.Type) (dm defaultMessage) { + sprop := GetProperties(t) + for _, prop := range sprop.Prop { + fi, ok := sprop.decoderTags.get(prop.Tag) + if !ok { + // XXX_unrecognized + continue + } + ft := t.Field(fi).Type + + sf, nested, err := fieldDefault(ft, prop) + switch { + case err != nil: + log.Print(err) + case nested: + dm.nested = append(dm.nested, fi) + case sf != nil: + sf.index = fi + dm.scalars = append(dm.scalars, *sf) + } + } + + return dm +} + +// fieldDefault returns the scalarField for field type ft. +// sf will be nil if the field can not have a default. +// nestedMessage will be true if this is a nested message. +// Note that sf.index is not set on return. +func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) { + var canHaveDefault bool + switch ft.Kind() { + case reflect.Ptr: + if ft.Elem().Kind() == reflect.Struct { + nestedMessage = true + } else { + canHaveDefault = true // proto2 scalar field + } + + case reflect.Slice: + switch ft.Elem().Kind() { + case reflect.Ptr: + nestedMessage = true // repeated message + case reflect.Uint8: + canHaveDefault = true // bytes field + } + + case reflect.Map: + if ft.Elem().Kind() == reflect.Ptr { + nestedMessage = true // map with message values + } + } + + if !canHaveDefault { + if nestedMessage { + return nil, true, nil + } + return nil, false, nil + } + + // We now know that ft is a pointer or slice. + sf = &scalarField{kind: ft.Elem().Kind()} + + // scalar fields without defaults + if !prop.HasDefault { + return sf, false, nil + } + + // a scalar field: either *T or []byte + switch ft.Elem().Kind() { + case reflect.Bool: + x, err := strconv.ParseBool(prop.Default) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err) + } + sf.value = x + case reflect.Float32: + x, err := strconv.ParseFloat(prop.Default, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err) + } + sf.value = float32(x) + case reflect.Float64: + x, err := strconv.ParseFloat(prop.Default, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err) + } + sf.value = x + case reflect.Int32: + x, err := strconv.ParseInt(prop.Default, 10, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err) + } + sf.value = int32(x) + case reflect.Int64: + x, err := strconv.ParseInt(prop.Default, 10, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err) + } + sf.value = x + case reflect.String: + sf.value = prop.Default + case reflect.Uint8: + // []byte (not *uint8) + sf.value = []byte(prop.Default) + case reflect.Uint32: + x, err := strconv.ParseUint(prop.Default, 10, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err) + } + sf.value = uint32(x) + case reflect.Uint64: + x, err := strconv.ParseUint(prop.Default, 10, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err) + } + sf.value = x + default: + return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind()) + } + + return sf, false, nil +} + +// Map fields may have key types of non-float scalars, strings and enums. +// The easiest way to sort them in some deterministic order is to use fmt. +// If this turns out to be inefficient we can always consider other options, +// such as doing a Schwartzian transform. + +type mapKeys []reflect.Value + +func (s mapKeys) Len() int { return len(s) } +func (s mapKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s mapKeys) Less(i, j int) bool { + return fmt.Sprint(s[i].Interface()) < fmt.Sprint(s[j].Interface()) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/Makefile charm-2.2.0/src/github.com/golang/protobuf/proto/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/proto/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,43 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +install: + go install + +test: install generate-test-pbs + go test + + +generate-test-pbs: + make install + make -C testdata + protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata:. proto3_proto/proto3.proto + make diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/message_set.go charm-2.2.0/src/github.com/golang/protobuf/proto/message_set.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/message_set.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/message_set.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,287 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Support for message sets. + */ + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "reflect" + "sort" +) + +// ErrNoMessageTypeId occurs when a protocol buffer does not have a message type ID. +// A message type ID is required for storing a protocol buffer in a message set. +var ErrNoMessageTypeId = errors.New("proto does not have a message type ID") + +// The first two types (_MessageSet_Item and MessageSet) +// model what the protocol compiler produces for the following protocol message: +// message MessageSet { +// repeated group Item = 1 { +// required int32 type_id = 2; +// required string message = 3; +// }; +// } +// That is the MessageSet wire format. We can't use a proto to generate these +// because that would introduce a circular dependency between it and this package. +// +// When a proto1 proto has a field that looks like: +// optional message info = 3; +// the protocol compiler produces a field in the generated struct that looks like: +// Info *_proto_.MessageSet `protobuf:"bytes,3,opt,name=info"` +// The package is automatically inserted so there is no need for that proto file to +// import this package. + +type _MessageSet_Item struct { + TypeId *int32 `protobuf:"varint,2,req,name=type_id"` + Message []byte `protobuf:"bytes,3,req,name=message"` +} + +type MessageSet struct { + Item []*_MessageSet_Item `protobuf:"group,1,rep"` + XXX_unrecognized []byte + // TODO: caching? +} + +// Make sure MessageSet is a Message. +var _ Message = (*MessageSet)(nil) + +// messageTypeIder is an interface satisfied by a protocol buffer type +// that may be stored in a MessageSet. +type messageTypeIder interface { + MessageTypeId() int32 +} + +func (ms *MessageSet) find(pb Message) *_MessageSet_Item { + mti, ok := pb.(messageTypeIder) + if !ok { + return nil + } + id := mti.MessageTypeId() + for _, item := range ms.Item { + if *item.TypeId == id { + return item + } + } + return nil +} + +func (ms *MessageSet) Has(pb Message) bool { + if ms.find(pb) != nil { + return true + } + return false +} + +func (ms *MessageSet) Unmarshal(pb Message) error { + if item := ms.find(pb); item != nil { + return Unmarshal(item.Message, pb) + } + if _, ok := pb.(messageTypeIder); !ok { + return ErrNoMessageTypeId + } + return nil // TODO: return error instead? +} + +func (ms *MessageSet) Marshal(pb Message) error { + msg, err := Marshal(pb) + if err != nil { + return err + } + if item := ms.find(pb); item != nil { + // reuse existing item + item.Message = msg + return nil + } + + mti, ok := pb.(messageTypeIder) + if !ok { + return ErrNoMessageTypeId + } + + mtid := mti.MessageTypeId() + ms.Item = append(ms.Item, &_MessageSet_Item{ + TypeId: &mtid, + Message: msg, + }) + return nil +} + +func (ms *MessageSet) Reset() { *ms = MessageSet{} } +func (ms *MessageSet) String() string { return CompactTextString(ms) } +func (*MessageSet) ProtoMessage() {} + +// Support for the message_set_wire_format message option. + +func skipVarint(buf []byte) []byte { + i := 0 + for ; buf[i]&0x80 != 0; i++ { + } + return buf[i+1:] +} + +// MarshalMessageSet encodes the extension map represented by m in the message set wire format. +// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option. +func MarshalMessageSet(m map[int32]Extension) ([]byte, error) { + if err := encodeExtensionMap(m); err != nil { + return nil, err + } + + // Sort extension IDs to provide a deterministic encoding. + // See also enc_map in encode.go. + ids := make([]int, 0, len(m)) + for id := range m { + ids = append(ids, int(id)) + } + sort.Ints(ids) + + ms := &MessageSet{Item: make([]*_MessageSet_Item, 0, len(m))} + for _, id := range ids { + e := m[int32(id)] + // Remove the wire type and field number varint, as well as the length varint. + msg := skipVarint(skipVarint(e.enc)) + + ms.Item = append(ms.Item, &_MessageSet_Item{ + TypeId: Int32(int32(id)), + Message: msg, + }) + } + return Marshal(ms) +} + +// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format. +// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option. +func UnmarshalMessageSet(buf []byte, m map[int32]Extension) error { + ms := new(MessageSet) + if err := Unmarshal(buf, ms); err != nil { + return err + } + for _, item := range ms.Item { + id := *item.TypeId + msg := item.Message + + // Restore wire type and field number varint, plus length varint. + // Be careful to preserve duplicate items. + b := EncodeVarint(uint64(id)<<3 | WireBytes) + if ext, ok := m[id]; ok { + // Existing data; rip off the tag and length varint + // so we join the new data correctly. + // We can assume that ext.enc is set because we are unmarshaling. + o := ext.enc[len(b):] // skip wire type and field number + _, n := DecodeVarint(o) // calculate length of length varint + o = o[n:] // skip length varint + msg = append(o, msg...) // join old data and new data + } + b = append(b, EncodeVarint(uint64(len(msg)))...) + b = append(b, msg...) + + m[id] = Extension{enc: b} + } + return nil +} + +// MarshalMessageSetJSON encodes the extension map represented by m in JSON format. +// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option. +func MarshalMessageSetJSON(m map[int32]Extension) ([]byte, error) { + var b bytes.Buffer + b.WriteByte('{') + + // Process the map in key order for deterministic output. + ids := make([]int32, 0, len(m)) + for id := range m { + ids = append(ids, id) + } + sort.Sort(int32Slice(ids)) // int32Slice defined in text.go + + for i, id := range ids { + ext := m[id] + if i > 0 { + b.WriteByte(',') + } + + msd, ok := messageSetMap[id] + if !ok { + // Unknown type; we can't render it, so skip it. + continue + } + fmt.Fprintf(&b, `"[%s]":`, msd.name) + + x := ext.value + if x == nil { + x = reflect.New(msd.t.Elem()).Interface() + if err := Unmarshal(ext.enc, x.(Message)); err != nil { + return nil, err + } + } + d, err := json.Marshal(x) + if err != nil { + return nil, err + } + b.Write(d) + } + b.WriteByte('}') + return b.Bytes(), nil +} + +// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format. +// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option. +func UnmarshalMessageSetJSON(buf []byte, m map[int32]Extension) error { + // Common-case fast path. + if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) { + return nil + } + + // This is fairly tricky, and it's not clear that it is needed. + return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented") +} + +// A global registry of types that can be used in a MessageSet. + +var messageSetMap = make(map[int32]messageSetDesc) + +type messageSetDesc struct { + t reflect.Type // pointer to struct + name string +} + +// RegisterMessageSetType is called from the generated code. +func RegisterMessageSetType(m Message, fieldNum int32, name string) { + messageSetMap[fieldNum] = messageSetDesc{ + t: reflect.TypeOf(m), + name: name, + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/message_set_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/message_set_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/message_set_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/message_set_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,66 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +import ( + "bytes" + "testing" +) + +func TestUnmarshalMessageSetWithDuplicate(t *testing.T) { + // Check that a repeated message set entry will be concatenated. + in := &MessageSet{ + Item: []*_MessageSet_Item{ + {TypeId: Int32(12345), Message: []byte("hoo")}, + {TypeId: Int32(12345), Message: []byte("hah")}, + }, + } + b, err := Marshal(in) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + t.Logf("Marshaled bytes: %q", b) + + m := make(map[int32]Extension) + if err := UnmarshalMessageSet(b, m); err != nil { + t.Fatalf("UnmarshalMessageSet: %v", err) + } + ext, ok := m[12345] + if !ok { + t.Fatalf("Didn't retrieve extension 12345; map is %v", m) + } + // Skip wire type/field number and length varints. + got := skipVarint(skipVarint(ext.enc)) + if want := []byte("hoohah"); !bytes.Equal(got, want) { + t.Errorf("Combined extension is %q, want %q", got, want) + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/pointer_reflect.go charm-2.2.0/src/github.com/golang/protobuf/proto/pointer_reflect.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/pointer_reflect.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/pointer_reflect.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,479 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +build appengine + +// This file contains an implementation of proto field accesses using package reflect. +// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can +// be used on App Engine. + +package proto + +import ( + "math" + "reflect" +) + +// A structPointer is a pointer to a struct. +type structPointer struct { + v reflect.Value +} + +// toStructPointer returns a structPointer equivalent to the given reflect value. +// The reflect value must itself be a pointer to a struct. +func toStructPointer(v reflect.Value) structPointer { + return structPointer{v} +} + +// IsNil reports whether p is nil. +func structPointer_IsNil(p structPointer) bool { + return p.v.IsNil() +} + +// Interface returns the struct pointer as an interface value. +func structPointer_Interface(p structPointer, _ reflect.Type) interface{} { + return p.v.Interface() +} + +// A field identifies a field in a struct, accessible from a structPointer. +// In this implementation, a field is identified by the sequence of field indices +// passed to reflect's FieldByIndex. +type field []int + +// toField returns a field equivalent to the given reflect field. +func toField(f *reflect.StructField) field { + return f.Index +} + +// invalidField is an invalid field identifier. +var invalidField = field(nil) + +// IsValid reports whether the field identifier is valid. +func (f field) IsValid() bool { return f != nil } + +// field returns the given field in the struct as a reflect value. +func structPointer_field(p structPointer, f field) reflect.Value { + // Special case: an extension map entry with a value of type T + // passes a *T to the struct-handling code with a zero field, + // expecting that it will be treated as equivalent to *struct{ X T }, + // which has the same memory layout. We have to handle that case + // specially, because reflect will panic if we call FieldByIndex on a + // non-struct. + if f == nil { + return p.v.Elem() + } + + return p.v.Elem().FieldByIndex(f) +} + +// ifield returns the given field in the struct as an interface value. +func structPointer_ifield(p structPointer, f field) interface{} { + return structPointer_field(p, f).Addr().Interface() +} + +// Bytes returns the address of a []byte field in the struct. +func structPointer_Bytes(p structPointer, f field) *[]byte { + return structPointer_ifield(p, f).(*[]byte) +} + +// BytesSlice returns the address of a [][]byte field in the struct. +func structPointer_BytesSlice(p structPointer, f field) *[][]byte { + return structPointer_ifield(p, f).(*[][]byte) +} + +// Bool returns the address of a *bool field in the struct. +func structPointer_Bool(p structPointer, f field) **bool { + return structPointer_ifield(p, f).(**bool) +} + +// BoolVal returns the address of a bool field in the struct. +func structPointer_BoolVal(p structPointer, f field) *bool { + return structPointer_ifield(p, f).(*bool) +} + +// BoolSlice returns the address of a []bool field in the struct. +func structPointer_BoolSlice(p structPointer, f field) *[]bool { + return structPointer_ifield(p, f).(*[]bool) +} + +// String returns the address of a *string field in the struct. +func structPointer_String(p structPointer, f field) **string { + return structPointer_ifield(p, f).(**string) +} + +// StringVal returns the address of a string field in the struct. +func structPointer_StringVal(p structPointer, f field) *string { + return structPointer_ifield(p, f).(*string) +} + +// StringSlice returns the address of a []string field in the struct. +func structPointer_StringSlice(p structPointer, f field) *[]string { + return structPointer_ifield(p, f).(*[]string) +} + +// ExtMap returns the address of an extension map field in the struct. +func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { + return structPointer_ifield(p, f).(*map[int32]Extension) +} + +// Map returns the reflect.Value for the address of a map field in the struct. +func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value { + return structPointer_field(p, f).Addr() +} + +// SetStructPointer writes a *struct field in the struct. +func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { + structPointer_field(p, f).Set(q.v) +} + +// GetStructPointer reads a *struct field in the struct. +func structPointer_GetStructPointer(p structPointer, f field) structPointer { + return structPointer{structPointer_field(p, f)} +} + +// StructPointerSlice the address of a []*struct field in the struct. +func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice { + return structPointerSlice{structPointer_field(p, f)} +} + +// A structPointerSlice represents the address of a slice of pointers to structs +// (themselves messages or groups). That is, v.Type() is *[]*struct{...}. +type structPointerSlice struct { + v reflect.Value +} + +func (p structPointerSlice) Len() int { return p.v.Len() } +func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} } +func (p structPointerSlice) Append(q structPointer) { + p.v.Set(reflect.Append(p.v, q.v)) +} + +var ( + int32Type = reflect.TypeOf(int32(0)) + uint32Type = reflect.TypeOf(uint32(0)) + float32Type = reflect.TypeOf(float32(0)) + int64Type = reflect.TypeOf(int64(0)) + uint64Type = reflect.TypeOf(uint64(0)) + float64Type = reflect.TypeOf(float64(0)) +) + +// A word32 represents a field of type *int32, *uint32, *float32, or *enum. +// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable. +type word32 struct { + v reflect.Value +} + +// IsNil reports whether p is nil. +func word32_IsNil(p word32) bool { + return p.v.IsNil() +} + +// Set sets p to point at a newly allocated word with bits set to x. +func word32_Set(p word32, o *Buffer, x uint32) { + t := p.v.Type().Elem() + switch t { + case int32Type: + if len(o.int32s) == 0 { + o.int32s = make([]int32, uint32PoolSize) + } + o.int32s[0] = int32(x) + p.v.Set(reflect.ValueOf(&o.int32s[0])) + o.int32s = o.int32s[1:] + return + case uint32Type: + if len(o.uint32s) == 0 { + o.uint32s = make([]uint32, uint32PoolSize) + } + o.uint32s[0] = x + p.v.Set(reflect.ValueOf(&o.uint32s[0])) + o.uint32s = o.uint32s[1:] + return + case float32Type: + if len(o.float32s) == 0 { + o.float32s = make([]float32, uint32PoolSize) + } + o.float32s[0] = math.Float32frombits(x) + p.v.Set(reflect.ValueOf(&o.float32s[0])) + o.float32s = o.float32s[1:] + return + } + + // must be enum + p.v.Set(reflect.New(t)) + p.v.Elem().SetInt(int64(int32(x))) +} + +// Get gets the bits pointed at by p, as a uint32. +func word32_Get(p word32) uint32 { + elem := p.v.Elem() + switch elem.Kind() { + case reflect.Int32: + return uint32(elem.Int()) + case reflect.Uint32: + return uint32(elem.Uint()) + case reflect.Float32: + return math.Float32bits(float32(elem.Float())) + } + panic("unreachable") +} + +// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct. +func structPointer_Word32(p structPointer, f field) word32 { + return word32{structPointer_field(p, f)} +} + +// A word32Val represents a field of type int32, uint32, float32, or enum. +// That is, v.Type() is int32, uint32, float32, or enum and v is assignable. +type word32Val struct { + v reflect.Value +} + +// Set sets *p to x. +func word32Val_Set(p word32Val, x uint32) { + switch p.v.Type() { + case int32Type: + p.v.SetInt(int64(x)) + return + case uint32Type: + p.v.SetUint(uint64(x)) + return + case float32Type: + p.v.SetFloat(float64(math.Float32frombits(x))) + return + } + + // must be enum + p.v.SetInt(int64(int32(x))) +} + +// Get gets the bits pointed at by p, as a uint32. +func word32Val_Get(p word32Val) uint32 { + elem := p.v + switch elem.Kind() { + case reflect.Int32: + return uint32(elem.Int()) + case reflect.Uint32: + return uint32(elem.Uint()) + case reflect.Float32: + return math.Float32bits(float32(elem.Float())) + } + panic("unreachable") +} + +// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct. +func structPointer_Word32Val(p structPointer, f field) word32Val { + return word32Val{structPointer_field(p, f)} +} + +// A word32Slice is a slice of 32-bit values. +// That is, v.Type() is []int32, []uint32, []float32, or []enum. +type word32Slice struct { + v reflect.Value +} + +func (p word32Slice) Append(x uint32) { + n, m := p.v.Len(), p.v.Cap() + if n < m { + p.v.SetLen(n + 1) + } else { + t := p.v.Type().Elem() + p.v.Set(reflect.Append(p.v, reflect.Zero(t))) + } + elem := p.v.Index(n) + switch elem.Kind() { + case reflect.Int32: + elem.SetInt(int64(int32(x))) + case reflect.Uint32: + elem.SetUint(uint64(x)) + case reflect.Float32: + elem.SetFloat(float64(math.Float32frombits(x))) + } +} + +func (p word32Slice) Len() int { + return p.v.Len() +} + +func (p word32Slice) Index(i int) uint32 { + elem := p.v.Index(i) + switch elem.Kind() { + case reflect.Int32: + return uint32(elem.Int()) + case reflect.Uint32: + return uint32(elem.Uint()) + case reflect.Float32: + return math.Float32bits(float32(elem.Float())) + } + panic("unreachable") +} + +// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct. +func structPointer_Word32Slice(p structPointer, f field) word32Slice { + return word32Slice{structPointer_field(p, f)} +} + +// word64 is like word32 but for 64-bit values. +type word64 struct { + v reflect.Value +} + +func word64_Set(p word64, o *Buffer, x uint64) { + t := p.v.Type().Elem() + switch t { + case int64Type: + if len(o.int64s) == 0 { + o.int64s = make([]int64, uint64PoolSize) + } + o.int64s[0] = int64(x) + p.v.Set(reflect.ValueOf(&o.int64s[0])) + o.int64s = o.int64s[1:] + return + case uint64Type: + if len(o.uint64s) == 0 { + o.uint64s = make([]uint64, uint64PoolSize) + } + o.uint64s[0] = x + p.v.Set(reflect.ValueOf(&o.uint64s[0])) + o.uint64s = o.uint64s[1:] + return + case float64Type: + if len(o.float64s) == 0 { + o.float64s = make([]float64, uint64PoolSize) + } + o.float64s[0] = math.Float64frombits(x) + p.v.Set(reflect.ValueOf(&o.float64s[0])) + o.float64s = o.float64s[1:] + return + } + panic("unreachable") +} + +func word64_IsNil(p word64) bool { + return p.v.IsNil() +} + +func word64_Get(p word64) uint64 { + elem := p.v.Elem() + switch elem.Kind() { + case reflect.Int64: + return uint64(elem.Int()) + case reflect.Uint64: + return elem.Uint() + case reflect.Float64: + return math.Float64bits(elem.Float()) + } + panic("unreachable") +} + +func structPointer_Word64(p structPointer, f field) word64 { + return word64{structPointer_field(p, f)} +} + +// word64Val is like word32Val but for 64-bit values. +type word64Val struct { + v reflect.Value +} + +func word64Val_Set(p word64Val, o *Buffer, x uint64) { + switch p.v.Type() { + case int64Type: + p.v.SetInt(int64(x)) + return + case uint64Type: + p.v.SetUint(x) + return + case float64Type: + p.v.SetFloat(math.Float64frombits(x)) + return + } + panic("unreachable") +} + +func word64Val_Get(p word64Val) uint64 { + elem := p.v + switch elem.Kind() { + case reflect.Int64: + return uint64(elem.Int()) + case reflect.Uint64: + return elem.Uint() + case reflect.Float64: + return math.Float64bits(elem.Float()) + } + panic("unreachable") +} + +func structPointer_Word64Val(p structPointer, f field) word64Val { + return word64Val{structPointer_field(p, f)} +} + +type word64Slice struct { + v reflect.Value +} + +func (p word64Slice) Append(x uint64) { + n, m := p.v.Len(), p.v.Cap() + if n < m { + p.v.SetLen(n + 1) + } else { + t := p.v.Type().Elem() + p.v.Set(reflect.Append(p.v, reflect.Zero(t))) + } + elem := p.v.Index(n) + switch elem.Kind() { + case reflect.Int64: + elem.SetInt(int64(int64(x))) + case reflect.Uint64: + elem.SetUint(uint64(x)) + case reflect.Float64: + elem.SetFloat(float64(math.Float64frombits(x))) + } +} + +func (p word64Slice) Len() int { + return p.v.Len() +} + +func (p word64Slice) Index(i int) uint64 { + elem := p.v.Index(i) + switch elem.Kind() { + case reflect.Int64: + return uint64(elem.Int()) + case reflect.Uint64: + return uint64(elem.Uint()) + case reflect.Float64: + return math.Float64bits(float64(elem.Float())) + } + panic("unreachable") +} + +func structPointer_Word64Slice(p structPointer, f field) word64Slice { + return word64Slice{structPointer_field(p, f)} +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/pointer_unsafe.go charm-2.2.0/src/github.com/golang/protobuf/proto/pointer_unsafe.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/pointer_unsafe.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/pointer_unsafe.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,266 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +build !appengine + +// This file contains the implementation of the proto field accesses using package unsafe. + +package proto + +import ( + "reflect" + "unsafe" +) + +// NOTE: These type_Foo functions would more idiomatically be methods, +// but Go does not allow methods on pointer types, and we must preserve +// some pointer type for the garbage collector. We use these +// funcs with clunky names as our poor approximation to methods. +// +// An alternative would be +// type structPointer struct { p unsafe.Pointer } +// but that does not registerize as well. + +// A structPointer is a pointer to a struct. +type structPointer unsafe.Pointer + +// toStructPointer returns a structPointer equivalent to the given reflect value. +func toStructPointer(v reflect.Value) structPointer { + return structPointer(unsafe.Pointer(v.Pointer())) +} + +// IsNil reports whether p is nil. +func structPointer_IsNil(p structPointer) bool { + return p == nil +} + +// Interface returns the struct pointer, assumed to have element type t, +// as an interface value. +func structPointer_Interface(p structPointer, t reflect.Type) interface{} { + return reflect.NewAt(t, unsafe.Pointer(p)).Interface() +} + +// A field identifies a field in a struct, accessible from a structPointer. +// In this implementation, a field is identified by its byte offset from the start of the struct. +type field uintptr + +// toField returns a field equivalent to the given reflect field. +func toField(f *reflect.StructField) field { + return field(f.Offset) +} + +// invalidField is an invalid field identifier. +const invalidField = ^field(0) + +// IsValid reports whether the field identifier is valid. +func (f field) IsValid() bool { + return f != ^field(0) +} + +// Bytes returns the address of a []byte field in the struct. +func structPointer_Bytes(p structPointer, f field) *[]byte { + return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// BytesSlice returns the address of a [][]byte field in the struct. +func structPointer_BytesSlice(p structPointer, f field) *[][]byte { + return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// Bool returns the address of a *bool field in the struct. +func structPointer_Bool(p structPointer, f field) **bool { + return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// BoolVal returns the address of a bool field in the struct. +func structPointer_BoolVal(p structPointer, f field) *bool { + return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// BoolSlice returns the address of a []bool field in the struct. +func structPointer_BoolSlice(p structPointer, f field) *[]bool { + return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// String returns the address of a *string field in the struct. +func structPointer_String(p structPointer, f field) **string { + return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// StringVal returns the address of a string field in the struct. +func structPointer_StringVal(p structPointer, f field) *string { + return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// StringSlice returns the address of a []string field in the struct. +func structPointer_StringSlice(p structPointer, f field) *[]string { + return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// ExtMap returns the address of an extension map field in the struct. +func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { + return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// Map returns the reflect.Value for the address of a map field in the struct. +func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value { + return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f))) +} + +// SetStructPointer writes a *struct field in the struct. +func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { + *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q +} + +// GetStructPointer reads a *struct field in the struct. +func structPointer_GetStructPointer(p structPointer, f field) structPointer { + return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// StructPointerSlice the address of a []*struct field in the struct. +func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice { + return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups). +type structPointerSlice []structPointer + +func (v *structPointerSlice) Len() int { return len(*v) } +func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] } +func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) } + +// A word32 is the address of a "pointer to 32-bit value" field. +type word32 **uint32 + +// IsNil reports whether *v is nil. +func word32_IsNil(p word32) bool { + return *p == nil +} + +// Set sets *v to point at a newly allocated word set to x. +func word32_Set(p word32, o *Buffer, x uint32) { + if len(o.uint32s) == 0 { + o.uint32s = make([]uint32, uint32PoolSize) + } + o.uint32s[0] = x + *p = &o.uint32s[0] + o.uint32s = o.uint32s[1:] +} + +// Get gets the value pointed at by *v. +func word32_Get(p word32) uint32 { + return **p +} + +// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct. +func structPointer_Word32(p structPointer, f field) word32 { + return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + +// A word32Val is the address of a 32-bit value field. +type word32Val *uint32 + +// Set sets *p to x. +func word32Val_Set(p word32Val, x uint32) { + *p = x +} + +// Get gets the value pointed at by p. +func word32Val_Get(p word32Val) uint32 { + return *p +} + +// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct. +func structPointer_Word32Val(p structPointer, f field) word32Val { + return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + +// A word32Slice is a slice of 32-bit values. +type word32Slice []uint32 + +func (v *word32Slice) Append(x uint32) { *v = append(*v, x) } +func (v *word32Slice) Len() int { return len(*v) } +func (v *word32Slice) Index(i int) uint32 { return (*v)[i] } + +// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct. +func structPointer_Word32Slice(p structPointer, f field) *word32Slice { + return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// word64 is like word32 but for 64-bit values. +type word64 **uint64 + +func word64_Set(p word64, o *Buffer, x uint64) { + if len(o.uint64s) == 0 { + o.uint64s = make([]uint64, uint64PoolSize) + } + o.uint64s[0] = x + *p = &o.uint64s[0] + o.uint64s = o.uint64s[1:] +} + +func word64_IsNil(p word64) bool { + return *p == nil +} + +func word64_Get(p word64) uint64 { + return **p +} + +func structPointer_Word64(p structPointer, f field) word64 { + return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + +// word64Val is like word32Val but for 64-bit values. +type word64Val *uint64 + +func word64Val_Set(p word64Val, o *Buffer, x uint64) { + *p = x +} + +func word64Val_Get(p word64Val) uint64 { + return *p +} + +func structPointer_Word64Val(p structPointer, f field) word64Val { + return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + +// word64Slice is like word32Slice but for 64-bit values. +type word64Slice []uint64 + +func (v *word64Slice) Append(x uint64) { *v = append(*v, x) } +func (v *word64Slice) Len() int { return len(*v) } +func (v *word64Slice) Index(i int) uint64 { return (*v)[i] } + +func structPointer_Word64Slice(p structPointer, f field) *word64Slice { + return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/properties.go charm-2.2.0/src/github.com/golang/protobuf/proto/properties.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/properties.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/properties.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,742 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Routines for encoding data into the wire format for protocol buffers. + */ + +import ( + "fmt" + "os" + "reflect" + "sort" + "strconv" + "strings" + "sync" +) + +const debug bool = false + +// Constants that identify the encoding of a value on the wire. +const ( + WireVarint = 0 + WireFixed64 = 1 + WireBytes = 2 + WireStartGroup = 3 + WireEndGroup = 4 + WireFixed32 = 5 +) + +const startSize = 10 // initial slice/string sizes + +// Encoders are defined in encode.go +// An encoder outputs the full representation of a field, including its +// tag and encoder type. +type encoder func(p *Buffer, prop *Properties, base structPointer) error + +// A valueEncoder encodes a single integer in a particular encoding. +type valueEncoder func(o *Buffer, x uint64) error + +// Sizers are defined in encode.go +// A sizer returns the encoded size of a field, including its tag and encoder +// type. +type sizer func(prop *Properties, base structPointer) int + +// A valueSizer returns the encoded size of a single integer in a particular +// encoding. +type valueSizer func(x uint64) int + +// Decoders are defined in decode.go +// A decoder creates a value from its wire representation. +// Unrecognized subelements are saved in unrec. +type decoder func(p *Buffer, prop *Properties, base structPointer) error + +// A valueDecoder decodes a single integer in a particular encoding. +type valueDecoder func(o *Buffer) (x uint64, err error) + +// tagMap is an optimization over map[int]int for typical protocol buffer +// use-cases. Encoded protocol buffers are often in tag order with small tag +// numbers. +type tagMap struct { + fastTags []int + slowTags map[int]int +} + +// tagMapFastLimit is the upper bound on the tag number that will be stored in +// the tagMap slice rather than its map. +const tagMapFastLimit = 1024 + +func (p *tagMap) get(t int) (int, bool) { + if t > 0 && t < tagMapFastLimit { + if t >= len(p.fastTags) { + return 0, false + } + fi := p.fastTags[t] + return fi, fi >= 0 + } + fi, ok := p.slowTags[t] + return fi, ok +} + +func (p *tagMap) put(t int, fi int) { + if t > 0 && t < tagMapFastLimit { + for len(p.fastTags) < t+1 { + p.fastTags = append(p.fastTags, -1) + } + p.fastTags[t] = fi + return + } + if p.slowTags == nil { + p.slowTags = make(map[int]int) + } + p.slowTags[t] = fi +} + +// StructProperties represents properties for all the fields of a struct. +// decoderTags and decoderOrigNames should only be used by the decoder. +type StructProperties struct { + Prop []*Properties // properties for each field + reqCount int // required count + decoderTags tagMap // map from proto tag to struct field number + decoderOrigNames map[string]int // map from original name to struct field number + order []int // list of struct field numbers in tag order + unrecField field // field id of the XXX_unrecognized []byte field + extendable bool // is this an extendable proto +} + +// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec. +// See encode.go, (*Buffer).enc_struct. + +func (sp *StructProperties) Len() int { return len(sp.order) } +func (sp *StructProperties) Less(i, j int) bool { + return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag +} +func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] } + +// Properties represents the protocol-specific behavior of a single struct field. +type Properties struct { + Name string // name of the field, for error messages + OrigName string // original name before protocol compiler (always set) + Wire string + WireType int + Tag int + Required bool + Optional bool + Repeated bool + Packed bool // relevant for repeated primitives only + Enum string // set for enum types only + proto3 bool // whether this is known to be a proto3 field; set for []byte only + + Default string // default value + HasDefault bool // whether an explicit default was provided + def_uint64 uint64 + + enc encoder + valEnc valueEncoder // set for bool and numeric types only + field field + tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType) + tagbuf [8]byte + stype reflect.Type // set for struct types only + sprop *StructProperties // set for struct types only + isMarshaler bool + isUnmarshaler bool + + mtype reflect.Type // set for map types only + mkeyprop *Properties // set for map types only + mvalprop *Properties // set for map types only + + size sizer + valSize valueSizer // set for bool and numeric types only + + dec decoder + valDec valueDecoder // set for bool and numeric types only + + // If this is a packable field, this will be the decoder for the packed version of the field. + packedDec decoder +} + +// String formats the properties in the protobuf struct field tag style. +func (p *Properties) String() string { + s := p.Wire + s = "," + s += strconv.Itoa(p.Tag) + if p.Required { + s += ",req" + } + if p.Optional { + s += ",opt" + } + if p.Repeated { + s += ",rep" + } + if p.Packed { + s += ",packed" + } + if p.OrigName != p.Name { + s += ",name=" + p.OrigName + } + if p.proto3 { + s += ",proto3" + } + if len(p.Enum) > 0 { + s += ",enum=" + p.Enum + } + if p.HasDefault { + s += ",def=" + p.Default + } + return s +} + +// Parse populates p by parsing a string in the protobuf struct field tag style. +func (p *Properties) Parse(s string) { + // "bytes,49,opt,name=foo,def=hello!" + fields := strings.Split(s, ",") // breaks def=, but handled below. + if len(fields) < 2 { + fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s) + return + } + + p.Wire = fields[0] + switch p.Wire { + case "varint": + p.WireType = WireVarint + p.valEnc = (*Buffer).EncodeVarint + p.valDec = (*Buffer).DecodeVarint + p.valSize = sizeVarint + case "fixed32": + p.WireType = WireFixed32 + p.valEnc = (*Buffer).EncodeFixed32 + p.valDec = (*Buffer).DecodeFixed32 + p.valSize = sizeFixed32 + case "fixed64": + p.WireType = WireFixed64 + p.valEnc = (*Buffer).EncodeFixed64 + p.valDec = (*Buffer).DecodeFixed64 + p.valSize = sizeFixed64 + case "zigzag32": + p.WireType = WireVarint + p.valEnc = (*Buffer).EncodeZigzag32 + p.valDec = (*Buffer).DecodeZigzag32 + p.valSize = sizeZigzag32 + case "zigzag64": + p.WireType = WireVarint + p.valEnc = (*Buffer).EncodeZigzag64 + p.valDec = (*Buffer).DecodeZigzag64 + p.valSize = sizeZigzag64 + case "bytes", "group": + p.WireType = WireBytes + // no numeric converter for non-numeric types + default: + fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s) + return + } + + var err error + p.Tag, err = strconv.Atoi(fields[1]) + if err != nil { + return + } + + for i := 2; i < len(fields); i++ { + f := fields[i] + switch { + case f == "req": + p.Required = true + case f == "opt": + p.Optional = true + case f == "rep": + p.Repeated = true + case f == "packed": + p.Packed = true + case strings.HasPrefix(f, "name="): + p.OrigName = f[5:] + case strings.HasPrefix(f, "enum="): + p.Enum = f[5:] + case f == "proto3": + p.proto3 = true + case strings.HasPrefix(f, "def="): + p.HasDefault = true + p.Default = f[4:] // rest of string + if i+1 < len(fields) { + // Commas aren't escaped, and def is always last. + p.Default += "," + strings.Join(fields[i+1:], ",") + break + } + } + } +} + +func logNoSliceEnc(t1, t2 reflect.Type) { + fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2) +} + +var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem() + +// Initialize the fields for encoding and decoding. +func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) { + p.enc = nil + p.dec = nil + p.size = nil + + switch t1 := typ; t1.Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1) + + // proto3 scalar types + + case reflect.Bool: + p.enc = (*Buffer).enc_proto3_bool + p.dec = (*Buffer).dec_proto3_bool + p.size = size_proto3_bool + case reflect.Int32: + p.enc = (*Buffer).enc_proto3_int32 + p.dec = (*Buffer).dec_proto3_int32 + p.size = size_proto3_int32 + case reflect.Uint32: + p.enc = (*Buffer).enc_proto3_uint32 + p.dec = (*Buffer).dec_proto3_int32 // can reuse + p.size = size_proto3_uint32 + case reflect.Int64, reflect.Uint64: + p.enc = (*Buffer).enc_proto3_int64 + p.dec = (*Buffer).dec_proto3_int64 + p.size = size_proto3_int64 + case reflect.Float32: + p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits + p.dec = (*Buffer).dec_proto3_int32 + p.size = size_proto3_uint32 + case reflect.Float64: + p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits + p.dec = (*Buffer).dec_proto3_int64 + p.size = size_proto3_int64 + case reflect.String: + p.enc = (*Buffer).enc_proto3_string + p.dec = (*Buffer).dec_proto3_string + p.size = size_proto3_string + + case reflect.Ptr: + switch t2 := t1.Elem(); t2.Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2) + break + case reflect.Bool: + p.enc = (*Buffer).enc_bool + p.dec = (*Buffer).dec_bool + p.size = size_bool + case reflect.Int32: + p.enc = (*Buffer).enc_int32 + p.dec = (*Buffer).dec_int32 + p.size = size_int32 + case reflect.Uint32: + p.enc = (*Buffer).enc_uint32 + p.dec = (*Buffer).dec_int32 // can reuse + p.size = size_uint32 + case reflect.Int64, reflect.Uint64: + p.enc = (*Buffer).enc_int64 + p.dec = (*Buffer).dec_int64 + p.size = size_int64 + case reflect.Float32: + p.enc = (*Buffer).enc_uint32 // can just treat them as bits + p.dec = (*Buffer).dec_int32 + p.size = size_uint32 + case reflect.Float64: + p.enc = (*Buffer).enc_int64 // can just treat them as bits + p.dec = (*Buffer).dec_int64 + p.size = size_int64 + case reflect.String: + p.enc = (*Buffer).enc_string + p.dec = (*Buffer).dec_string + p.size = size_string + case reflect.Struct: + p.stype = t1.Elem() + p.isMarshaler = isMarshaler(t1) + p.isUnmarshaler = isUnmarshaler(t1) + if p.Wire == "bytes" { + p.enc = (*Buffer).enc_struct_message + p.dec = (*Buffer).dec_struct_message + p.size = size_struct_message + } else { + p.enc = (*Buffer).enc_struct_group + p.dec = (*Buffer).dec_struct_group + p.size = size_struct_group + } + } + + case reflect.Slice: + switch t2 := t1.Elem(); t2.Kind() { + default: + logNoSliceEnc(t1, t2) + break + case reflect.Bool: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_bool + p.size = size_slice_packed_bool + } else { + p.enc = (*Buffer).enc_slice_bool + p.size = size_slice_bool + } + p.dec = (*Buffer).dec_slice_bool + p.packedDec = (*Buffer).dec_slice_packed_bool + case reflect.Int32: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_int32 + p.size = size_slice_packed_int32 + } else { + p.enc = (*Buffer).enc_slice_int32 + p.size = size_slice_int32 + } + p.dec = (*Buffer).dec_slice_int32 + p.packedDec = (*Buffer).dec_slice_packed_int32 + case reflect.Uint32: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_uint32 + p.size = size_slice_packed_uint32 + } else { + p.enc = (*Buffer).enc_slice_uint32 + p.size = size_slice_uint32 + } + p.dec = (*Buffer).dec_slice_int32 + p.packedDec = (*Buffer).dec_slice_packed_int32 + case reflect.Int64, reflect.Uint64: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_int64 + p.size = size_slice_packed_int64 + } else { + p.enc = (*Buffer).enc_slice_int64 + p.size = size_slice_int64 + } + p.dec = (*Buffer).dec_slice_int64 + p.packedDec = (*Buffer).dec_slice_packed_int64 + case reflect.Uint8: + p.enc = (*Buffer).enc_slice_byte + p.dec = (*Buffer).dec_slice_byte + p.size = size_slice_byte + // This is a []byte, which is either a bytes field, + // or the value of a map field. In the latter case, + // we always encode an empty []byte, so we should not + // use the proto3 enc/size funcs. + // f == nil iff this is the key/value of a map field. + if p.proto3 && f != nil { + p.enc = (*Buffer).enc_proto3_slice_byte + p.size = size_proto3_slice_byte + } + case reflect.Float32, reflect.Float64: + switch t2.Bits() { + case 32: + // can just treat them as bits + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_uint32 + p.size = size_slice_packed_uint32 + } else { + p.enc = (*Buffer).enc_slice_uint32 + p.size = size_slice_uint32 + } + p.dec = (*Buffer).dec_slice_int32 + p.packedDec = (*Buffer).dec_slice_packed_int32 + case 64: + // can just treat them as bits + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_int64 + p.size = size_slice_packed_int64 + } else { + p.enc = (*Buffer).enc_slice_int64 + p.size = size_slice_int64 + } + p.dec = (*Buffer).dec_slice_int64 + p.packedDec = (*Buffer).dec_slice_packed_int64 + default: + logNoSliceEnc(t1, t2) + break + } + case reflect.String: + p.enc = (*Buffer).enc_slice_string + p.dec = (*Buffer).dec_slice_string + p.size = size_slice_string + case reflect.Ptr: + switch t3 := t2.Elem(); t3.Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3) + break + case reflect.Struct: + p.stype = t2.Elem() + p.isMarshaler = isMarshaler(t2) + p.isUnmarshaler = isUnmarshaler(t2) + if p.Wire == "bytes" { + p.enc = (*Buffer).enc_slice_struct_message + p.dec = (*Buffer).dec_slice_struct_message + p.size = size_slice_struct_message + } else { + p.enc = (*Buffer).enc_slice_struct_group + p.dec = (*Buffer).dec_slice_struct_group + p.size = size_slice_struct_group + } + } + case reflect.Slice: + switch t2.Elem().Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem()) + break + case reflect.Uint8: + p.enc = (*Buffer).enc_slice_slice_byte + p.dec = (*Buffer).dec_slice_slice_byte + p.size = size_slice_slice_byte + } + } + + case reflect.Map: + p.enc = (*Buffer).enc_new_map + p.dec = (*Buffer).dec_new_map + p.size = size_new_map + + p.mtype = t1 + p.mkeyprop = &Properties{} + p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp) + p.mvalprop = &Properties{} + vtype := p.mtype.Elem() + if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice { + // The value type is not a message (*T) or bytes ([]byte), + // so we need encoders for the pointer to this type. + vtype = reflect.PtrTo(vtype) + } + p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp) + } + + // precalculate tag code + wire := p.WireType + if p.Packed { + wire = WireBytes + } + x := uint32(p.Tag)<<3 | uint32(wire) + i := 0 + for i = 0; x > 127; i++ { + p.tagbuf[i] = 0x80 | uint8(x&0x7F) + x >>= 7 + } + p.tagbuf[i] = uint8(x) + p.tagcode = p.tagbuf[0 : i+1] + + if p.stype != nil { + if lockGetProp { + p.sprop = GetProperties(p.stype) + } else { + p.sprop = getPropertiesLocked(p.stype) + } + } +} + +var ( + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() +) + +// isMarshaler reports whether type t implements Marshaler. +func isMarshaler(t reflect.Type) bool { + // We're checking for (likely) pointer-receiver methods + // so if t is not a pointer, something is very wrong. + // The calls above only invoke isMarshaler on pointer types. + if t.Kind() != reflect.Ptr { + panic("proto: misuse of isMarshaler") + } + return t.Implements(marshalerType) +} + +// isUnmarshaler reports whether type t implements Unmarshaler. +func isUnmarshaler(t reflect.Type) bool { + // We're checking for (likely) pointer-receiver methods + // so if t is not a pointer, something is very wrong. + // The calls above only invoke isUnmarshaler on pointer types. + if t.Kind() != reflect.Ptr { + panic("proto: misuse of isUnmarshaler") + } + return t.Implements(unmarshalerType) +} + +// Init populates the properties from a protocol buffer struct tag. +func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) { + p.init(typ, name, tag, f, true) +} + +func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) { + // "bytes,49,opt,def=hello!" + p.Name = name + p.OrigName = name + if f != nil { + p.field = toField(f) + } + if tag == "" { + return + } + p.Parse(tag) + p.setEncAndDec(typ, f, lockGetProp) +} + +var ( + propertiesMu sync.RWMutex + propertiesMap = make(map[reflect.Type]*StructProperties) +) + +// GetProperties returns the list of properties for the type represented by t. +// t must represent a generated struct type of a protocol message. +func GetProperties(t reflect.Type) *StructProperties { + if t.Kind() != reflect.Struct { + panic("proto: type must have kind struct") + } + + // Most calls to GetProperties in a long-running program will be + // retrieving details for types we have seen before. + propertiesMu.RLock() + sprop, ok := propertiesMap[t] + propertiesMu.RUnlock() + if ok { + if collectStats { + stats.Chit++ + } + return sprop + } + + propertiesMu.Lock() + sprop = getPropertiesLocked(t) + propertiesMu.Unlock() + return sprop +} + +// getPropertiesLocked requires that propertiesMu is held. +func getPropertiesLocked(t reflect.Type) *StructProperties { + if prop, ok := propertiesMap[t]; ok { + if collectStats { + stats.Chit++ + } + return prop + } + if collectStats { + stats.Cmiss++ + } + + prop := new(StructProperties) + // in case of recursive protos, fill this in now. + propertiesMap[t] = prop + + // build properties + prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) + prop.unrecField = invalidField + prop.Prop = make([]*Properties, t.NumField()) + prop.order = make([]int, t.NumField()) + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + p := new(Properties) + name := f.Name + p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false) + + if f.Name == "XXX_extensions" { // special case + p.enc = (*Buffer).enc_map + p.dec = nil // not needed + p.size = size_map + } + if f.Name == "XXX_unrecognized" { // special case + prop.unrecField = toField(&f) + } + prop.Prop[i] = p + prop.order[i] = i + if debug { + print(i, " ", f.Name, " ", t.String(), " ") + if p.Tag > 0 { + print(p.String()) + } + print("\n") + } + if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") { + fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]") + } + } + + // Re-order prop.order. + sort.Sort(prop) + + // build required counts + // build tags + reqCount := 0 + prop.decoderOrigNames = make(map[string]int) + for i, p := range prop.Prop { + if strings.HasPrefix(p.Name, "XXX_") { + // Internal fields should not appear in tags/origNames maps. + // They are handled specially when encoding and decoding. + continue + } + if p.Required { + reqCount++ + } + prop.decoderTags.put(p.Tag, i) + prop.decoderOrigNames[p.OrigName] = i + } + prop.reqCount = reqCount + + return prop +} + +// Return the Properties object for the x[0]'th field of the structure. +func propByIndex(t reflect.Type, x []int) *Properties { + if len(x) != 1 { + fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t) + return nil + } + prop := GetProperties(t) + return prop.Prop[x[0]] +} + +// Get the address and type of a pointer to a struct from an interface. +func getbase(pb Message) (t reflect.Type, b structPointer, err error) { + if pb == nil { + err = ErrNil + return + } + // get the reflect type of the pointer to the struct. + t = reflect.TypeOf(pb) + // get the address of the struct. + value := reflect.ValueOf(pb) + b = toStructPointer(value) + return +} + +// A global registry of enum types. +// The generated code will register the generated maps by calling RegisterEnum. + +var enumValueMaps = make(map[string]map[string]int32) + +// RegisterEnum is called from the generated code to install the enum descriptor +// maps into the global table to aid parsing text format protocol buffers. +func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) { + if _, ok := enumValueMaps[typeName]; ok { + panic("proto: duplicate enum registered: " + typeName) + } + enumValueMaps[typeName] = valueMap +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go charm-2.2.0/src/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,122 @@ +// Code generated by protoc-gen-go. +// source: proto3_proto/proto3.proto +// DO NOT EDIT! + +/* +Package proto3_proto is a generated protocol buffer package. + +It is generated from these files: + proto3_proto/proto3.proto + +It has these top-level messages: + Message + Nested + MessageWithMap +*/ +package proto3_proto + +import proto "github.com/golang/protobuf/proto" +import testdata "github.com/golang/protobuf/proto/testdata" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal + +type Message_Humour int32 + +const ( + Message_UNKNOWN Message_Humour = 0 + Message_PUNS Message_Humour = 1 + Message_SLAPSTICK Message_Humour = 2 + Message_BILL_BAILEY Message_Humour = 3 +) + +var Message_Humour_name = map[int32]string{ + 0: "UNKNOWN", + 1: "PUNS", + 2: "SLAPSTICK", + 3: "BILL_BAILEY", +} +var Message_Humour_value = map[string]int32{ + "UNKNOWN": 0, + "PUNS": 1, + "SLAPSTICK": 2, + "BILL_BAILEY": 3, +} + +func (x Message_Humour) String() string { + return proto.EnumName(Message_Humour_name, int32(x)) +} + +type Message struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Hilarity Message_Humour `protobuf:"varint,2,opt,name=hilarity,enum=proto3_proto.Message_Humour" json:"hilarity,omitempty"` + HeightInCm uint32 `protobuf:"varint,3,opt,name=height_in_cm" json:"height_in_cm,omitempty"` + Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` + ResultCount int64 `protobuf:"varint,7,opt,name=result_count" json:"result_count,omitempty"` + TrueScotsman bool `protobuf:"varint,8,opt,name=true_scotsman" json:"true_scotsman,omitempty"` + Score float32 `protobuf:"fixed32,9,opt,name=score" json:"score,omitempty"` + Key []uint64 `protobuf:"varint,5,rep,name=key" json:"key,omitempty"` + Nested *Nested `protobuf:"bytes,6,opt,name=nested" json:"nested,omitempty"` + Terrain map[string]*Nested `protobuf:"bytes,10,rep,name=terrain" json:"terrain,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Proto2Field *testdata.SubDefaults `protobuf:"bytes,11,opt,name=proto2_field" json:"proto2_field,omitempty"` + Proto2Value map[string]*testdata.SubDefaults `protobuf:"bytes,13,rep,name=proto2_value" json:"proto2_value,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} + +func (m *Message) GetNested() *Nested { + if m != nil { + return m.Nested + } + return nil +} + +func (m *Message) GetTerrain() map[string]*Nested { + if m != nil { + return m.Terrain + } + return nil +} + +func (m *Message) GetProto2Field() *testdata.SubDefaults { + if m != nil { + return m.Proto2Field + } + return nil +} + +func (m *Message) GetProto2Value() map[string]*testdata.SubDefaults { + if m != nil { + return m.Proto2Value + } + return nil +} + +type Nested struct { + Bunny string `protobuf:"bytes,1,opt,name=bunny" json:"bunny,omitempty"` +} + +func (m *Nested) Reset() { *m = Nested{} } +func (m *Nested) String() string { return proto.CompactTextString(m) } +func (*Nested) ProtoMessage() {} + +type MessageWithMap struct { + ByteMapping map[bool][]byte `protobuf:"bytes,1,rep,name=byte_mapping" json:"byte_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (m *MessageWithMap) Reset() { *m = MessageWithMap{} } +func (m *MessageWithMap) String() string { return proto.CompactTextString(m) } +func (*MessageWithMap) ProtoMessage() {} + +func (m *MessageWithMap) GetByteMapping() map[bool][]byte { + if m != nil { + return m.ByteMapping + } + return nil +} + +func init() { + proto.RegisterEnum("proto3_proto.Message_Humour", Message_Humour_name, Message_Humour_value) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto charm-2.2.0/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto --- charm-2.1.1/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,68 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +import "testdata/test.proto"; + +package proto3_proto; + +message Message { + enum Humour { + UNKNOWN = 0; + PUNS = 1; + SLAPSTICK = 2; + BILL_BAILEY = 3; + } + + string name = 1; + Humour hilarity = 2; + uint32 height_in_cm = 3; + bytes data = 4; + int64 result_count = 7; + bool true_scotsman = 8; + float score = 9; + + repeated uint64 key = 5; + Nested nested = 6; + + map terrain = 10; + testdata.SubDefaults proto2_field = 11; + map proto2_value = 13; +} + +message Nested { + string bunny = 1; +} + +message MessageWithMap { + map byte_mapping = 1; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/proto3_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/proto3_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/proto3_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/proto3_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,125 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "testing" + + "github.com/golang/protobuf/proto" + pb "github.com/golang/protobuf/proto/proto3_proto" + tpb "github.com/golang/protobuf/proto/testdata" +) + +func TestProto3ZeroValues(t *testing.T) { + tests := []struct { + desc string + m proto.Message + }{ + {"zero message", &pb.Message{}}, + {"empty bytes field", &pb.Message{Data: []byte{}}}, + } + for _, test := range tests { + b, err := proto.Marshal(test.m) + if err != nil { + t.Errorf("%s: proto.Marshal: %v", test.desc, err) + continue + } + if len(b) > 0 { + t.Errorf("%s: Encoding is non-empty: %q", test.desc, b) + } + } +} + +func TestRoundTripProto3(t *testing.T) { + m := &pb.Message{ + Name: "David", // (2 | 1<<3): 0x0a 0x05 "David" + Hilarity: pb.Message_PUNS, // (0 | 2<<3): 0x10 0x01 + HeightInCm: 178, // (0 | 3<<3): 0x18 0xb2 0x01 + Data: []byte("roboto"), // (2 | 4<<3): 0x20 0x06 "roboto" + ResultCount: 47, // (0 | 7<<3): 0x38 0x2f + TrueScotsman: true, // (0 | 8<<3): 0x40 0x01 + Score: 8.1, // (5 | 9<<3): 0x4d <8.1> + + Key: []uint64{1, 0xdeadbeef}, + Nested: &pb.Nested{ + Bunny: "Monty", + }, + } + t.Logf(" m: %v", m) + + b, err := proto.Marshal(m) + if err != nil { + t.Fatalf("proto.Marshal: %v", err) + } + t.Logf(" b: %q", b) + + m2 := new(pb.Message) + if err := proto.Unmarshal(b, m2); err != nil { + t.Fatalf("proto.Unmarshal: %v", err) + } + t.Logf("m2: %v", m2) + + if !proto.Equal(m, m2) { + t.Errorf("proto.Equal returned false:\n m: %v\nm2: %v", m, m2) + } +} + +func TestProto3SetDefaults(t *testing.T) { + in := &pb.Message{ + Terrain: map[string]*pb.Nested{ + "meadow": new(pb.Nested), + }, + Proto2Field: new(tpb.SubDefaults), + Proto2Value: map[string]*tpb.SubDefaults{ + "badlands": new(tpb.SubDefaults), + }, + } + + got := proto.Clone(in).(*pb.Message) + proto.SetDefaults(got) + + // There are no defaults in proto3. Everything should be the zero value, but + // we need to remember to set defaults for nested proto2 messages. + want := &pb.Message{ + Terrain: map[string]*pb.Nested{ + "meadow": new(pb.Nested), + }, + Proto2Field: &tpb.SubDefaults{N: proto.Int64(7)}, + Proto2Value: map[string]*tpb.SubDefaults{ + "badlands": &tpb.SubDefaults{N: proto.Int64(7)}, + }, + } + + if !proto.Equal(got, want) { + t.Errorf("with in = %v\nproto.SetDefaults(in) =>\ngot %v\nwant %v", in, got, want) + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/size2_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/size2_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/size2_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/size2_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,63 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +import ( + "testing" +) + +// This is a separate file and package from size_test.go because that one uses +// generated messages and thus may not be in package proto without having a circular +// dependency, whereas this file tests unexported details of size.go. + +func TestVarintSize(t *testing.T) { + // Check the edge cases carefully. + testCases := []struct { + n uint64 + size int + }{ + {0, 1}, + {1, 1}, + {127, 1}, + {128, 2}, + {16383, 2}, + {16384, 3}, + {1<<63 - 1, 9}, + {1 << 63, 10}, + } + for _, tc := range testCases { + size := sizeVarint(tc.n) + if size != tc.size { + t.Errorf("sizeVarint(%d) = %d, want %d", tc.n, size, tc.size) + } + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/size_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/size_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/size_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/size_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,142 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "log" + "strings" + "testing" + + . "github.com/golang/protobuf/proto" + proto3pb "github.com/golang/protobuf/proto/proto3_proto" + pb "github.com/golang/protobuf/proto/testdata" +) + +var messageWithExtension1 = &pb.MyMessage{Count: Int32(7)} + +// messageWithExtension2 is in equal_test.go. +var messageWithExtension3 = &pb.MyMessage{Count: Int32(8)} + +func init() { + if err := SetExtension(messageWithExtension1, pb.E_Ext_More, &pb.Ext{Data: String("Abbott")}); err != nil { + log.Panicf("SetExtension: %v", err) + } + if err := SetExtension(messageWithExtension3, pb.E_Ext_More, &pb.Ext{Data: String("Costello")}); err != nil { + log.Panicf("SetExtension: %v", err) + } + + // Force messageWithExtension3 to have the extension encoded. + Marshal(messageWithExtension3) + +} + +var SizeTests = []struct { + desc string + pb Message +}{ + {"empty", &pb.OtherMessage{}}, + // Basic types. + {"bool", &pb.Defaults{F_Bool: Bool(true)}}, + {"int32", &pb.Defaults{F_Int32: Int32(12)}}, + {"negative int32", &pb.Defaults{F_Int32: Int32(-1)}}, + {"small int64", &pb.Defaults{F_Int64: Int64(1)}}, + {"big int64", &pb.Defaults{F_Int64: Int64(1 << 20)}}, + {"negative int64", &pb.Defaults{F_Int64: Int64(-1)}}, + {"fixed32", &pb.Defaults{F_Fixed32: Uint32(71)}}, + {"fixed64", &pb.Defaults{F_Fixed64: Uint64(72)}}, + {"uint32", &pb.Defaults{F_Uint32: Uint32(123)}}, + {"uint64", &pb.Defaults{F_Uint64: Uint64(124)}}, + {"float", &pb.Defaults{F_Float: Float32(12.6)}}, + {"double", &pb.Defaults{F_Double: Float64(13.9)}}, + {"string", &pb.Defaults{F_String: String("niles")}}, + {"bytes", &pb.Defaults{F_Bytes: []byte("wowsa")}}, + {"bytes, empty", &pb.Defaults{F_Bytes: []byte{}}}, + {"sint32", &pb.Defaults{F_Sint32: Int32(65)}}, + {"sint64", &pb.Defaults{F_Sint64: Int64(67)}}, + {"enum", &pb.Defaults{F_Enum: pb.Defaults_BLUE.Enum()}}, + // Repeated. + {"empty repeated bool", &pb.MoreRepeated{Bools: []bool{}}}, + {"repeated bool", &pb.MoreRepeated{Bools: []bool{false, true, true, false}}}, + {"packed repeated bool", &pb.MoreRepeated{BoolsPacked: []bool{false, true, true, false, true, true, true}}}, + {"repeated int32", &pb.MoreRepeated{Ints: []int32{1, 12203, 1729, -1}}}, + {"repeated int32 packed", &pb.MoreRepeated{IntsPacked: []int32{1, 12203, 1729}}}, + {"repeated int64 packed", &pb.MoreRepeated{Int64SPacked: []int64{ + // Need enough large numbers to verify that the header is counting the number of bytes + // for the field, not the number of elements. + 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, + 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, + }}}, + {"repeated string", &pb.MoreRepeated{Strings: []string{"r", "ken", "gri"}}}, + {"repeated fixed", &pb.MoreRepeated{Fixeds: []uint32{1, 2, 3, 4}}}, + // Nested. + {"nested", &pb.OldMessage{Nested: &pb.OldMessage_Nested{Name: String("whatever")}}}, + {"group", &pb.GroupOld{G: &pb.GroupOld_G{X: Int32(12345)}}}, + // Other things. + {"unrecognized", &pb.MoreRepeated{XXX_unrecognized: []byte{13<<3 | 0, 4}}}, + {"extension (unencoded)", messageWithExtension1}, + {"extension (encoded)", messageWithExtension3}, + // proto3 message + {"proto3 empty", &proto3pb.Message{}}, + {"proto3 bool", &proto3pb.Message{TrueScotsman: true}}, + {"proto3 int64", &proto3pb.Message{ResultCount: 1}}, + {"proto3 uint32", &proto3pb.Message{HeightInCm: 123}}, + {"proto3 float", &proto3pb.Message{Score: 12.6}}, + {"proto3 string", &proto3pb.Message{Name: "Snezana"}}, + {"proto3 bytes", &proto3pb.Message{Data: []byte("wowsa")}}, + {"proto3 bytes, empty", &proto3pb.Message{Data: []byte{}}}, + {"proto3 enum", &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}}, + {"proto3 map field with empty bytes", &proto3pb.MessageWithMap{ByteMapping: map[bool][]byte{false: []byte{}}}}, + + {"map field", &pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob", 7: "Andrew"}}}, + {"map field with message", &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{0x7001: &pb.FloatingPoint{F: Float64(2.0)}}}}, + {"map field with bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte("this time for sure")}}}, + {"map field with empty bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte{}}}}, + + {"map field with big entry", &pb.MessageWithMap{NameMapping: map[int32]string{8: strings.Repeat("x", 125)}}}, + {"map field with big key and val", &pb.MessageWithMap{StrToStr: map[string]string{strings.Repeat("x", 70): strings.Repeat("y", 70)}}}, + {"map field with big numeric key", &pb.MessageWithMap{NameMapping: map[int32]string{0xf00d: "om nom nom"}}}, +} + +func TestSize(t *testing.T) { + for _, tc := range SizeTests { + size := Size(tc.pb) + b, err := Marshal(tc.pb) + if err != nil { + t.Errorf("%v: Marshal failed: %v", tc.desc, err) + continue + } + if size != len(b) { + t.Errorf("%v: Size(%v) = %d, want %d", tc.desc, tc.pb, size, len(b)) + t.Logf("%v: bytes: %#v", tc.desc, b) + } + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/golden_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/golden_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/golden_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/golden_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,86 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Verify that the compiler output for test.proto is unchanged. + +package testdata + +import ( + "crypto/sha1" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "testing" +) + +// sum returns in string form (for easy comparison) the SHA-1 hash of the named file. +func sum(t *testing.T, name string) string { + data, err := ioutil.ReadFile(name) + if err != nil { + t.Fatal(err) + } + t.Logf("sum(%q): length is %d", name, len(data)) + hash := sha1.New() + _, err = hash.Write(data) + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf("% x", hash.Sum(nil)) +} + +func run(t *testing.T, name string, args ...string) { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + t.Fatal(err) + } +} + +func TestGolden(t *testing.T) { + // Compute the original checksum. + goldenSum := sum(t, "test.pb.go") + // Run the proto compiler. + run(t, "protoc", "--go_out="+os.TempDir(), "test.proto") + newFile := filepath.Join(os.TempDir(), "test.pb.go") + defer os.Remove(newFile) + // Compute the new checksum. + newSum := sum(t, newFile) + // Verify + if newSum != goldenSum { + run(t, "diff", "-u", "test.pb.go", newFile) + t.Fatal("Code generated by protoc-gen-go has changed; update test.pb.go") + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/Makefile charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,50 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +include ../../Make.protobuf + +all: regenerate + +regenerate: + rm -f test.pb.go + make test.pb.go + +# The following rules are just aids to development. Not needed for typical testing. + +diff: regenerate + git diff test.pb.go + +restore: + cp test.pb.go.golden test.pb.go + +preserve: + cp test.pb.go test.pb.go.golden diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/test.pb.go charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/test.pb.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/test.pb.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/test.pb.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,2746 @@ +// Code generated by protoc-gen-go. +// source: test.proto +// DO NOT EDIT! + +/* +Package testdata is a generated protocol buffer package. + +It is generated from these files: + test.proto + +It has these top-level messages: + GoEnum + GoTestField + GoTest + GoSkipTest + NonPackedTest + PackedTest + MaxTag + OldMessage + NewMessage + InnerMessage + OtherMessage + MyMessage + Ext + DefaultsMessage + MyMessageSet + Empty + MessageList + Strings + Defaults + SubDefaults + RepeatedEnum + MoreRepeated + GroupOld + GroupNew + FloatingPoint + MessageWithMap +*/ +package testdata + +import proto "github.com/golang/protobuf/proto" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +type FOO int32 + +const ( + FOO_FOO1 FOO = 1 +) + +var FOO_name = map[int32]string{ + 1: "FOO1", +} +var FOO_value = map[string]int32{ + "FOO1": 1, +} + +func (x FOO) Enum() *FOO { + p := new(FOO) + *p = x + return p +} +func (x FOO) String() string { + return proto.EnumName(FOO_name, int32(x)) +} +func (x *FOO) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO") + if err != nil { + return err + } + *x = FOO(value) + return nil +} + +// An enum, for completeness. +type GoTest_KIND int32 + +const ( + GoTest_VOID GoTest_KIND = 0 + // Basic types + GoTest_BOOL GoTest_KIND = 1 + GoTest_BYTES GoTest_KIND = 2 + GoTest_FINGERPRINT GoTest_KIND = 3 + GoTest_FLOAT GoTest_KIND = 4 + GoTest_INT GoTest_KIND = 5 + GoTest_STRING GoTest_KIND = 6 + GoTest_TIME GoTest_KIND = 7 + // Groupings + GoTest_TUPLE GoTest_KIND = 8 + GoTest_ARRAY GoTest_KIND = 9 + GoTest_MAP GoTest_KIND = 10 + // Table types + GoTest_TABLE GoTest_KIND = 11 + // Functions + GoTest_FUNCTION GoTest_KIND = 12 +) + +var GoTest_KIND_name = map[int32]string{ + 0: "VOID", + 1: "BOOL", + 2: "BYTES", + 3: "FINGERPRINT", + 4: "FLOAT", + 5: "INT", + 6: "STRING", + 7: "TIME", + 8: "TUPLE", + 9: "ARRAY", + 10: "MAP", + 11: "TABLE", + 12: "FUNCTION", +} +var GoTest_KIND_value = map[string]int32{ + "VOID": 0, + "BOOL": 1, + "BYTES": 2, + "FINGERPRINT": 3, + "FLOAT": 4, + "INT": 5, + "STRING": 6, + "TIME": 7, + "TUPLE": 8, + "ARRAY": 9, + "MAP": 10, + "TABLE": 11, + "FUNCTION": 12, +} + +func (x GoTest_KIND) Enum() *GoTest_KIND { + p := new(GoTest_KIND) + *p = x + return p +} +func (x GoTest_KIND) String() string { + return proto.EnumName(GoTest_KIND_name, int32(x)) +} +func (x *GoTest_KIND) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(GoTest_KIND_value, data, "GoTest_KIND") + if err != nil { + return err + } + *x = GoTest_KIND(value) + return nil +} + +type MyMessage_Color int32 + +const ( + MyMessage_RED MyMessage_Color = 0 + MyMessage_GREEN MyMessage_Color = 1 + MyMessage_BLUE MyMessage_Color = 2 +) + +var MyMessage_Color_name = map[int32]string{ + 0: "RED", + 1: "GREEN", + 2: "BLUE", +} +var MyMessage_Color_value = map[string]int32{ + "RED": 0, + "GREEN": 1, + "BLUE": 2, +} + +func (x MyMessage_Color) Enum() *MyMessage_Color { + p := new(MyMessage_Color) + *p = x + return p +} +func (x MyMessage_Color) String() string { + return proto.EnumName(MyMessage_Color_name, int32(x)) +} +func (x *MyMessage_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(MyMessage_Color_value, data, "MyMessage_Color") + if err != nil { + return err + } + *x = MyMessage_Color(value) + return nil +} + +type DefaultsMessage_DefaultsEnum int32 + +const ( + DefaultsMessage_ZERO DefaultsMessage_DefaultsEnum = 0 + DefaultsMessage_ONE DefaultsMessage_DefaultsEnum = 1 + DefaultsMessage_TWO DefaultsMessage_DefaultsEnum = 2 +) + +var DefaultsMessage_DefaultsEnum_name = map[int32]string{ + 0: "ZERO", + 1: "ONE", + 2: "TWO", +} +var DefaultsMessage_DefaultsEnum_value = map[string]int32{ + "ZERO": 0, + "ONE": 1, + "TWO": 2, +} + +func (x DefaultsMessage_DefaultsEnum) Enum() *DefaultsMessage_DefaultsEnum { + p := new(DefaultsMessage_DefaultsEnum) + *p = x + return p +} +func (x DefaultsMessage_DefaultsEnum) String() string { + return proto.EnumName(DefaultsMessage_DefaultsEnum_name, int32(x)) +} +func (x *DefaultsMessage_DefaultsEnum) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(DefaultsMessage_DefaultsEnum_value, data, "DefaultsMessage_DefaultsEnum") + if err != nil { + return err + } + *x = DefaultsMessage_DefaultsEnum(value) + return nil +} + +type Defaults_Color int32 + +const ( + Defaults_RED Defaults_Color = 0 + Defaults_GREEN Defaults_Color = 1 + Defaults_BLUE Defaults_Color = 2 +) + +var Defaults_Color_name = map[int32]string{ + 0: "RED", + 1: "GREEN", + 2: "BLUE", +} +var Defaults_Color_value = map[string]int32{ + "RED": 0, + "GREEN": 1, + "BLUE": 2, +} + +func (x Defaults_Color) Enum() *Defaults_Color { + p := new(Defaults_Color) + *p = x + return p +} +func (x Defaults_Color) String() string { + return proto.EnumName(Defaults_Color_name, int32(x)) +} +func (x *Defaults_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Defaults_Color_value, data, "Defaults_Color") + if err != nil { + return err + } + *x = Defaults_Color(value) + return nil +} + +type RepeatedEnum_Color int32 + +const ( + RepeatedEnum_RED RepeatedEnum_Color = 1 +) + +var RepeatedEnum_Color_name = map[int32]string{ + 1: "RED", +} +var RepeatedEnum_Color_value = map[string]int32{ + "RED": 1, +} + +func (x RepeatedEnum_Color) Enum() *RepeatedEnum_Color { + p := new(RepeatedEnum_Color) + *p = x + return p +} +func (x RepeatedEnum_Color) String() string { + return proto.EnumName(RepeatedEnum_Color_name, int32(x)) +} +func (x *RepeatedEnum_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(RepeatedEnum_Color_value, data, "RepeatedEnum_Color") + if err != nil { + return err + } + *x = RepeatedEnum_Color(value) + return nil +} + +type GoEnum struct { + Foo *FOO `protobuf:"varint,1,req,name=foo,enum=testdata.FOO" json:"foo,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoEnum) Reset() { *m = GoEnum{} } +func (m *GoEnum) String() string { return proto.CompactTextString(m) } +func (*GoEnum) ProtoMessage() {} + +func (m *GoEnum) GetFoo() FOO { + if m != nil && m.Foo != nil { + return *m.Foo + } + return FOO_FOO1 +} + +type GoTestField struct { + Label *string `protobuf:"bytes,1,req" json:"Label,omitempty"` + Type *string `protobuf:"bytes,2,req" json:"Type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTestField) Reset() { *m = GoTestField{} } +func (m *GoTestField) String() string { return proto.CompactTextString(m) } +func (*GoTestField) ProtoMessage() {} + +func (m *GoTestField) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *GoTestField) GetType() string { + if m != nil && m.Type != nil { + return *m.Type + } + return "" +} + +type GoTest struct { + // Some typical parameters + Kind *GoTest_KIND `protobuf:"varint,1,req,enum=testdata.GoTest_KIND" json:"Kind,omitempty"` + Table *string `protobuf:"bytes,2,opt" json:"Table,omitempty"` + Param *int32 `protobuf:"varint,3,opt" json:"Param,omitempty"` + // Required, repeated and optional foreign fields. + RequiredField *GoTestField `protobuf:"bytes,4,req" json:"RequiredField,omitempty"` + RepeatedField []*GoTestField `protobuf:"bytes,5,rep" json:"RepeatedField,omitempty"` + OptionalField *GoTestField `protobuf:"bytes,6,opt" json:"OptionalField,omitempty"` + // Required fields of all basic types + F_BoolRequired *bool `protobuf:"varint,10,req,name=F_Bool_required" json:"F_Bool_required,omitempty"` + F_Int32Required *int32 `protobuf:"varint,11,req,name=F_Int32_required" json:"F_Int32_required,omitempty"` + F_Int64Required *int64 `protobuf:"varint,12,req,name=F_Int64_required" json:"F_Int64_required,omitempty"` + F_Fixed32Required *uint32 `protobuf:"fixed32,13,req,name=F_Fixed32_required" json:"F_Fixed32_required,omitempty"` + F_Fixed64Required *uint64 `protobuf:"fixed64,14,req,name=F_Fixed64_required" json:"F_Fixed64_required,omitempty"` + F_Uint32Required *uint32 `protobuf:"varint,15,req,name=F_Uint32_required" json:"F_Uint32_required,omitempty"` + F_Uint64Required *uint64 `protobuf:"varint,16,req,name=F_Uint64_required" json:"F_Uint64_required,omitempty"` + F_FloatRequired *float32 `protobuf:"fixed32,17,req,name=F_Float_required" json:"F_Float_required,omitempty"` + F_DoubleRequired *float64 `protobuf:"fixed64,18,req,name=F_Double_required" json:"F_Double_required,omitempty"` + F_StringRequired *string `protobuf:"bytes,19,req,name=F_String_required" json:"F_String_required,omitempty"` + F_BytesRequired []byte `protobuf:"bytes,101,req,name=F_Bytes_required" json:"F_Bytes_required,omitempty"` + F_Sint32Required *int32 `protobuf:"zigzag32,102,req,name=F_Sint32_required" json:"F_Sint32_required,omitempty"` + F_Sint64Required *int64 `protobuf:"zigzag64,103,req,name=F_Sint64_required" json:"F_Sint64_required,omitempty"` + // Repeated fields of all basic types + F_BoolRepeated []bool `protobuf:"varint,20,rep,name=F_Bool_repeated" json:"F_Bool_repeated,omitempty"` + F_Int32Repeated []int32 `protobuf:"varint,21,rep,name=F_Int32_repeated" json:"F_Int32_repeated,omitempty"` + F_Int64Repeated []int64 `protobuf:"varint,22,rep,name=F_Int64_repeated" json:"F_Int64_repeated,omitempty"` + F_Fixed32Repeated []uint32 `protobuf:"fixed32,23,rep,name=F_Fixed32_repeated" json:"F_Fixed32_repeated,omitempty"` + F_Fixed64Repeated []uint64 `protobuf:"fixed64,24,rep,name=F_Fixed64_repeated" json:"F_Fixed64_repeated,omitempty"` + F_Uint32Repeated []uint32 `protobuf:"varint,25,rep,name=F_Uint32_repeated" json:"F_Uint32_repeated,omitempty"` + F_Uint64Repeated []uint64 `protobuf:"varint,26,rep,name=F_Uint64_repeated" json:"F_Uint64_repeated,omitempty"` + F_FloatRepeated []float32 `protobuf:"fixed32,27,rep,name=F_Float_repeated" json:"F_Float_repeated,omitempty"` + F_DoubleRepeated []float64 `protobuf:"fixed64,28,rep,name=F_Double_repeated" json:"F_Double_repeated,omitempty"` + F_StringRepeated []string `protobuf:"bytes,29,rep,name=F_String_repeated" json:"F_String_repeated,omitempty"` + F_BytesRepeated [][]byte `protobuf:"bytes,201,rep,name=F_Bytes_repeated" json:"F_Bytes_repeated,omitempty"` + F_Sint32Repeated []int32 `protobuf:"zigzag32,202,rep,name=F_Sint32_repeated" json:"F_Sint32_repeated,omitempty"` + F_Sint64Repeated []int64 `protobuf:"zigzag64,203,rep,name=F_Sint64_repeated" json:"F_Sint64_repeated,omitempty"` + // Optional fields of all basic types + F_BoolOptional *bool `protobuf:"varint,30,opt,name=F_Bool_optional" json:"F_Bool_optional,omitempty"` + F_Int32Optional *int32 `protobuf:"varint,31,opt,name=F_Int32_optional" json:"F_Int32_optional,omitempty"` + F_Int64Optional *int64 `protobuf:"varint,32,opt,name=F_Int64_optional" json:"F_Int64_optional,omitempty"` + F_Fixed32Optional *uint32 `protobuf:"fixed32,33,opt,name=F_Fixed32_optional" json:"F_Fixed32_optional,omitempty"` + F_Fixed64Optional *uint64 `protobuf:"fixed64,34,opt,name=F_Fixed64_optional" json:"F_Fixed64_optional,omitempty"` + F_Uint32Optional *uint32 `protobuf:"varint,35,opt,name=F_Uint32_optional" json:"F_Uint32_optional,omitempty"` + F_Uint64Optional *uint64 `protobuf:"varint,36,opt,name=F_Uint64_optional" json:"F_Uint64_optional,omitempty"` + F_FloatOptional *float32 `protobuf:"fixed32,37,opt,name=F_Float_optional" json:"F_Float_optional,omitempty"` + F_DoubleOptional *float64 `protobuf:"fixed64,38,opt,name=F_Double_optional" json:"F_Double_optional,omitempty"` + F_StringOptional *string `protobuf:"bytes,39,opt,name=F_String_optional" json:"F_String_optional,omitempty"` + F_BytesOptional []byte `protobuf:"bytes,301,opt,name=F_Bytes_optional" json:"F_Bytes_optional,omitempty"` + F_Sint32Optional *int32 `protobuf:"zigzag32,302,opt,name=F_Sint32_optional" json:"F_Sint32_optional,omitempty"` + F_Sint64Optional *int64 `protobuf:"zigzag64,303,opt,name=F_Sint64_optional" json:"F_Sint64_optional,omitempty"` + // Default-valued fields of all basic types + F_BoolDefaulted *bool `protobuf:"varint,40,opt,name=F_Bool_defaulted,def=1" json:"F_Bool_defaulted,omitempty"` + F_Int32Defaulted *int32 `protobuf:"varint,41,opt,name=F_Int32_defaulted,def=32" json:"F_Int32_defaulted,omitempty"` + F_Int64Defaulted *int64 `protobuf:"varint,42,opt,name=F_Int64_defaulted,def=64" json:"F_Int64_defaulted,omitempty"` + F_Fixed32Defaulted *uint32 `protobuf:"fixed32,43,opt,name=F_Fixed32_defaulted,def=320" json:"F_Fixed32_defaulted,omitempty"` + F_Fixed64Defaulted *uint64 `protobuf:"fixed64,44,opt,name=F_Fixed64_defaulted,def=640" json:"F_Fixed64_defaulted,omitempty"` + F_Uint32Defaulted *uint32 `protobuf:"varint,45,opt,name=F_Uint32_defaulted,def=3200" json:"F_Uint32_defaulted,omitempty"` + F_Uint64Defaulted *uint64 `protobuf:"varint,46,opt,name=F_Uint64_defaulted,def=6400" json:"F_Uint64_defaulted,omitempty"` + F_FloatDefaulted *float32 `protobuf:"fixed32,47,opt,name=F_Float_defaulted,def=314159" json:"F_Float_defaulted,omitempty"` + F_DoubleDefaulted *float64 `protobuf:"fixed64,48,opt,name=F_Double_defaulted,def=271828" json:"F_Double_defaulted,omitempty"` + F_StringDefaulted *string `protobuf:"bytes,49,opt,name=F_String_defaulted,def=hello, \"world!\"\n" json:"F_String_defaulted,omitempty"` + F_BytesDefaulted []byte `protobuf:"bytes,401,opt,name=F_Bytes_defaulted,def=Bignose" json:"F_Bytes_defaulted,omitempty"` + F_Sint32Defaulted *int32 `protobuf:"zigzag32,402,opt,name=F_Sint32_defaulted,def=-32" json:"F_Sint32_defaulted,omitempty"` + F_Sint64Defaulted *int64 `protobuf:"zigzag64,403,opt,name=F_Sint64_defaulted,def=-64" json:"F_Sint64_defaulted,omitempty"` + // Packed repeated fields (no string or bytes). + F_BoolRepeatedPacked []bool `protobuf:"varint,50,rep,packed,name=F_Bool_repeated_packed" json:"F_Bool_repeated_packed,omitempty"` + F_Int32RepeatedPacked []int32 `protobuf:"varint,51,rep,packed,name=F_Int32_repeated_packed" json:"F_Int32_repeated_packed,omitempty"` + F_Int64RepeatedPacked []int64 `protobuf:"varint,52,rep,packed,name=F_Int64_repeated_packed" json:"F_Int64_repeated_packed,omitempty"` + F_Fixed32RepeatedPacked []uint32 `protobuf:"fixed32,53,rep,packed,name=F_Fixed32_repeated_packed" json:"F_Fixed32_repeated_packed,omitempty"` + F_Fixed64RepeatedPacked []uint64 `protobuf:"fixed64,54,rep,packed,name=F_Fixed64_repeated_packed" json:"F_Fixed64_repeated_packed,omitempty"` + F_Uint32RepeatedPacked []uint32 `protobuf:"varint,55,rep,packed,name=F_Uint32_repeated_packed" json:"F_Uint32_repeated_packed,omitempty"` + F_Uint64RepeatedPacked []uint64 `protobuf:"varint,56,rep,packed,name=F_Uint64_repeated_packed" json:"F_Uint64_repeated_packed,omitempty"` + F_FloatRepeatedPacked []float32 `protobuf:"fixed32,57,rep,packed,name=F_Float_repeated_packed" json:"F_Float_repeated_packed,omitempty"` + F_DoubleRepeatedPacked []float64 `protobuf:"fixed64,58,rep,packed,name=F_Double_repeated_packed" json:"F_Double_repeated_packed,omitempty"` + F_Sint32RepeatedPacked []int32 `protobuf:"zigzag32,502,rep,packed,name=F_Sint32_repeated_packed" json:"F_Sint32_repeated_packed,omitempty"` + F_Sint64RepeatedPacked []int64 `protobuf:"zigzag64,503,rep,packed,name=F_Sint64_repeated_packed" json:"F_Sint64_repeated_packed,omitempty"` + Requiredgroup *GoTest_RequiredGroup `protobuf:"group,70,req,name=RequiredGroup" json:"requiredgroup,omitempty"` + Repeatedgroup []*GoTest_RepeatedGroup `protobuf:"group,80,rep,name=RepeatedGroup" json:"repeatedgroup,omitempty"` + Optionalgroup *GoTest_OptionalGroup `protobuf:"group,90,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest) Reset() { *m = GoTest{} } +func (m *GoTest) String() string { return proto.CompactTextString(m) } +func (*GoTest) ProtoMessage() {} + +const Default_GoTest_F_BoolDefaulted bool = true +const Default_GoTest_F_Int32Defaulted int32 = 32 +const Default_GoTest_F_Int64Defaulted int64 = 64 +const Default_GoTest_F_Fixed32Defaulted uint32 = 320 +const Default_GoTest_F_Fixed64Defaulted uint64 = 640 +const Default_GoTest_F_Uint32Defaulted uint32 = 3200 +const Default_GoTest_F_Uint64Defaulted uint64 = 6400 +const Default_GoTest_F_FloatDefaulted float32 = 314159 +const Default_GoTest_F_DoubleDefaulted float64 = 271828 +const Default_GoTest_F_StringDefaulted string = "hello, \"world!\"\n" + +var Default_GoTest_F_BytesDefaulted []byte = []byte("Bignose") + +const Default_GoTest_F_Sint32Defaulted int32 = -32 +const Default_GoTest_F_Sint64Defaulted int64 = -64 + +func (m *GoTest) GetKind() GoTest_KIND { + if m != nil && m.Kind != nil { + return *m.Kind + } + return GoTest_VOID +} + +func (m *GoTest) GetTable() string { + if m != nil && m.Table != nil { + return *m.Table + } + return "" +} + +func (m *GoTest) GetParam() int32 { + if m != nil && m.Param != nil { + return *m.Param + } + return 0 +} + +func (m *GoTest) GetRequiredField() *GoTestField { + if m != nil { + return m.RequiredField + } + return nil +} + +func (m *GoTest) GetRepeatedField() []*GoTestField { + if m != nil { + return m.RepeatedField + } + return nil +} + +func (m *GoTest) GetOptionalField() *GoTestField { + if m != nil { + return m.OptionalField + } + return nil +} + +func (m *GoTest) GetF_BoolRequired() bool { + if m != nil && m.F_BoolRequired != nil { + return *m.F_BoolRequired + } + return false +} + +func (m *GoTest) GetF_Int32Required() int32 { + if m != nil && m.F_Int32Required != nil { + return *m.F_Int32Required + } + return 0 +} + +func (m *GoTest) GetF_Int64Required() int64 { + if m != nil && m.F_Int64Required != nil { + return *m.F_Int64Required + } + return 0 +} + +func (m *GoTest) GetF_Fixed32Required() uint32 { + if m != nil && m.F_Fixed32Required != nil { + return *m.F_Fixed32Required + } + return 0 +} + +func (m *GoTest) GetF_Fixed64Required() uint64 { + if m != nil && m.F_Fixed64Required != nil { + return *m.F_Fixed64Required + } + return 0 +} + +func (m *GoTest) GetF_Uint32Required() uint32 { + if m != nil && m.F_Uint32Required != nil { + return *m.F_Uint32Required + } + return 0 +} + +func (m *GoTest) GetF_Uint64Required() uint64 { + if m != nil && m.F_Uint64Required != nil { + return *m.F_Uint64Required + } + return 0 +} + +func (m *GoTest) GetF_FloatRequired() float32 { + if m != nil && m.F_FloatRequired != nil { + return *m.F_FloatRequired + } + return 0 +} + +func (m *GoTest) GetF_DoubleRequired() float64 { + if m != nil && m.F_DoubleRequired != nil { + return *m.F_DoubleRequired + } + return 0 +} + +func (m *GoTest) GetF_StringRequired() string { + if m != nil && m.F_StringRequired != nil { + return *m.F_StringRequired + } + return "" +} + +func (m *GoTest) GetF_BytesRequired() []byte { + if m != nil { + return m.F_BytesRequired + } + return nil +} + +func (m *GoTest) GetF_Sint32Required() int32 { + if m != nil && m.F_Sint32Required != nil { + return *m.F_Sint32Required + } + return 0 +} + +func (m *GoTest) GetF_Sint64Required() int64 { + if m != nil && m.F_Sint64Required != nil { + return *m.F_Sint64Required + } + return 0 +} + +func (m *GoTest) GetF_BoolRepeated() []bool { + if m != nil { + return m.F_BoolRepeated + } + return nil +} + +func (m *GoTest) GetF_Int32Repeated() []int32 { + if m != nil { + return m.F_Int32Repeated + } + return nil +} + +func (m *GoTest) GetF_Int64Repeated() []int64 { + if m != nil { + return m.F_Int64Repeated + } + return nil +} + +func (m *GoTest) GetF_Fixed32Repeated() []uint32 { + if m != nil { + return m.F_Fixed32Repeated + } + return nil +} + +func (m *GoTest) GetF_Fixed64Repeated() []uint64 { + if m != nil { + return m.F_Fixed64Repeated + } + return nil +} + +func (m *GoTest) GetF_Uint32Repeated() []uint32 { + if m != nil { + return m.F_Uint32Repeated + } + return nil +} + +func (m *GoTest) GetF_Uint64Repeated() []uint64 { + if m != nil { + return m.F_Uint64Repeated + } + return nil +} + +func (m *GoTest) GetF_FloatRepeated() []float32 { + if m != nil { + return m.F_FloatRepeated + } + return nil +} + +func (m *GoTest) GetF_DoubleRepeated() []float64 { + if m != nil { + return m.F_DoubleRepeated + } + return nil +} + +func (m *GoTest) GetF_StringRepeated() []string { + if m != nil { + return m.F_StringRepeated + } + return nil +} + +func (m *GoTest) GetF_BytesRepeated() [][]byte { + if m != nil { + return m.F_BytesRepeated + } + return nil +} + +func (m *GoTest) GetF_Sint32Repeated() []int32 { + if m != nil { + return m.F_Sint32Repeated + } + return nil +} + +func (m *GoTest) GetF_Sint64Repeated() []int64 { + if m != nil { + return m.F_Sint64Repeated + } + return nil +} + +func (m *GoTest) GetF_BoolOptional() bool { + if m != nil && m.F_BoolOptional != nil { + return *m.F_BoolOptional + } + return false +} + +func (m *GoTest) GetF_Int32Optional() int32 { + if m != nil && m.F_Int32Optional != nil { + return *m.F_Int32Optional + } + return 0 +} + +func (m *GoTest) GetF_Int64Optional() int64 { + if m != nil && m.F_Int64Optional != nil { + return *m.F_Int64Optional + } + return 0 +} + +func (m *GoTest) GetF_Fixed32Optional() uint32 { + if m != nil && m.F_Fixed32Optional != nil { + return *m.F_Fixed32Optional + } + return 0 +} + +func (m *GoTest) GetF_Fixed64Optional() uint64 { + if m != nil && m.F_Fixed64Optional != nil { + return *m.F_Fixed64Optional + } + return 0 +} + +func (m *GoTest) GetF_Uint32Optional() uint32 { + if m != nil && m.F_Uint32Optional != nil { + return *m.F_Uint32Optional + } + return 0 +} + +func (m *GoTest) GetF_Uint64Optional() uint64 { + if m != nil && m.F_Uint64Optional != nil { + return *m.F_Uint64Optional + } + return 0 +} + +func (m *GoTest) GetF_FloatOptional() float32 { + if m != nil && m.F_FloatOptional != nil { + return *m.F_FloatOptional + } + return 0 +} + +func (m *GoTest) GetF_DoubleOptional() float64 { + if m != nil && m.F_DoubleOptional != nil { + return *m.F_DoubleOptional + } + return 0 +} + +func (m *GoTest) GetF_StringOptional() string { + if m != nil && m.F_StringOptional != nil { + return *m.F_StringOptional + } + return "" +} + +func (m *GoTest) GetF_BytesOptional() []byte { + if m != nil { + return m.F_BytesOptional + } + return nil +} + +func (m *GoTest) GetF_Sint32Optional() int32 { + if m != nil && m.F_Sint32Optional != nil { + return *m.F_Sint32Optional + } + return 0 +} + +func (m *GoTest) GetF_Sint64Optional() int64 { + if m != nil && m.F_Sint64Optional != nil { + return *m.F_Sint64Optional + } + return 0 +} + +func (m *GoTest) GetF_BoolDefaulted() bool { + if m != nil && m.F_BoolDefaulted != nil { + return *m.F_BoolDefaulted + } + return Default_GoTest_F_BoolDefaulted +} + +func (m *GoTest) GetF_Int32Defaulted() int32 { + if m != nil && m.F_Int32Defaulted != nil { + return *m.F_Int32Defaulted + } + return Default_GoTest_F_Int32Defaulted +} + +func (m *GoTest) GetF_Int64Defaulted() int64 { + if m != nil && m.F_Int64Defaulted != nil { + return *m.F_Int64Defaulted + } + return Default_GoTest_F_Int64Defaulted +} + +func (m *GoTest) GetF_Fixed32Defaulted() uint32 { + if m != nil && m.F_Fixed32Defaulted != nil { + return *m.F_Fixed32Defaulted + } + return Default_GoTest_F_Fixed32Defaulted +} + +func (m *GoTest) GetF_Fixed64Defaulted() uint64 { + if m != nil && m.F_Fixed64Defaulted != nil { + return *m.F_Fixed64Defaulted + } + return Default_GoTest_F_Fixed64Defaulted +} + +func (m *GoTest) GetF_Uint32Defaulted() uint32 { + if m != nil && m.F_Uint32Defaulted != nil { + return *m.F_Uint32Defaulted + } + return Default_GoTest_F_Uint32Defaulted +} + +func (m *GoTest) GetF_Uint64Defaulted() uint64 { + if m != nil && m.F_Uint64Defaulted != nil { + return *m.F_Uint64Defaulted + } + return Default_GoTest_F_Uint64Defaulted +} + +func (m *GoTest) GetF_FloatDefaulted() float32 { + if m != nil && m.F_FloatDefaulted != nil { + return *m.F_FloatDefaulted + } + return Default_GoTest_F_FloatDefaulted +} + +func (m *GoTest) GetF_DoubleDefaulted() float64 { + if m != nil && m.F_DoubleDefaulted != nil { + return *m.F_DoubleDefaulted + } + return Default_GoTest_F_DoubleDefaulted +} + +func (m *GoTest) GetF_StringDefaulted() string { + if m != nil && m.F_StringDefaulted != nil { + return *m.F_StringDefaulted + } + return Default_GoTest_F_StringDefaulted +} + +func (m *GoTest) GetF_BytesDefaulted() []byte { + if m != nil && m.F_BytesDefaulted != nil { + return m.F_BytesDefaulted + } + return append([]byte(nil), Default_GoTest_F_BytesDefaulted...) +} + +func (m *GoTest) GetF_Sint32Defaulted() int32 { + if m != nil && m.F_Sint32Defaulted != nil { + return *m.F_Sint32Defaulted + } + return Default_GoTest_F_Sint32Defaulted +} + +func (m *GoTest) GetF_Sint64Defaulted() int64 { + if m != nil && m.F_Sint64Defaulted != nil { + return *m.F_Sint64Defaulted + } + return Default_GoTest_F_Sint64Defaulted +} + +func (m *GoTest) GetF_BoolRepeatedPacked() []bool { + if m != nil { + return m.F_BoolRepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Int32RepeatedPacked() []int32 { + if m != nil { + return m.F_Int32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Int64RepeatedPacked() []int64 { + if m != nil { + return m.F_Int64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Fixed32RepeatedPacked() []uint32 { + if m != nil { + return m.F_Fixed32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Fixed64RepeatedPacked() []uint64 { + if m != nil { + return m.F_Fixed64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Uint32RepeatedPacked() []uint32 { + if m != nil { + return m.F_Uint32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Uint64RepeatedPacked() []uint64 { + if m != nil { + return m.F_Uint64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_FloatRepeatedPacked() []float32 { + if m != nil { + return m.F_FloatRepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_DoubleRepeatedPacked() []float64 { + if m != nil { + return m.F_DoubleRepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Sint32RepeatedPacked() []int32 { + if m != nil { + return m.F_Sint32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Sint64RepeatedPacked() []int64 { + if m != nil { + return m.F_Sint64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetRequiredgroup() *GoTest_RequiredGroup { + if m != nil { + return m.Requiredgroup + } + return nil +} + +func (m *GoTest) GetRepeatedgroup() []*GoTest_RepeatedGroup { + if m != nil { + return m.Repeatedgroup + } + return nil +} + +func (m *GoTest) GetOptionalgroup() *GoTest_OptionalGroup { + if m != nil { + return m.Optionalgroup + } + return nil +} + +// Required, repeated, and optional groups. +type GoTest_RequiredGroup struct { + RequiredField *string `protobuf:"bytes,71,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest_RequiredGroup) Reset() { *m = GoTest_RequiredGroup{} } +func (m *GoTest_RequiredGroup) String() string { return proto.CompactTextString(m) } +func (*GoTest_RequiredGroup) ProtoMessage() {} + +func (m *GoTest_RequiredGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" +} + +type GoTest_RepeatedGroup struct { + RequiredField *string `protobuf:"bytes,81,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest_RepeatedGroup) Reset() { *m = GoTest_RepeatedGroup{} } +func (m *GoTest_RepeatedGroup) String() string { return proto.CompactTextString(m) } +func (*GoTest_RepeatedGroup) ProtoMessage() {} + +func (m *GoTest_RepeatedGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" +} + +type GoTest_OptionalGroup struct { + RequiredField *string `protobuf:"bytes,91,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest_OptionalGroup) Reset() { *m = GoTest_OptionalGroup{} } +func (m *GoTest_OptionalGroup) String() string { return proto.CompactTextString(m) } +func (*GoTest_OptionalGroup) ProtoMessage() {} + +func (m *GoTest_OptionalGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" +} + +// For testing skipping of unrecognized fields. +// Numbers are all big, larger than tag numbers in GoTestField, +// the message used in the corresponding test. +type GoSkipTest struct { + SkipInt32 *int32 `protobuf:"varint,11,req,name=skip_int32" json:"skip_int32,omitempty"` + SkipFixed32 *uint32 `protobuf:"fixed32,12,req,name=skip_fixed32" json:"skip_fixed32,omitempty"` + SkipFixed64 *uint64 `protobuf:"fixed64,13,req,name=skip_fixed64" json:"skip_fixed64,omitempty"` + SkipString *string `protobuf:"bytes,14,req,name=skip_string" json:"skip_string,omitempty"` + Skipgroup *GoSkipTest_SkipGroup `protobuf:"group,15,req,name=SkipGroup" json:"skipgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoSkipTest) Reset() { *m = GoSkipTest{} } +func (m *GoSkipTest) String() string { return proto.CompactTextString(m) } +func (*GoSkipTest) ProtoMessage() {} + +func (m *GoSkipTest) GetSkipInt32() int32 { + if m != nil && m.SkipInt32 != nil { + return *m.SkipInt32 + } + return 0 +} + +func (m *GoSkipTest) GetSkipFixed32() uint32 { + if m != nil && m.SkipFixed32 != nil { + return *m.SkipFixed32 + } + return 0 +} + +func (m *GoSkipTest) GetSkipFixed64() uint64 { + if m != nil && m.SkipFixed64 != nil { + return *m.SkipFixed64 + } + return 0 +} + +func (m *GoSkipTest) GetSkipString() string { + if m != nil && m.SkipString != nil { + return *m.SkipString + } + return "" +} + +func (m *GoSkipTest) GetSkipgroup() *GoSkipTest_SkipGroup { + if m != nil { + return m.Skipgroup + } + return nil +} + +type GoSkipTest_SkipGroup struct { + GroupInt32 *int32 `protobuf:"varint,16,req,name=group_int32" json:"group_int32,omitempty"` + GroupString *string `protobuf:"bytes,17,req,name=group_string" json:"group_string,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoSkipTest_SkipGroup) Reset() { *m = GoSkipTest_SkipGroup{} } +func (m *GoSkipTest_SkipGroup) String() string { return proto.CompactTextString(m) } +func (*GoSkipTest_SkipGroup) ProtoMessage() {} + +func (m *GoSkipTest_SkipGroup) GetGroupInt32() int32 { + if m != nil && m.GroupInt32 != nil { + return *m.GroupInt32 + } + return 0 +} + +func (m *GoSkipTest_SkipGroup) GetGroupString() string { + if m != nil && m.GroupString != nil { + return *m.GroupString + } + return "" +} + +// For testing packed/non-packed decoder switching. +// A serialized instance of one should be deserializable as the other. +type NonPackedTest struct { + A []int32 `protobuf:"varint,1,rep,name=a" json:"a,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *NonPackedTest) Reset() { *m = NonPackedTest{} } +func (m *NonPackedTest) String() string { return proto.CompactTextString(m) } +func (*NonPackedTest) ProtoMessage() {} + +func (m *NonPackedTest) GetA() []int32 { + if m != nil { + return m.A + } + return nil +} + +type PackedTest struct { + B []int32 `protobuf:"varint,1,rep,packed,name=b" json:"b,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *PackedTest) Reset() { *m = PackedTest{} } +func (m *PackedTest) String() string { return proto.CompactTextString(m) } +func (*PackedTest) ProtoMessage() {} + +func (m *PackedTest) GetB() []int32 { + if m != nil { + return m.B + } + return nil +} + +type MaxTag struct { + // Maximum possible tag number. + LastField *string `protobuf:"bytes,536870911,opt,name=last_field" json:"last_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MaxTag) Reset() { *m = MaxTag{} } +func (m *MaxTag) String() string { return proto.CompactTextString(m) } +func (*MaxTag) ProtoMessage() {} + +func (m *MaxTag) GetLastField() string { + if m != nil && m.LastField != nil { + return *m.LastField + } + return "" +} + +type OldMessage struct { + Nested *OldMessage_Nested `protobuf:"bytes,1,opt,name=nested" json:"nested,omitempty"` + Num *int32 `protobuf:"varint,2,opt,name=num" json:"num,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OldMessage) Reset() { *m = OldMessage{} } +func (m *OldMessage) String() string { return proto.CompactTextString(m) } +func (*OldMessage) ProtoMessage() {} + +func (m *OldMessage) GetNested() *OldMessage_Nested { + if m != nil { + return m.Nested + } + return nil +} + +func (m *OldMessage) GetNum() int32 { + if m != nil && m.Num != nil { + return *m.Num + } + return 0 +} + +type OldMessage_Nested struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OldMessage_Nested) Reset() { *m = OldMessage_Nested{} } +func (m *OldMessage_Nested) String() string { return proto.CompactTextString(m) } +func (*OldMessage_Nested) ProtoMessage() {} + +func (m *OldMessage_Nested) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +// NewMessage is wire compatible with OldMessage; +// imagine it as a future version. +type NewMessage struct { + Nested *NewMessage_Nested `protobuf:"bytes,1,opt,name=nested" json:"nested,omitempty"` + // This is an int32 in OldMessage. + Num *int64 `protobuf:"varint,2,opt,name=num" json:"num,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *NewMessage) Reset() { *m = NewMessage{} } +func (m *NewMessage) String() string { return proto.CompactTextString(m) } +func (*NewMessage) ProtoMessage() {} + +func (m *NewMessage) GetNested() *NewMessage_Nested { + if m != nil { + return m.Nested + } + return nil +} + +func (m *NewMessage) GetNum() int64 { + if m != nil && m.Num != nil { + return *m.Num + } + return 0 +} + +type NewMessage_Nested struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + FoodGroup *string `protobuf:"bytes,2,opt,name=food_group" json:"food_group,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *NewMessage_Nested) Reset() { *m = NewMessage_Nested{} } +func (m *NewMessage_Nested) String() string { return proto.CompactTextString(m) } +func (*NewMessage_Nested) ProtoMessage() {} + +func (m *NewMessage_Nested) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *NewMessage_Nested) GetFoodGroup() string { + if m != nil && m.FoodGroup != nil { + return *m.FoodGroup + } + return "" +} + +type InnerMessage struct { + Host *string `protobuf:"bytes,1,req,name=host" json:"host,omitempty"` + Port *int32 `protobuf:"varint,2,opt,name=port,def=4000" json:"port,omitempty"` + Connected *bool `protobuf:"varint,3,opt,name=connected" json:"connected,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *InnerMessage) Reset() { *m = InnerMessage{} } +func (m *InnerMessage) String() string { return proto.CompactTextString(m) } +func (*InnerMessage) ProtoMessage() {} + +const Default_InnerMessage_Port int32 = 4000 + +func (m *InnerMessage) GetHost() string { + if m != nil && m.Host != nil { + return *m.Host + } + return "" +} + +func (m *InnerMessage) GetPort() int32 { + if m != nil && m.Port != nil { + return *m.Port + } + return Default_InnerMessage_Port +} + +func (m *InnerMessage) GetConnected() bool { + if m != nil && m.Connected != nil { + return *m.Connected + } + return false +} + +type OtherMessage struct { + Key *int64 `protobuf:"varint,1,opt,name=key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` + Weight *float32 `protobuf:"fixed32,3,opt,name=weight" json:"weight,omitempty"` + Inner *InnerMessage `protobuf:"bytes,4,opt,name=inner" json:"inner,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OtherMessage) Reset() { *m = OtherMessage{} } +func (m *OtherMessage) String() string { return proto.CompactTextString(m) } +func (*OtherMessage) ProtoMessage() {} + +func (m *OtherMessage) GetKey() int64 { + if m != nil && m.Key != nil { + return *m.Key + } + return 0 +} + +func (m *OtherMessage) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *OtherMessage) GetWeight() float32 { + if m != nil && m.Weight != nil { + return *m.Weight + } + return 0 +} + +func (m *OtherMessage) GetInner() *InnerMessage { + if m != nil { + return m.Inner + } + return nil +} + +type MyMessage struct { + Count *int32 `protobuf:"varint,1,req,name=count" json:"count,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` + Quote *string `protobuf:"bytes,3,opt,name=quote" json:"quote,omitempty"` + Pet []string `protobuf:"bytes,4,rep,name=pet" json:"pet,omitempty"` + Inner *InnerMessage `protobuf:"bytes,5,opt,name=inner" json:"inner,omitempty"` + Others []*OtherMessage `protobuf:"bytes,6,rep,name=others" json:"others,omitempty"` + RepInner []*InnerMessage `protobuf:"bytes,12,rep,name=rep_inner" json:"rep_inner,omitempty"` + Bikeshed *MyMessage_Color `protobuf:"varint,7,opt,name=bikeshed,enum=testdata.MyMessage_Color" json:"bikeshed,omitempty"` + Somegroup *MyMessage_SomeGroup `protobuf:"group,8,opt,name=SomeGroup" json:"somegroup,omitempty"` + // This field becomes [][]byte in the generated code. + RepBytes [][]byte `protobuf:"bytes,10,rep,name=rep_bytes" json:"rep_bytes,omitempty"` + Bigfloat *float64 `protobuf:"fixed64,11,opt,name=bigfloat" json:"bigfloat,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MyMessage) Reset() { *m = MyMessage{} } +func (m *MyMessage) String() string { return proto.CompactTextString(m) } +func (*MyMessage) ProtoMessage() {} + +var extRange_MyMessage = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*MyMessage) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MyMessage +} +func (m *MyMessage) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *MyMessage) GetCount() int32 { + if m != nil && m.Count != nil { + return *m.Count + } + return 0 +} + +func (m *MyMessage) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *MyMessage) GetQuote() string { + if m != nil && m.Quote != nil { + return *m.Quote + } + return "" +} + +func (m *MyMessage) GetPet() []string { + if m != nil { + return m.Pet + } + return nil +} + +func (m *MyMessage) GetInner() *InnerMessage { + if m != nil { + return m.Inner + } + return nil +} + +func (m *MyMessage) GetOthers() []*OtherMessage { + if m != nil { + return m.Others + } + return nil +} + +func (m *MyMessage) GetRepInner() []*InnerMessage { + if m != nil { + return m.RepInner + } + return nil +} + +func (m *MyMessage) GetBikeshed() MyMessage_Color { + if m != nil && m.Bikeshed != nil { + return *m.Bikeshed + } + return MyMessage_RED +} + +func (m *MyMessage) GetSomegroup() *MyMessage_SomeGroup { + if m != nil { + return m.Somegroup + } + return nil +} + +func (m *MyMessage) GetRepBytes() [][]byte { + if m != nil { + return m.RepBytes + } + return nil +} + +func (m *MyMessage) GetBigfloat() float64 { + if m != nil && m.Bigfloat != nil { + return *m.Bigfloat + } + return 0 +} + +type MyMessage_SomeGroup struct { + GroupField *int32 `protobuf:"varint,9,opt,name=group_field" json:"group_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MyMessage_SomeGroup) Reset() { *m = MyMessage_SomeGroup{} } +func (m *MyMessage_SomeGroup) String() string { return proto.CompactTextString(m) } +func (*MyMessage_SomeGroup) ProtoMessage() {} + +func (m *MyMessage_SomeGroup) GetGroupField() int32 { + if m != nil && m.GroupField != nil { + return *m.GroupField + } + return 0 +} + +type Ext struct { + Data *string `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Ext) Reset() { *m = Ext{} } +func (m *Ext) String() string { return proto.CompactTextString(m) } +func (*Ext) ProtoMessage() {} + +func (m *Ext) GetData() string { + if m != nil && m.Data != nil { + return *m.Data + } + return "" +} + +var E_Ext_More = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: (*Ext)(nil), + Field: 103, + Name: "testdata.Ext.more", + Tag: "bytes,103,opt,name=more", +} + +var E_Ext_Text = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: (*string)(nil), + Field: 104, + Name: "testdata.Ext.text", + Tag: "bytes,104,opt,name=text", +} + +var E_Ext_Number = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 105, + Name: "testdata.Ext.number", + Tag: "varint,105,opt,name=number", +} + +type DefaultsMessage struct { + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DefaultsMessage) Reset() { *m = DefaultsMessage{} } +func (m *DefaultsMessage) String() string { return proto.CompactTextString(m) } +func (*DefaultsMessage) ProtoMessage() {} + +var extRange_DefaultsMessage = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*DefaultsMessage) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_DefaultsMessage +} +func (m *DefaultsMessage) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +type MyMessageSet struct { + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MyMessageSet) Reset() { *m = MyMessageSet{} } +func (m *MyMessageSet) String() string { return proto.CompactTextString(m) } +func (*MyMessageSet) ProtoMessage() {} + +func (m *MyMessageSet) Marshal() ([]byte, error) { + return proto.MarshalMessageSet(m.ExtensionMap()) +} +func (m *MyMessageSet) Unmarshal(buf []byte) error { + return proto.UnmarshalMessageSet(buf, m.ExtensionMap()) +} +func (m *MyMessageSet) MarshalJSON() ([]byte, error) { + return proto.MarshalMessageSetJSON(m.XXX_extensions) +} +func (m *MyMessageSet) UnmarshalJSON(buf []byte) error { + return proto.UnmarshalMessageSetJSON(buf, m.XXX_extensions) +} + +// ensure MyMessageSet satisfies proto.Marshaler and proto.Unmarshaler +var _ proto.Marshaler = (*MyMessageSet)(nil) +var _ proto.Unmarshaler = (*MyMessageSet)(nil) + +var extRange_MyMessageSet = []proto.ExtensionRange{ + {100, 2147483646}, +} + +func (*MyMessageSet) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MyMessageSet +} +func (m *MyMessageSet) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +type Empty struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto.CompactTextString(m) } +func (*Empty) ProtoMessage() {} + +type MessageList struct { + Message []*MessageList_Message `protobuf:"group,1,rep" json:"message,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageList) Reset() { *m = MessageList{} } +func (m *MessageList) String() string { return proto.CompactTextString(m) } +func (*MessageList) ProtoMessage() {} + +func (m *MessageList) GetMessage() []*MessageList_Message { + if m != nil { + return m.Message + } + return nil +} + +type MessageList_Message struct { + Name *string `protobuf:"bytes,2,req,name=name" json:"name,omitempty"` + Count *int32 `protobuf:"varint,3,req,name=count" json:"count,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageList_Message) Reset() { *m = MessageList_Message{} } +func (m *MessageList_Message) String() string { return proto.CompactTextString(m) } +func (*MessageList_Message) ProtoMessage() {} + +func (m *MessageList_Message) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *MessageList_Message) GetCount() int32 { + if m != nil && m.Count != nil { + return *m.Count + } + return 0 +} + +type Strings struct { + StringField *string `protobuf:"bytes,1,opt,name=string_field" json:"string_field,omitempty"` + BytesField []byte `protobuf:"bytes,2,opt,name=bytes_field" json:"bytes_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Strings) Reset() { *m = Strings{} } +func (m *Strings) String() string { return proto.CompactTextString(m) } +func (*Strings) ProtoMessage() {} + +func (m *Strings) GetStringField() string { + if m != nil && m.StringField != nil { + return *m.StringField + } + return "" +} + +func (m *Strings) GetBytesField() []byte { + if m != nil { + return m.BytesField + } + return nil +} + +type Defaults struct { + // Default-valued fields of all basic types. + // Same as GoTest, but copied here to make testing easier. + F_Bool *bool `protobuf:"varint,1,opt,def=1" json:"F_Bool,omitempty"` + F_Int32 *int32 `protobuf:"varint,2,opt,def=32" json:"F_Int32,omitempty"` + F_Int64 *int64 `protobuf:"varint,3,opt,def=64" json:"F_Int64,omitempty"` + F_Fixed32 *uint32 `protobuf:"fixed32,4,opt,def=320" json:"F_Fixed32,omitempty"` + F_Fixed64 *uint64 `protobuf:"fixed64,5,opt,def=640" json:"F_Fixed64,omitempty"` + F_Uint32 *uint32 `protobuf:"varint,6,opt,def=3200" json:"F_Uint32,omitempty"` + F_Uint64 *uint64 `protobuf:"varint,7,opt,def=6400" json:"F_Uint64,omitempty"` + F_Float *float32 `protobuf:"fixed32,8,opt,def=314159" json:"F_Float,omitempty"` + F_Double *float64 `protobuf:"fixed64,9,opt,def=271828" json:"F_Double,omitempty"` + F_String *string `protobuf:"bytes,10,opt,def=hello, \"world!\"\n" json:"F_String,omitempty"` + F_Bytes []byte `protobuf:"bytes,11,opt,def=Bignose" json:"F_Bytes,omitempty"` + F_Sint32 *int32 `protobuf:"zigzag32,12,opt,def=-32" json:"F_Sint32,omitempty"` + F_Sint64 *int64 `protobuf:"zigzag64,13,opt,def=-64" json:"F_Sint64,omitempty"` + F_Enum *Defaults_Color `protobuf:"varint,14,opt,enum=testdata.Defaults_Color,def=1" json:"F_Enum,omitempty"` + // More fields with crazy defaults. + F_Pinf *float32 `protobuf:"fixed32,15,opt,def=inf" json:"F_Pinf,omitempty"` + F_Ninf *float32 `protobuf:"fixed32,16,opt,def=-inf" json:"F_Ninf,omitempty"` + F_Nan *float32 `protobuf:"fixed32,17,opt,def=nan" json:"F_Nan,omitempty"` + // Sub-message. + Sub *SubDefaults `protobuf:"bytes,18,opt,name=sub" json:"sub,omitempty"` + // Redundant but explicit defaults. + StrZero *string `protobuf:"bytes,19,opt,name=str_zero,def=" json:"str_zero,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Defaults) Reset() { *m = Defaults{} } +func (m *Defaults) String() string { return proto.CompactTextString(m) } +func (*Defaults) ProtoMessage() {} + +const Default_Defaults_F_Bool bool = true +const Default_Defaults_F_Int32 int32 = 32 +const Default_Defaults_F_Int64 int64 = 64 +const Default_Defaults_F_Fixed32 uint32 = 320 +const Default_Defaults_F_Fixed64 uint64 = 640 +const Default_Defaults_F_Uint32 uint32 = 3200 +const Default_Defaults_F_Uint64 uint64 = 6400 +const Default_Defaults_F_Float float32 = 314159 +const Default_Defaults_F_Double float64 = 271828 +const Default_Defaults_F_String string = "hello, \"world!\"\n" + +var Default_Defaults_F_Bytes []byte = []byte("Bignose") + +const Default_Defaults_F_Sint32 int32 = -32 +const Default_Defaults_F_Sint64 int64 = -64 +const Default_Defaults_F_Enum Defaults_Color = Defaults_GREEN + +var Default_Defaults_F_Pinf float32 = float32(math.Inf(1)) +var Default_Defaults_F_Ninf float32 = float32(math.Inf(-1)) +var Default_Defaults_F_Nan float32 = float32(math.NaN()) + +func (m *Defaults) GetF_Bool() bool { + if m != nil && m.F_Bool != nil { + return *m.F_Bool + } + return Default_Defaults_F_Bool +} + +func (m *Defaults) GetF_Int32() int32 { + if m != nil && m.F_Int32 != nil { + return *m.F_Int32 + } + return Default_Defaults_F_Int32 +} + +func (m *Defaults) GetF_Int64() int64 { + if m != nil && m.F_Int64 != nil { + return *m.F_Int64 + } + return Default_Defaults_F_Int64 +} + +func (m *Defaults) GetF_Fixed32() uint32 { + if m != nil && m.F_Fixed32 != nil { + return *m.F_Fixed32 + } + return Default_Defaults_F_Fixed32 +} + +func (m *Defaults) GetF_Fixed64() uint64 { + if m != nil && m.F_Fixed64 != nil { + return *m.F_Fixed64 + } + return Default_Defaults_F_Fixed64 +} + +func (m *Defaults) GetF_Uint32() uint32 { + if m != nil && m.F_Uint32 != nil { + return *m.F_Uint32 + } + return Default_Defaults_F_Uint32 +} + +func (m *Defaults) GetF_Uint64() uint64 { + if m != nil && m.F_Uint64 != nil { + return *m.F_Uint64 + } + return Default_Defaults_F_Uint64 +} + +func (m *Defaults) GetF_Float() float32 { + if m != nil && m.F_Float != nil { + return *m.F_Float + } + return Default_Defaults_F_Float +} + +func (m *Defaults) GetF_Double() float64 { + if m != nil && m.F_Double != nil { + return *m.F_Double + } + return Default_Defaults_F_Double +} + +func (m *Defaults) GetF_String() string { + if m != nil && m.F_String != nil { + return *m.F_String + } + return Default_Defaults_F_String +} + +func (m *Defaults) GetF_Bytes() []byte { + if m != nil && m.F_Bytes != nil { + return m.F_Bytes + } + return append([]byte(nil), Default_Defaults_F_Bytes...) +} + +func (m *Defaults) GetF_Sint32() int32 { + if m != nil && m.F_Sint32 != nil { + return *m.F_Sint32 + } + return Default_Defaults_F_Sint32 +} + +func (m *Defaults) GetF_Sint64() int64 { + if m != nil && m.F_Sint64 != nil { + return *m.F_Sint64 + } + return Default_Defaults_F_Sint64 +} + +func (m *Defaults) GetF_Enum() Defaults_Color { + if m != nil && m.F_Enum != nil { + return *m.F_Enum + } + return Default_Defaults_F_Enum +} + +func (m *Defaults) GetF_Pinf() float32 { + if m != nil && m.F_Pinf != nil { + return *m.F_Pinf + } + return Default_Defaults_F_Pinf +} + +func (m *Defaults) GetF_Ninf() float32 { + if m != nil && m.F_Ninf != nil { + return *m.F_Ninf + } + return Default_Defaults_F_Ninf +} + +func (m *Defaults) GetF_Nan() float32 { + if m != nil && m.F_Nan != nil { + return *m.F_Nan + } + return Default_Defaults_F_Nan +} + +func (m *Defaults) GetSub() *SubDefaults { + if m != nil { + return m.Sub + } + return nil +} + +func (m *Defaults) GetStrZero() string { + if m != nil && m.StrZero != nil { + return *m.StrZero + } + return "" +} + +type SubDefaults struct { + N *int64 `protobuf:"varint,1,opt,name=n,def=7" json:"n,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SubDefaults) Reset() { *m = SubDefaults{} } +func (m *SubDefaults) String() string { return proto.CompactTextString(m) } +func (*SubDefaults) ProtoMessage() {} + +const Default_SubDefaults_N int64 = 7 + +func (m *SubDefaults) GetN() int64 { + if m != nil && m.N != nil { + return *m.N + } + return Default_SubDefaults_N +} + +type RepeatedEnum struct { + Color []RepeatedEnum_Color `protobuf:"varint,1,rep,name=color,enum=testdata.RepeatedEnum_Color" json:"color,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *RepeatedEnum) Reset() { *m = RepeatedEnum{} } +func (m *RepeatedEnum) String() string { return proto.CompactTextString(m) } +func (*RepeatedEnum) ProtoMessage() {} + +func (m *RepeatedEnum) GetColor() []RepeatedEnum_Color { + if m != nil { + return m.Color + } + return nil +} + +type MoreRepeated struct { + Bools []bool `protobuf:"varint,1,rep,name=bools" json:"bools,omitempty"` + BoolsPacked []bool `protobuf:"varint,2,rep,packed,name=bools_packed" json:"bools_packed,omitempty"` + Ints []int32 `protobuf:"varint,3,rep,name=ints" json:"ints,omitempty"` + IntsPacked []int32 `protobuf:"varint,4,rep,packed,name=ints_packed" json:"ints_packed,omitempty"` + Int64SPacked []int64 `protobuf:"varint,7,rep,packed,name=int64s_packed" json:"int64s_packed,omitempty"` + Strings []string `protobuf:"bytes,5,rep,name=strings" json:"strings,omitempty"` + Fixeds []uint32 `protobuf:"fixed32,6,rep,name=fixeds" json:"fixeds,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MoreRepeated) Reset() { *m = MoreRepeated{} } +func (m *MoreRepeated) String() string { return proto.CompactTextString(m) } +func (*MoreRepeated) ProtoMessage() {} + +func (m *MoreRepeated) GetBools() []bool { + if m != nil { + return m.Bools + } + return nil +} + +func (m *MoreRepeated) GetBoolsPacked() []bool { + if m != nil { + return m.BoolsPacked + } + return nil +} + +func (m *MoreRepeated) GetInts() []int32 { + if m != nil { + return m.Ints + } + return nil +} + +func (m *MoreRepeated) GetIntsPacked() []int32 { + if m != nil { + return m.IntsPacked + } + return nil +} + +func (m *MoreRepeated) GetInt64SPacked() []int64 { + if m != nil { + return m.Int64SPacked + } + return nil +} + +func (m *MoreRepeated) GetStrings() []string { + if m != nil { + return m.Strings + } + return nil +} + +func (m *MoreRepeated) GetFixeds() []uint32 { + if m != nil { + return m.Fixeds + } + return nil +} + +type GroupOld struct { + G *GroupOld_G `protobuf:"group,101,opt" json:"g,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupOld) Reset() { *m = GroupOld{} } +func (m *GroupOld) String() string { return proto.CompactTextString(m) } +func (*GroupOld) ProtoMessage() {} + +func (m *GroupOld) GetG() *GroupOld_G { + if m != nil { + return m.G + } + return nil +} + +type GroupOld_G struct { + X *int32 `protobuf:"varint,2,opt,name=x" json:"x,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupOld_G) Reset() { *m = GroupOld_G{} } +func (m *GroupOld_G) String() string { return proto.CompactTextString(m) } +func (*GroupOld_G) ProtoMessage() {} + +func (m *GroupOld_G) GetX() int32 { + if m != nil && m.X != nil { + return *m.X + } + return 0 +} + +type GroupNew struct { + G *GroupNew_G `protobuf:"group,101,opt" json:"g,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupNew) Reset() { *m = GroupNew{} } +func (m *GroupNew) String() string { return proto.CompactTextString(m) } +func (*GroupNew) ProtoMessage() {} + +func (m *GroupNew) GetG() *GroupNew_G { + if m != nil { + return m.G + } + return nil +} + +type GroupNew_G struct { + X *int32 `protobuf:"varint,2,opt,name=x" json:"x,omitempty"` + Y *int32 `protobuf:"varint,3,opt,name=y" json:"y,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupNew_G) Reset() { *m = GroupNew_G{} } +func (m *GroupNew_G) String() string { return proto.CompactTextString(m) } +func (*GroupNew_G) ProtoMessage() {} + +func (m *GroupNew_G) GetX() int32 { + if m != nil && m.X != nil { + return *m.X + } + return 0 +} + +func (m *GroupNew_G) GetY() int32 { + if m != nil && m.Y != nil { + return *m.Y + } + return 0 +} + +type FloatingPoint struct { + F *float64 `protobuf:"fixed64,1,req,name=f" json:"f,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FloatingPoint) Reset() { *m = FloatingPoint{} } +func (m *FloatingPoint) String() string { return proto.CompactTextString(m) } +func (*FloatingPoint) ProtoMessage() {} + +func (m *FloatingPoint) GetF() float64 { + if m != nil && m.F != nil { + return *m.F + } + return 0 +} + +type MessageWithMap struct { + NameMapping map[int32]string `protobuf:"bytes,1,rep,name=name_mapping" json:"name_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MsgMapping map[int64]*FloatingPoint `protobuf:"bytes,2,rep,name=msg_mapping" json:"msg_mapping,omitempty" protobuf_key:"zigzag64,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + ByteMapping map[bool][]byte `protobuf:"bytes,3,rep,name=byte_mapping" json:"byte_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + StrToStr map[string]string `protobuf:"bytes,4,rep,name=str_to_str" json:"str_to_str,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageWithMap) Reset() { *m = MessageWithMap{} } +func (m *MessageWithMap) String() string { return proto.CompactTextString(m) } +func (*MessageWithMap) ProtoMessage() {} + +func (m *MessageWithMap) GetNameMapping() map[int32]string { + if m != nil { + return m.NameMapping + } + return nil +} + +func (m *MessageWithMap) GetMsgMapping() map[int64]*FloatingPoint { + if m != nil { + return m.MsgMapping + } + return nil +} + +func (m *MessageWithMap) GetByteMapping() map[bool][]byte { + if m != nil { + return m.ByteMapping + } + return nil +} + +func (m *MessageWithMap) GetStrToStr() map[string]string { + if m != nil { + return m.StrToStr + } + return nil +} + +var E_Greeting = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: ([]string)(nil), + Field: 106, + Name: "testdata.greeting", + Tag: "bytes,106,rep,name=greeting", +} + +var E_NoDefaultDouble = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float64)(nil), + Field: 101, + Name: "testdata.no_default_double", + Tag: "fixed64,101,opt,name=no_default_double", +} + +var E_NoDefaultFloat = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float32)(nil), + Field: 102, + Name: "testdata.no_default_float", + Tag: "fixed32,102,opt,name=no_default_float", +} + +var E_NoDefaultInt32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 103, + Name: "testdata.no_default_int32", + Tag: "varint,103,opt,name=no_default_int32", +} + +var E_NoDefaultInt64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 104, + Name: "testdata.no_default_int64", + Tag: "varint,104,opt,name=no_default_int64", +} + +var E_NoDefaultUint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 105, + Name: "testdata.no_default_uint32", + Tag: "varint,105,opt,name=no_default_uint32", +} + +var E_NoDefaultUint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 106, + Name: "testdata.no_default_uint64", + Tag: "varint,106,opt,name=no_default_uint64", +} + +var E_NoDefaultSint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 107, + Name: "testdata.no_default_sint32", + Tag: "zigzag32,107,opt,name=no_default_sint32", +} + +var E_NoDefaultSint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 108, + Name: "testdata.no_default_sint64", + Tag: "zigzag64,108,opt,name=no_default_sint64", +} + +var E_NoDefaultFixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 109, + Name: "testdata.no_default_fixed32", + Tag: "fixed32,109,opt,name=no_default_fixed32", +} + +var E_NoDefaultFixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 110, + Name: "testdata.no_default_fixed64", + Tag: "fixed64,110,opt,name=no_default_fixed64", +} + +var E_NoDefaultSfixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 111, + Name: "testdata.no_default_sfixed32", + Tag: "fixed32,111,opt,name=no_default_sfixed32", +} + +var E_NoDefaultSfixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 112, + Name: "testdata.no_default_sfixed64", + Tag: "fixed64,112,opt,name=no_default_sfixed64", +} + +var E_NoDefaultBool = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*bool)(nil), + Field: 113, + Name: "testdata.no_default_bool", + Tag: "varint,113,opt,name=no_default_bool", +} + +var E_NoDefaultString = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*string)(nil), + Field: 114, + Name: "testdata.no_default_string", + Tag: "bytes,114,opt,name=no_default_string", +} + +var E_NoDefaultBytes = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: ([]byte)(nil), + Field: 115, + Name: "testdata.no_default_bytes", + Tag: "bytes,115,opt,name=no_default_bytes", +} + +var E_NoDefaultEnum = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*DefaultsMessage_DefaultsEnum)(nil), + Field: 116, + Name: "testdata.no_default_enum", + Tag: "varint,116,opt,name=no_default_enum,enum=testdata.DefaultsMessage_DefaultsEnum", +} + +var E_DefaultDouble = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float64)(nil), + Field: 201, + Name: "testdata.default_double", + Tag: "fixed64,201,opt,name=default_double,def=3.1415", +} + +var E_DefaultFloat = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float32)(nil), + Field: 202, + Name: "testdata.default_float", + Tag: "fixed32,202,opt,name=default_float,def=3.14", +} + +var E_DefaultInt32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 203, + Name: "testdata.default_int32", + Tag: "varint,203,opt,name=default_int32,def=42", +} + +var E_DefaultInt64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 204, + Name: "testdata.default_int64", + Tag: "varint,204,opt,name=default_int64,def=43", +} + +var E_DefaultUint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 205, + Name: "testdata.default_uint32", + Tag: "varint,205,opt,name=default_uint32,def=44", +} + +var E_DefaultUint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 206, + Name: "testdata.default_uint64", + Tag: "varint,206,opt,name=default_uint64,def=45", +} + +var E_DefaultSint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 207, + Name: "testdata.default_sint32", + Tag: "zigzag32,207,opt,name=default_sint32,def=46", +} + +var E_DefaultSint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 208, + Name: "testdata.default_sint64", + Tag: "zigzag64,208,opt,name=default_sint64,def=47", +} + +var E_DefaultFixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 209, + Name: "testdata.default_fixed32", + Tag: "fixed32,209,opt,name=default_fixed32,def=48", +} + +var E_DefaultFixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 210, + Name: "testdata.default_fixed64", + Tag: "fixed64,210,opt,name=default_fixed64,def=49", +} + +var E_DefaultSfixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 211, + Name: "testdata.default_sfixed32", + Tag: "fixed32,211,opt,name=default_sfixed32,def=50", +} + +var E_DefaultSfixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 212, + Name: "testdata.default_sfixed64", + Tag: "fixed64,212,opt,name=default_sfixed64,def=51", +} + +var E_DefaultBool = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*bool)(nil), + Field: 213, + Name: "testdata.default_bool", + Tag: "varint,213,opt,name=default_bool,def=1", +} + +var E_DefaultString = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*string)(nil), + Field: 214, + Name: "testdata.default_string", + Tag: "bytes,214,opt,name=default_string,def=Hello, string", +} + +var E_DefaultBytes = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: ([]byte)(nil), + Field: 215, + Name: "testdata.default_bytes", + Tag: "bytes,215,opt,name=default_bytes,def=Hello, bytes", +} + +var E_DefaultEnum = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*DefaultsMessage_DefaultsEnum)(nil), + Field: 216, + Name: "testdata.default_enum", + Tag: "varint,216,opt,name=default_enum,enum=testdata.DefaultsMessage_DefaultsEnum,def=1", +} + +var E_X201 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 201, + Name: "testdata.x201", + Tag: "bytes,201,opt,name=x201", +} + +var E_X202 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 202, + Name: "testdata.x202", + Tag: "bytes,202,opt,name=x202", +} + +var E_X203 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 203, + Name: "testdata.x203", + Tag: "bytes,203,opt,name=x203", +} + +var E_X204 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 204, + Name: "testdata.x204", + Tag: "bytes,204,opt,name=x204", +} + +var E_X205 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 205, + Name: "testdata.x205", + Tag: "bytes,205,opt,name=x205", +} + +var E_X206 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 206, + Name: "testdata.x206", + Tag: "bytes,206,opt,name=x206", +} + +var E_X207 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 207, + Name: "testdata.x207", + Tag: "bytes,207,opt,name=x207", +} + +var E_X208 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 208, + Name: "testdata.x208", + Tag: "bytes,208,opt,name=x208", +} + +var E_X209 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 209, + Name: "testdata.x209", + Tag: "bytes,209,opt,name=x209", +} + +var E_X210 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 210, + Name: "testdata.x210", + Tag: "bytes,210,opt,name=x210", +} + +var E_X211 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 211, + Name: "testdata.x211", + Tag: "bytes,211,opt,name=x211", +} + +var E_X212 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 212, + Name: "testdata.x212", + Tag: "bytes,212,opt,name=x212", +} + +var E_X213 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 213, + Name: "testdata.x213", + Tag: "bytes,213,opt,name=x213", +} + +var E_X214 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 214, + Name: "testdata.x214", + Tag: "bytes,214,opt,name=x214", +} + +var E_X215 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 215, + Name: "testdata.x215", + Tag: "bytes,215,opt,name=x215", +} + +var E_X216 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 216, + Name: "testdata.x216", + Tag: "bytes,216,opt,name=x216", +} + +var E_X217 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 217, + Name: "testdata.x217", + Tag: "bytes,217,opt,name=x217", +} + +var E_X218 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 218, + Name: "testdata.x218", + Tag: "bytes,218,opt,name=x218", +} + +var E_X219 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 219, + Name: "testdata.x219", + Tag: "bytes,219,opt,name=x219", +} + +var E_X220 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 220, + Name: "testdata.x220", + Tag: "bytes,220,opt,name=x220", +} + +var E_X221 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 221, + Name: "testdata.x221", + Tag: "bytes,221,opt,name=x221", +} + +var E_X222 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 222, + Name: "testdata.x222", + Tag: "bytes,222,opt,name=x222", +} + +var E_X223 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 223, + Name: "testdata.x223", + Tag: "bytes,223,opt,name=x223", +} + +var E_X224 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 224, + Name: "testdata.x224", + Tag: "bytes,224,opt,name=x224", +} + +var E_X225 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 225, + Name: "testdata.x225", + Tag: "bytes,225,opt,name=x225", +} + +var E_X226 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 226, + Name: "testdata.x226", + Tag: "bytes,226,opt,name=x226", +} + +var E_X227 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 227, + Name: "testdata.x227", + Tag: "bytes,227,opt,name=x227", +} + +var E_X228 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 228, + Name: "testdata.x228", + Tag: "bytes,228,opt,name=x228", +} + +var E_X229 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 229, + Name: "testdata.x229", + Tag: "bytes,229,opt,name=x229", +} + +var E_X230 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 230, + Name: "testdata.x230", + Tag: "bytes,230,opt,name=x230", +} + +var E_X231 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 231, + Name: "testdata.x231", + Tag: "bytes,231,opt,name=x231", +} + +var E_X232 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 232, + Name: "testdata.x232", + Tag: "bytes,232,opt,name=x232", +} + +var E_X233 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 233, + Name: "testdata.x233", + Tag: "bytes,233,opt,name=x233", +} + +var E_X234 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 234, + Name: "testdata.x234", + Tag: "bytes,234,opt,name=x234", +} + +var E_X235 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 235, + Name: "testdata.x235", + Tag: "bytes,235,opt,name=x235", +} + +var E_X236 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 236, + Name: "testdata.x236", + Tag: "bytes,236,opt,name=x236", +} + +var E_X237 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 237, + Name: "testdata.x237", + Tag: "bytes,237,opt,name=x237", +} + +var E_X238 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 238, + Name: "testdata.x238", + Tag: "bytes,238,opt,name=x238", +} + +var E_X239 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 239, + Name: "testdata.x239", + Tag: "bytes,239,opt,name=x239", +} + +var E_X240 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 240, + Name: "testdata.x240", + Tag: "bytes,240,opt,name=x240", +} + +var E_X241 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 241, + Name: "testdata.x241", + Tag: "bytes,241,opt,name=x241", +} + +var E_X242 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 242, + Name: "testdata.x242", + Tag: "bytes,242,opt,name=x242", +} + +var E_X243 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 243, + Name: "testdata.x243", + Tag: "bytes,243,opt,name=x243", +} + +var E_X244 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 244, + Name: "testdata.x244", + Tag: "bytes,244,opt,name=x244", +} + +var E_X245 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 245, + Name: "testdata.x245", + Tag: "bytes,245,opt,name=x245", +} + +var E_X246 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 246, + Name: "testdata.x246", + Tag: "bytes,246,opt,name=x246", +} + +var E_X247 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 247, + Name: "testdata.x247", + Tag: "bytes,247,opt,name=x247", +} + +var E_X248 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 248, + Name: "testdata.x248", + Tag: "bytes,248,opt,name=x248", +} + +var E_X249 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 249, + Name: "testdata.x249", + Tag: "bytes,249,opt,name=x249", +} + +var E_X250 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 250, + Name: "testdata.x250", + Tag: "bytes,250,opt,name=x250", +} + +func init() { + proto.RegisterEnum("testdata.FOO", FOO_name, FOO_value) + proto.RegisterEnum("testdata.GoTest_KIND", GoTest_KIND_name, GoTest_KIND_value) + proto.RegisterEnum("testdata.MyMessage_Color", MyMessage_Color_name, MyMessage_Color_value) + proto.RegisterEnum("testdata.DefaultsMessage_DefaultsEnum", DefaultsMessage_DefaultsEnum_name, DefaultsMessage_DefaultsEnum_value) + proto.RegisterEnum("testdata.Defaults_Color", Defaults_Color_name, Defaults_Color_value) + proto.RegisterEnum("testdata.RepeatedEnum_Color", RepeatedEnum_Color_name, RepeatedEnum_Color_value) + proto.RegisterExtension(E_Ext_More) + proto.RegisterExtension(E_Ext_Text) + proto.RegisterExtension(E_Ext_Number) + proto.RegisterExtension(E_Greeting) + proto.RegisterExtension(E_NoDefaultDouble) + proto.RegisterExtension(E_NoDefaultFloat) + proto.RegisterExtension(E_NoDefaultInt32) + proto.RegisterExtension(E_NoDefaultInt64) + proto.RegisterExtension(E_NoDefaultUint32) + proto.RegisterExtension(E_NoDefaultUint64) + proto.RegisterExtension(E_NoDefaultSint32) + proto.RegisterExtension(E_NoDefaultSint64) + proto.RegisterExtension(E_NoDefaultFixed32) + proto.RegisterExtension(E_NoDefaultFixed64) + proto.RegisterExtension(E_NoDefaultSfixed32) + proto.RegisterExtension(E_NoDefaultSfixed64) + proto.RegisterExtension(E_NoDefaultBool) + proto.RegisterExtension(E_NoDefaultString) + proto.RegisterExtension(E_NoDefaultBytes) + proto.RegisterExtension(E_NoDefaultEnum) + proto.RegisterExtension(E_DefaultDouble) + proto.RegisterExtension(E_DefaultFloat) + proto.RegisterExtension(E_DefaultInt32) + proto.RegisterExtension(E_DefaultInt64) + proto.RegisterExtension(E_DefaultUint32) + proto.RegisterExtension(E_DefaultUint64) + proto.RegisterExtension(E_DefaultSint32) + proto.RegisterExtension(E_DefaultSint64) + proto.RegisterExtension(E_DefaultFixed32) + proto.RegisterExtension(E_DefaultFixed64) + proto.RegisterExtension(E_DefaultSfixed32) + proto.RegisterExtension(E_DefaultSfixed64) + proto.RegisterExtension(E_DefaultBool) + proto.RegisterExtension(E_DefaultString) + proto.RegisterExtension(E_DefaultBytes) + proto.RegisterExtension(E_DefaultEnum) + proto.RegisterExtension(E_X201) + proto.RegisterExtension(E_X202) + proto.RegisterExtension(E_X203) + proto.RegisterExtension(E_X204) + proto.RegisterExtension(E_X205) + proto.RegisterExtension(E_X206) + proto.RegisterExtension(E_X207) + proto.RegisterExtension(E_X208) + proto.RegisterExtension(E_X209) + proto.RegisterExtension(E_X210) + proto.RegisterExtension(E_X211) + proto.RegisterExtension(E_X212) + proto.RegisterExtension(E_X213) + proto.RegisterExtension(E_X214) + proto.RegisterExtension(E_X215) + proto.RegisterExtension(E_X216) + proto.RegisterExtension(E_X217) + proto.RegisterExtension(E_X218) + proto.RegisterExtension(E_X219) + proto.RegisterExtension(E_X220) + proto.RegisterExtension(E_X221) + proto.RegisterExtension(E_X222) + proto.RegisterExtension(E_X223) + proto.RegisterExtension(E_X224) + proto.RegisterExtension(E_X225) + proto.RegisterExtension(E_X226) + proto.RegisterExtension(E_X227) + proto.RegisterExtension(E_X228) + proto.RegisterExtension(E_X229) + proto.RegisterExtension(E_X230) + proto.RegisterExtension(E_X231) + proto.RegisterExtension(E_X232) + proto.RegisterExtension(E_X233) + proto.RegisterExtension(E_X234) + proto.RegisterExtension(E_X235) + proto.RegisterExtension(E_X236) + proto.RegisterExtension(E_X237) + proto.RegisterExtension(E_X238) + proto.RegisterExtension(E_X239) + proto.RegisterExtension(E_X240) + proto.RegisterExtension(E_X241) + proto.RegisterExtension(E_X242) + proto.RegisterExtension(E_X243) + proto.RegisterExtension(E_X244) + proto.RegisterExtension(E_X245) + proto.RegisterExtension(E_X246) + proto.RegisterExtension(E_X247) + proto.RegisterExtension(E_X248) + proto.RegisterExtension(E_X249) + proto.RegisterExtension(E_X250) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/test.proto charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/test.proto --- charm-2.1.1/src/github.com/golang/protobuf/proto/testdata/test.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/testdata/test.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,480 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// A feature-rich test file for the protocol compiler and libraries. + +syntax = "proto2"; + +package testdata; + +enum FOO { FOO1 = 1; }; + +message GoEnum { + required FOO foo = 1; +} + +message GoTestField { + required string Label = 1; + required string Type = 2; +} + +message GoTest { + // An enum, for completeness. + enum KIND { + VOID = 0; + + // Basic types + BOOL = 1; + BYTES = 2; + FINGERPRINT = 3; + FLOAT = 4; + INT = 5; + STRING = 6; + TIME = 7; + + // Groupings + TUPLE = 8; + ARRAY = 9; + MAP = 10; + + // Table types + TABLE = 11; + + // Functions + FUNCTION = 12; // last tag + }; + + // Some typical parameters + required KIND Kind = 1; + optional string Table = 2; + optional int32 Param = 3; + + // Required, repeated and optional foreign fields. + required GoTestField RequiredField = 4; + repeated GoTestField RepeatedField = 5; + optional GoTestField OptionalField = 6; + + // Required fields of all basic types + required bool F_Bool_required = 10; + required int32 F_Int32_required = 11; + required int64 F_Int64_required = 12; + required fixed32 F_Fixed32_required = 13; + required fixed64 F_Fixed64_required = 14; + required uint32 F_Uint32_required = 15; + required uint64 F_Uint64_required = 16; + required float F_Float_required = 17; + required double F_Double_required = 18; + required string F_String_required = 19; + required bytes F_Bytes_required = 101; + required sint32 F_Sint32_required = 102; + required sint64 F_Sint64_required = 103; + + // Repeated fields of all basic types + repeated bool F_Bool_repeated = 20; + repeated int32 F_Int32_repeated = 21; + repeated int64 F_Int64_repeated = 22; + repeated fixed32 F_Fixed32_repeated = 23; + repeated fixed64 F_Fixed64_repeated = 24; + repeated uint32 F_Uint32_repeated = 25; + repeated uint64 F_Uint64_repeated = 26; + repeated float F_Float_repeated = 27; + repeated double F_Double_repeated = 28; + repeated string F_String_repeated = 29; + repeated bytes F_Bytes_repeated = 201; + repeated sint32 F_Sint32_repeated = 202; + repeated sint64 F_Sint64_repeated = 203; + + // Optional fields of all basic types + optional bool F_Bool_optional = 30; + optional int32 F_Int32_optional = 31; + optional int64 F_Int64_optional = 32; + optional fixed32 F_Fixed32_optional = 33; + optional fixed64 F_Fixed64_optional = 34; + optional uint32 F_Uint32_optional = 35; + optional uint64 F_Uint64_optional = 36; + optional float F_Float_optional = 37; + optional double F_Double_optional = 38; + optional string F_String_optional = 39; + optional bytes F_Bytes_optional = 301; + optional sint32 F_Sint32_optional = 302; + optional sint64 F_Sint64_optional = 303; + + // Default-valued fields of all basic types + optional bool F_Bool_defaulted = 40 [default=true]; + optional int32 F_Int32_defaulted = 41 [default=32]; + optional int64 F_Int64_defaulted = 42 [default=64]; + optional fixed32 F_Fixed32_defaulted = 43 [default=320]; + optional fixed64 F_Fixed64_defaulted = 44 [default=640]; + optional uint32 F_Uint32_defaulted = 45 [default=3200]; + optional uint64 F_Uint64_defaulted = 46 [default=6400]; + optional float F_Float_defaulted = 47 [default=314159.]; + optional double F_Double_defaulted = 48 [default=271828.]; + optional string F_String_defaulted = 49 [default="hello, \"world!\"\n"]; + optional bytes F_Bytes_defaulted = 401 [default="Bignose"]; + optional sint32 F_Sint32_defaulted = 402 [default = -32]; + optional sint64 F_Sint64_defaulted = 403 [default = -64]; + + // Packed repeated fields (no string or bytes). + repeated bool F_Bool_repeated_packed = 50 [packed=true]; + repeated int32 F_Int32_repeated_packed = 51 [packed=true]; + repeated int64 F_Int64_repeated_packed = 52 [packed=true]; + repeated fixed32 F_Fixed32_repeated_packed = 53 [packed=true]; + repeated fixed64 F_Fixed64_repeated_packed = 54 [packed=true]; + repeated uint32 F_Uint32_repeated_packed = 55 [packed=true]; + repeated uint64 F_Uint64_repeated_packed = 56 [packed=true]; + repeated float F_Float_repeated_packed = 57 [packed=true]; + repeated double F_Double_repeated_packed = 58 [packed=true]; + repeated sint32 F_Sint32_repeated_packed = 502 [packed=true]; + repeated sint64 F_Sint64_repeated_packed = 503 [packed=true]; + + // Required, repeated, and optional groups. + required group RequiredGroup = 70 { + required string RequiredField = 71; + }; + + repeated group RepeatedGroup = 80 { + required string RequiredField = 81; + }; + + optional group OptionalGroup = 90 { + required string RequiredField = 91; + }; +} + +// For testing skipping of unrecognized fields. +// Numbers are all big, larger than tag numbers in GoTestField, +// the message used in the corresponding test. +message GoSkipTest { + required int32 skip_int32 = 11; + required fixed32 skip_fixed32 = 12; + required fixed64 skip_fixed64 = 13; + required string skip_string = 14; + required group SkipGroup = 15 { + required int32 group_int32 = 16; + required string group_string = 17; + } +} + +// For testing packed/non-packed decoder switching. +// A serialized instance of one should be deserializable as the other. +message NonPackedTest { + repeated int32 a = 1; +} + +message PackedTest { + repeated int32 b = 1 [packed=true]; +} + +message MaxTag { + // Maximum possible tag number. + optional string last_field = 536870911; +} + +message OldMessage { + message Nested { + optional string name = 1; + } + optional Nested nested = 1; + + optional int32 num = 2; +} + +// NewMessage is wire compatible with OldMessage; +// imagine it as a future version. +message NewMessage { + message Nested { + optional string name = 1; + optional string food_group = 2; + } + optional Nested nested = 1; + + // This is an int32 in OldMessage. + optional int64 num = 2; +} + +// Smaller tests for ASCII formatting. + +message InnerMessage { + required string host = 1; + optional int32 port = 2 [default=4000]; + optional bool connected = 3; +} + +message OtherMessage { + optional int64 key = 1; + optional bytes value = 2; + optional float weight = 3; + optional InnerMessage inner = 4; +} + +message MyMessage { + required int32 count = 1; + optional string name = 2; + optional string quote = 3; + repeated string pet = 4; + optional InnerMessage inner = 5; + repeated OtherMessage others = 6; + repeated InnerMessage rep_inner = 12; + + enum Color { + RED = 0; + GREEN = 1; + BLUE = 2; + }; + optional Color bikeshed = 7; + + optional group SomeGroup = 8 { + optional int32 group_field = 9; + } + + // This field becomes [][]byte in the generated code. + repeated bytes rep_bytes = 10; + + optional double bigfloat = 11; + + extensions 100 to max; +} + +message Ext { + extend MyMessage { + optional Ext more = 103; + optional string text = 104; + optional int32 number = 105; + } + + optional string data = 1; +} + +extend MyMessage { + repeated string greeting = 106; +} + +message DefaultsMessage { + enum DefaultsEnum { + ZERO = 0; + ONE = 1; + TWO = 2; + }; + extensions 100 to max; +} + +extend DefaultsMessage { + optional double no_default_double = 101; + optional float no_default_float = 102; + optional int32 no_default_int32 = 103; + optional int64 no_default_int64 = 104; + optional uint32 no_default_uint32 = 105; + optional uint64 no_default_uint64 = 106; + optional sint32 no_default_sint32 = 107; + optional sint64 no_default_sint64 = 108; + optional fixed32 no_default_fixed32 = 109; + optional fixed64 no_default_fixed64 = 110; + optional sfixed32 no_default_sfixed32 = 111; + optional sfixed64 no_default_sfixed64 = 112; + optional bool no_default_bool = 113; + optional string no_default_string = 114; + optional bytes no_default_bytes = 115; + optional DefaultsMessage.DefaultsEnum no_default_enum = 116; + + optional double default_double = 201 [default = 3.1415]; + optional float default_float = 202 [default = 3.14]; + optional int32 default_int32 = 203 [default = 42]; + optional int64 default_int64 = 204 [default = 43]; + optional uint32 default_uint32 = 205 [default = 44]; + optional uint64 default_uint64 = 206 [default = 45]; + optional sint32 default_sint32 = 207 [default = 46]; + optional sint64 default_sint64 = 208 [default = 47]; + optional fixed32 default_fixed32 = 209 [default = 48]; + optional fixed64 default_fixed64 = 210 [default = 49]; + optional sfixed32 default_sfixed32 = 211 [default = 50]; + optional sfixed64 default_sfixed64 = 212 [default = 51]; + optional bool default_bool = 213 [default = true]; + optional string default_string = 214 [default = "Hello, string"]; + optional bytes default_bytes = 215 [default = "Hello, bytes"]; + optional DefaultsMessage.DefaultsEnum default_enum = 216 [default = ONE]; +} + +message MyMessageSet { + option message_set_wire_format = true; + extensions 100 to max; +} + +message Empty { +} + +extend MyMessageSet { + optional Empty x201 = 201; + optional Empty x202 = 202; + optional Empty x203 = 203; + optional Empty x204 = 204; + optional Empty x205 = 205; + optional Empty x206 = 206; + optional Empty x207 = 207; + optional Empty x208 = 208; + optional Empty x209 = 209; + optional Empty x210 = 210; + optional Empty x211 = 211; + optional Empty x212 = 212; + optional Empty x213 = 213; + optional Empty x214 = 214; + optional Empty x215 = 215; + optional Empty x216 = 216; + optional Empty x217 = 217; + optional Empty x218 = 218; + optional Empty x219 = 219; + optional Empty x220 = 220; + optional Empty x221 = 221; + optional Empty x222 = 222; + optional Empty x223 = 223; + optional Empty x224 = 224; + optional Empty x225 = 225; + optional Empty x226 = 226; + optional Empty x227 = 227; + optional Empty x228 = 228; + optional Empty x229 = 229; + optional Empty x230 = 230; + optional Empty x231 = 231; + optional Empty x232 = 232; + optional Empty x233 = 233; + optional Empty x234 = 234; + optional Empty x235 = 235; + optional Empty x236 = 236; + optional Empty x237 = 237; + optional Empty x238 = 238; + optional Empty x239 = 239; + optional Empty x240 = 240; + optional Empty x241 = 241; + optional Empty x242 = 242; + optional Empty x243 = 243; + optional Empty x244 = 244; + optional Empty x245 = 245; + optional Empty x246 = 246; + optional Empty x247 = 247; + optional Empty x248 = 248; + optional Empty x249 = 249; + optional Empty x250 = 250; +} + +message MessageList { + repeated group Message = 1 { + required string name = 2; + required int32 count = 3; + } +} + +message Strings { + optional string string_field = 1; + optional bytes bytes_field = 2; +} + +message Defaults { + enum Color { + RED = 0; + GREEN = 1; + BLUE = 2; + } + + // Default-valued fields of all basic types. + // Same as GoTest, but copied here to make testing easier. + optional bool F_Bool = 1 [default=true]; + optional int32 F_Int32 = 2 [default=32]; + optional int64 F_Int64 = 3 [default=64]; + optional fixed32 F_Fixed32 = 4 [default=320]; + optional fixed64 F_Fixed64 = 5 [default=640]; + optional uint32 F_Uint32 = 6 [default=3200]; + optional uint64 F_Uint64 = 7 [default=6400]; + optional float F_Float = 8 [default=314159.]; + optional double F_Double = 9 [default=271828.]; + optional string F_String = 10 [default="hello, \"world!\"\n"]; + optional bytes F_Bytes = 11 [default="Bignose"]; + optional sint32 F_Sint32 = 12 [default=-32]; + optional sint64 F_Sint64 = 13 [default=-64]; + optional Color F_Enum = 14 [default=GREEN]; + + // More fields with crazy defaults. + optional float F_Pinf = 15 [default=inf]; + optional float F_Ninf = 16 [default=-inf]; + optional float F_Nan = 17 [default=nan]; + + // Sub-message. + optional SubDefaults sub = 18; + + // Redundant but explicit defaults. + optional string str_zero = 19 [default=""]; +} + +message SubDefaults { + optional int64 n = 1 [default=7]; +} + +message RepeatedEnum { + enum Color { + RED = 1; + } + repeated Color color = 1; +} + +message MoreRepeated { + repeated bool bools = 1; + repeated bool bools_packed = 2 [packed=true]; + repeated int32 ints = 3; + repeated int32 ints_packed = 4 [packed=true]; + repeated int64 int64s_packed = 7 [packed=true]; + repeated string strings = 5; + repeated fixed32 fixeds = 6; +} + +// GroupOld and GroupNew have the same wire format. +// GroupNew has a new field inside a group. + +message GroupOld { + optional group G = 101 { + optional int32 x = 2; + } +} + +message GroupNew { + optional group G = 101 { + optional int32 x = 2; + optional int32 y = 3; + } +} + +message FloatingPoint { + required double f = 1; +} + +message MessageWithMap { + map name_mapping = 1; + map msg_mapping = 2; + map byte_mapping = 3; + map str_to_str = 4; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/text.go charm-2.2.0/src/github.com/golang/protobuf/proto/text.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/text.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/text.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,792 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +// Functions for writing the text protocol buffer format. + +import ( + "bufio" + "bytes" + "encoding" + "fmt" + "io" + "log" + "math" + "os" + "reflect" + "sort" + "strings" +) + +var ( + newline = []byte("\n") + spaces = []byte(" ") + gtNewline = []byte(">\n") + endBraceNewline = []byte("}\n") + backslashN = []byte{'\\', 'n'} + backslashR = []byte{'\\', 'r'} + backslashT = []byte{'\\', 't'} + backslashDQ = []byte{'\\', '"'} + backslashBS = []byte{'\\', '\\'} + posInf = []byte("inf") + negInf = []byte("-inf") + nan = []byte("nan") +) + +type writer interface { + io.Writer + WriteByte(byte) error +} + +// textWriter is an io.Writer that tracks its indentation level. +type textWriter struct { + ind int + complete bool // if the current position is a complete line + compact bool // whether to write out as a one-liner + w writer +} + +func (w *textWriter) WriteString(s string) (n int, err error) { + if !strings.Contains(s, "\n") { + if !w.compact && w.complete { + w.writeIndent() + } + w.complete = false + return io.WriteString(w.w, s) + } + // WriteString is typically called without newlines, so this + // codepath and its copy are rare. We copy to avoid + // duplicating all of Write's logic here. + return w.Write([]byte(s)) +} + +func (w *textWriter) Write(p []byte) (n int, err error) { + newlines := bytes.Count(p, newline) + if newlines == 0 { + if !w.compact && w.complete { + w.writeIndent() + } + n, err = w.w.Write(p) + w.complete = false + return n, err + } + + frags := bytes.SplitN(p, newline, newlines+1) + if w.compact { + for i, frag := range frags { + if i > 0 { + if err := w.w.WriteByte(' '); err != nil { + return n, err + } + n++ + } + nn, err := w.w.Write(frag) + n += nn + if err != nil { + return n, err + } + } + return n, nil + } + + for i, frag := range frags { + if w.complete { + w.writeIndent() + } + nn, err := w.w.Write(frag) + n += nn + if err != nil { + return n, err + } + if i+1 < len(frags) { + if err := w.w.WriteByte('\n'); err != nil { + return n, err + } + n++ + } + } + w.complete = len(frags[len(frags)-1]) == 0 + return n, nil +} + +func (w *textWriter) WriteByte(c byte) error { + if w.compact && c == '\n' { + c = ' ' + } + if !w.compact && w.complete { + w.writeIndent() + } + err := w.w.WriteByte(c) + w.complete = c == '\n' + return err +} + +func (w *textWriter) indent() { w.ind++ } + +func (w *textWriter) unindent() { + if w.ind == 0 { + log.Printf("proto: textWriter unindented too far") + return + } + w.ind-- +} + +func writeName(w *textWriter, props *Properties) error { + if _, err := w.WriteString(props.OrigName); err != nil { + return err + } + if props.Wire != "group" { + return w.WriteByte(':') + } + return nil +} + +var ( + messageSetType = reflect.TypeOf((*MessageSet)(nil)).Elem() +) + +// raw is the interface satisfied by RawMessage. +type raw interface { + Bytes() []byte +} + +func writeStruct(w *textWriter, sv reflect.Value) error { + if sv.Type() == messageSetType { + return writeMessageSet(w, sv.Addr().Interface().(*MessageSet)) + } + + st := sv.Type() + sprops := GetProperties(st) + for i := 0; i < sv.NumField(); i++ { + fv := sv.Field(i) + props := sprops.Prop[i] + name := st.Field(i).Name + + if strings.HasPrefix(name, "XXX_") { + // There are two XXX_ fields: + // XXX_unrecognized []byte + // XXX_extensions map[int32]proto.Extension + // The first is handled here; + // the second is handled at the bottom of this function. + if name == "XXX_unrecognized" && !fv.IsNil() { + if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil { + return err + } + } + continue + } + if fv.Kind() == reflect.Ptr && fv.IsNil() { + // Field not filled in. This could be an optional field or + // a required field that wasn't filled in. Either way, there + // isn't anything we can show for it. + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + // Repeated field that is empty, or a bytes field that is unused. + continue + } + + if props.Repeated && fv.Kind() == reflect.Slice { + // Repeated field. + for j := 0; j < fv.Len(); j++ { + if err := writeName(w, props); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + v := fv.Index(j) + if v.Kind() == reflect.Ptr && v.IsNil() { + // A nil message in a repeated field is not valid, + // but we can handle that more gracefully than panicking. + if _, err := w.Write([]byte("\n")); err != nil { + return err + } + continue + } + if err := writeAny(w, v, props); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + } + continue + } + if fv.Kind() == reflect.Map { + // Map fields are rendered as a repeated struct with key/value fields. + keys := fv.MapKeys() // TODO: should we sort these for deterministic output? + sort.Sort(mapKeys(keys)) + for _, key := range keys { + val := fv.MapIndex(key) + if err := writeName(w, props); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + // open struct + if err := w.WriteByte('<'); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte('\n'); err != nil { + return err + } + } + w.indent() + // key + if _, err := w.WriteString("key:"); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if err := writeAny(w, key, props.mkeyprop); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + // nil values aren't legal, but we can avoid panicking because of them. + if val.Kind() != reflect.Ptr || !val.IsNil() { + // value + if _, err := w.WriteString("value:"); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if err := writeAny(w, val, props.mvalprop); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + } + // close struct + w.unindent() + if err := w.WriteByte('>'); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + } + continue + } + if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 { + // empty bytes field + continue + } + if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice { + // proto3 non-repeated scalar field; skip if zero value + switch fv.Kind() { + case reflect.Bool: + if !fv.Bool() { + continue + } + case reflect.Int32, reflect.Int64: + if fv.Int() == 0 { + continue + } + case reflect.Uint32, reflect.Uint64: + if fv.Uint() == 0 { + continue + } + case reflect.Float32, reflect.Float64: + if fv.Float() == 0 { + continue + } + case reflect.String: + if fv.String() == "" { + continue + } + } + } + + if err := writeName(w, props); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if b, ok := fv.Interface().(raw); ok { + if err := writeRaw(w, b.Bytes()); err != nil { + return err + } + continue + } + + // Enums have a String method, so writeAny will work fine. + if err := writeAny(w, fv, props); err != nil { + return err + } + + if err := w.WriteByte('\n'); err != nil { + return err + } + } + + // Extensions (the XXX_extensions field). + pv := sv.Addr() + if pv.Type().Implements(extendableProtoType) { + if err := writeExtensions(w, pv); err != nil { + return err + } + } + + return nil +} + +// writeRaw writes an uninterpreted raw message. +func writeRaw(w *textWriter, b []byte) error { + if err := w.WriteByte('<'); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte('\n'); err != nil { + return err + } + } + w.indent() + if err := writeUnknownStruct(w, b); err != nil { + return err + } + w.unindent() + if err := w.WriteByte('>'); err != nil { + return err + } + return nil +} + +// writeAny writes an arbitrary field. +func writeAny(w *textWriter, v reflect.Value, props *Properties) error { + v = reflect.Indirect(v) + + // Floats have special cases. + if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 { + x := v.Float() + var b []byte + switch { + case math.IsInf(x, 1): + b = posInf + case math.IsInf(x, -1): + b = negInf + case math.IsNaN(x): + b = nan + } + if b != nil { + _, err := w.Write(b) + return err + } + // Other values are handled below. + } + + // We don't attempt to serialise every possible value type; only those + // that can occur in protocol buffers. + switch v.Kind() { + case reflect.Slice: + // Should only be a []byte; repeated fields are handled in writeStruct. + if err := writeString(w, string(v.Interface().([]byte))); err != nil { + return err + } + case reflect.String: + if err := writeString(w, v.String()); err != nil { + return err + } + case reflect.Struct: + // Required/optional group/message. + var bra, ket byte = '<', '>' + if props != nil && props.Wire == "group" { + bra, ket = '{', '}' + } + if err := w.WriteByte(bra); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte('\n'); err != nil { + return err + } + } + w.indent() + if tm, ok := v.Interface().(encoding.TextMarshaler); ok { + text, err := tm.MarshalText() + if err != nil { + return err + } + if _, err = w.Write(text); err != nil { + return err + } + } else if err := writeStruct(w, v); err != nil { + return err + } + w.unindent() + if err := w.WriteByte(ket); err != nil { + return err + } + default: + _, err := fmt.Fprint(w, v.Interface()) + return err + } + return nil +} + +// equivalent to C's isprint. +func isprint(c byte) bool { + return c >= 0x20 && c < 0x7f +} + +// writeString writes a string in the protocol buffer text format. +// It is similar to strconv.Quote except we don't use Go escape sequences, +// we treat the string as a byte sequence, and we use octal escapes. +// These differences are to maintain interoperability with the other +// languages' implementations of the text format. +func writeString(w *textWriter, s string) error { + // use WriteByte here to get any needed indent + if err := w.WriteByte('"'); err != nil { + return err + } + // Loop over the bytes, not the runes. + for i := 0; i < len(s); i++ { + var err error + // Divergence from C++: we don't escape apostrophes. + // There's no need to escape them, and the C++ parser + // copes with a naked apostrophe. + switch c := s[i]; c { + case '\n': + _, err = w.w.Write(backslashN) + case '\r': + _, err = w.w.Write(backslashR) + case '\t': + _, err = w.w.Write(backslashT) + case '"': + _, err = w.w.Write(backslashDQ) + case '\\': + _, err = w.w.Write(backslashBS) + default: + if isprint(c) { + err = w.w.WriteByte(c) + } else { + _, err = fmt.Fprintf(w.w, "\\%03o", c) + } + } + if err != nil { + return err + } + } + return w.WriteByte('"') +} + +func writeMessageSet(w *textWriter, ms *MessageSet) error { + for _, item := range ms.Item { + id := *item.TypeId + if msd, ok := messageSetMap[id]; ok { + // Known message set type. + if _, err := fmt.Fprintf(w, "[%s]: <\n", msd.name); err != nil { + return err + } + w.indent() + + pb := reflect.New(msd.t.Elem()) + if err := Unmarshal(item.Message, pb.Interface().(Message)); err != nil { + if _, err := fmt.Fprintf(w, "/* bad message: %v */\n", err); err != nil { + return err + } + } else { + if err := writeStruct(w, pb.Elem()); err != nil { + return err + } + } + } else { + // Unknown type. + if _, err := fmt.Fprintf(w, "[%d]: <\n", id); err != nil { + return err + } + w.indent() + if err := writeUnknownStruct(w, item.Message); err != nil { + return err + } + } + w.unindent() + if _, err := w.Write(gtNewline); err != nil { + return err + } + } + return nil +} + +func writeUnknownStruct(w *textWriter, data []byte) (err error) { + if !w.compact { + if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil { + return err + } + } + b := NewBuffer(data) + for b.index < len(b.buf) { + x, err := b.DecodeVarint() + if err != nil { + _, err := fmt.Fprintf(w, "/* %v */\n", err) + return err + } + wire, tag := x&7, x>>3 + if wire == WireEndGroup { + w.unindent() + if _, err := w.Write(endBraceNewline); err != nil { + return err + } + continue + } + if _, err := fmt.Fprint(w, tag); err != nil { + return err + } + if wire != WireStartGroup { + if err := w.WriteByte(':'); err != nil { + return err + } + } + if !w.compact || wire == WireStartGroup { + if err := w.WriteByte(' '); err != nil { + return err + } + } + switch wire { + case WireBytes: + buf, e := b.DecodeRawBytes(false) + if e == nil { + _, err = fmt.Fprintf(w, "%q", buf) + } else { + _, err = fmt.Fprintf(w, "/* %v */", e) + } + case WireFixed32: + x, err = b.DecodeFixed32() + err = writeUnknownInt(w, x, err) + case WireFixed64: + x, err = b.DecodeFixed64() + err = writeUnknownInt(w, x, err) + case WireStartGroup: + err = w.WriteByte('{') + w.indent() + case WireVarint: + x, err = b.DecodeVarint() + err = writeUnknownInt(w, x, err) + default: + _, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire) + } + if err != nil { + return err + } + if err = w.WriteByte('\n'); err != nil { + return err + } + } + return nil +} + +func writeUnknownInt(w *textWriter, x uint64, err error) error { + if err == nil { + _, err = fmt.Fprint(w, x) + } else { + _, err = fmt.Fprintf(w, "/* %v */", err) + } + return err +} + +type int32Slice []int32 + +func (s int32Slice) Len() int { return len(s) } +func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] } +func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// writeExtensions writes all the extensions in pv. +// pv is assumed to be a pointer to a protocol message struct that is extendable. +func writeExtensions(w *textWriter, pv reflect.Value) error { + emap := extensionMaps[pv.Type().Elem()] + ep := pv.Interface().(extendableProto) + + // Order the extensions by ID. + // This isn't strictly necessary, but it will give us + // canonical output, which will also make testing easier. + m := ep.ExtensionMap() + ids := make([]int32, 0, len(m)) + for id := range m { + ids = append(ids, id) + } + sort.Sort(int32Slice(ids)) + + for _, extNum := range ids { + ext := m[extNum] + var desc *ExtensionDesc + if emap != nil { + desc = emap[extNum] + } + if desc == nil { + // Unknown extension. + if err := writeUnknownStruct(w, ext.enc); err != nil { + return err + } + continue + } + + pb, err := GetExtension(ep, desc) + if err != nil { + if _, err := fmt.Fprintln(os.Stderr, "proto: failed getting extension: ", err); err != nil { + return err + } + continue + } + + // Repeated extensions will appear as a slice. + if !desc.repeated() { + if err := writeExtension(w, desc.Name, pb); err != nil { + return err + } + } else { + v := reflect.ValueOf(pb) + for i := 0; i < v.Len(); i++ { + if err := writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil { + return err + } + } + } + } + return nil +} + +func writeExtension(w *textWriter, name string, pb interface{}) error { + if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if err := writeAny(w, reflect.ValueOf(pb), nil); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + return nil +} + +func (w *textWriter) writeIndent() { + if !w.complete { + return + } + remain := w.ind * 2 + for remain > 0 { + n := remain + if n > len(spaces) { + n = len(spaces) + } + w.w.Write(spaces[:n]) + remain -= n + } + w.complete = false +} + +func marshalText(w io.Writer, pb Message, compact bool) error { + val := reflect.ValueOf(pb) + if pb == nil || val.IsNil() { + w.Write([]byte("")) + return nil + } + var bw *bufio.Writer + ww, ok := w.(writer) + if !ok { + bw = bufio.NewWriter(w) + ww = bw + } + aw := &textWriter{ + w: ww, + complete: true, + compact: compact, + } + + if tm, ok := pb.(encoding.TextMarshaler); ok { + text, err := tm.MarshalText() + if err != nil { + return err + } + if _, err = aw.Write(text); err != nil { + return err + } + if bw != nil { + return bw.Flush() + } + return nil + } + // Dereference the received pointer so we don't have outer < and >. + v := reflect.Indirect(val) + if err := writeStruct(aw, v); err != nil { + return err + } + if bw != nil { + return bw.Flush() + } + return nil +} + +// MarshalText writes a given protocol buffer in text format. +// The only errors returned are from w. +func MarshalText(w io.Writer, pb Message) error { + return marshalText(w, pb, false) +} + +// MarshalTextString is the same as MarshalText, but returns the string directly. +func MarshalTextString(pb Message) string { + var buf bytes.Buffer + marshalText(&buf, pb, false) + return buf.String() +} + +// CompactText writes a given protocol buffer in compact text format (one line). +func CompactText(w io.Writer, pb Message) error { return marshalText(w, pb, true) } + +// CompactTextString is the same as CompactText, but returns the string directly. +func CompactTextString(pb Message) string { + var buf bytes.Buffer + marshalText(&buf, pb, true) + return buf.String() +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/text_parser.go charm-2.2.0/src/github.com/golang/protobuf/proto/text_parser.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/text_parser.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/text_parser.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,772 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +// Functions for parsing the Text protocol buffer format. +// TODO: message sets. + +import ( + "encoding" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "unicode/utf8" +) + +type ParseError struct { + Message string + Line int // 1-based line number + Offset int // 0-based byte offset from start of input +} + +func (p *ParseError) Error() string { + if p.Line == 1 { + // show offset only for first line + return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message) + } + return fmt.Sprintf("line %d: %v", p.Line, p.Message) +} + +type token struct { + value string + err *ParseError + line int // line number + offset int // byte number from start of input, not start of line + unquoted string // the unquoted version of value, if it was a quoted string +} + +func (t *token) String() string { + if t.err == nil { + return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset) + } + return fmt.Sprintf("parse error: %v", t.err) +} + +type textParser struct { + s string // remaining input + done bool // whether the parsing is finished (success or error) + backed bool // whether back() was called + offset, line int + cur token +} + +func newTextParser(s string) *textParser { + p := new(textParser) + p.s = s + p.line = 1 + p.cur.line = 1 + return p +} + +func (p *textParser) errorf(format string, a ...interface{}) *ParseError { + pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset} + p.cur.err = pe + p.done = true + return pe +} + +// Numbers and identifiers are matched by [-+._A-Za-z0-9] +func isIdentOrNumberChar(c byte) bool { + switch { + case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z': + return true + case '0' <= c && c <= '9': + return true + } + switch c { + case '-', '+', '.', '_': + return true + } + return false +} + +func isWhitespace(c byte) bool { + switch c { + case ' ', '\t', '\n', '\r': + return true + } + return false +} + +func (p *textParser) skipWhitespace() { + i := 0 + for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') { + if p.s[i] == '#' { + // comment; skip to end of line or input + for i < len(p.s) && p.s[i] != '\n' { + i++ + } + if i == len(p.s) { + break + } + } + if p.s[i] == '\n' { + p.line++ + } + i++ + } + p.offset += i + p.s = p.s[i:len(p.s)] + if len(p.s) == 0 { + p.done = true + } +} + +func (p *textParser) advance() { + // Skip whitespace + p.skipWhitespace() + if p.done { + return + } + + // Start of non-whitespace + p.cur.err = nil + p.cur.offset, p.cur.line = p.offset, p.line + p.cur.unquoted = "" + switch p.s[0] { + case '<', '>', '{', '}', ':', '[', ']', ';', ',': + // Single symbol + p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)] + case '"', '\'': + // Quoted string + i := 1 + for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' { + if p.s[i] == '\\' && i+1 < len(p.s) { + // skip escaped char + i++ + } + i++ + } + if i >= len(p.s) || p.s[i] != p.s[0] { + p.errorf("unmatched quote") + return + } + unq, err := unquoteC(p.s[1:i], rune(p.s[0])) + if err != nil { + p.errorf("invalid quoted string %v", p.s[0:i+1]) + return + } + p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)] + p.cur.unquoted = unq + default: + i := 0 + for i < len(p.s) && isIdentOrNumberChar(p.s[i]) { + i++ + } + if i == 0 { + p.errorf("unexpected byte %#x", p.s[0]) + return + } + p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)] + } + p.offset += len(p.cur.value) +} + +var ( + errBadUTF8 = errors.New("proto: bad UTF-8") + errBadHex = errors.New("proto: bad hexadecimal") +) + +func unquoteC(s string, quote rune) (string, error) { + // This is based on C++'s tokenizer.cc. + // Despite its name, this is *not* parsing C syntax. + // For instance, "\0" is an invalid quoted string. + + // Avoid allocation in trivial cases. + simple := true + for _, r := range s { + if r == '\\' || r == quote { + simple = false + break + } + } + if simple { + return s, nil + } + + buf := make([]byte, 0, 3*len(s)/2) + for len(s) > 0 { + r, n := utf8.DecodeRuneInString(s) + if r == utf8.RuneError && n == 1 { + return "", errBadUTF8 + } + s = s[n:] + if r != '\\' { + if r < utf8.RuneSelf { + buf = append(buf, byte(r)) + } else { + buf = append(buf, string(r)...) + } + continue + } + + ch, tail, err := unescape(s) + if err != nil { + return "", err + } + buf = append(buf, ch...) + s = tail + } + return string(buf), nil +} + +func unescape(s string) (ch string, tail string, err error) { + r, n := utf8.DecodeRuneInString(s) + if r == utf8.RuneError && n == 1 { + return "", "", errBadUTF8 + } + s = s[n:] + switch r { + case 'a': + return "\a", s, nil + case 'b': + return "\b", s, nil + case 'f': + return "\f", s, nil + case 'n': + return "\n", s, nil + case 'r': + return "\r", s, nil + case 't': + return "\t", s, nil + case 'v': + return "\v", s, nil + case '?': + return "?", s, nil // trigraph workaround + case '\'', '"', '\\': + return string(r), s, nil + case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X': + if len(s) < 2 { + return "", "", fmt.Errorf(`\%c requires 2 following digits`, r) + } + base := 8 + ss := s[:2] + s = s[2:] + if r == 'x' || r == 'X' { + base = 16 + } else { + ss = string(r) + ss + } + i, err := strconv.ParseUint(ss, base, 8) + if err != nil { + return "", "", err + } + return string([]byte{byte(i)}), s, nil + case 'u', 'U': + n := 4 + if r == 'U' { + n = 8 + } + if len(s) < n { + return "", "", fmt.Errorf(`\%c requires %d digits`, r, n) + } + + bs := make([]byte, n/2) + for i := 0; i < n; i += 2 { + a, ok1 := unhex(s[i]) + b, ok2 := unhex(s[i+1]) + if !ok1 || !ok2 { + return "", "", errBadHex + } + bs[i/2] = a<<4 | b + } + s = s[n:] + return string(bs), s, nil + } + return "", "", fmt.Errorf(`unknown escape \%c`, r) +} + +// Adapted from src/pkg/strconv/quote.go. +func unhex(b byte) (v byte, ok bool) { + switch { + case '0' <= b && b <= '9': + return b - '0', true + case 'a' <= b && b <= 'f': + return b - 'a' + 10, true + case 'A' <= b && b <= 'F': + return b - 'A' + 10, true + } + return 0, false +} + +// Back off the parser by one token. Can only be done between calls to next(). +// It makes the next advance() a no-op. +func (p *textParser) back() { p.backed = true } + +// Advances the parser and returns the new current token. +func (p *textParser) next() *token { + if p.backed || p.done { + p.backed = false + return &p.cur + } + p.advance() + if p.done { + p.cur.value = "" + } else if len(p.cur.value) > 0 && p.cur.value[0] == '"' { + // Look for multiple quoted strings separated by whitespace, + // and concatenate them. + cat := p.cur + for { + p.skipWhitespace() + if p.done || p.s[0] != '"' { + break + } + p.advance() + if p.cur.err != nil { + return &p.cur + } + cat.value += " " + p.cur.value + cat.unquoted += p.cur.unquoted + } + p.done = false // parser may have seen EOF, but we want to return cat + p.cur = cat + } + return &p.cur +} + +func (p *textParser) consumeToken(s string) error { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value != s { + p.back() + return p.errorf("expected %q, found %q", s, tok.value) + } + return nil +} + +// Return a RequiredNotSetError indicating which required field was not set. +func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError { + st := sv.Type() + sprops := GetProperties(st) + for i := 0; i < st.NumField(); i++ { + if !isNil(sv.Field(i)) { + continue + } + + props := sprops.Prop[i] + if props.Required { + return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)} + } + } + return &RequiredNotSetError{fmt.Sprintf("%v.", st)} // should not happen +} + +// Returns the index in the struct for the named field, as well as the parsed tag properties. +func structFieldByName(st reflect.Type, name string) (int, *Properties, bool) { + sprops := GetProperties(st) + i, ok := sprops.decoderOrigNames[name] + if ok { + return i, sprops.Prop[i], true + } + return -1, nil, false +} + +// Consume a ':' from the input stream (if the next token is a colon), +// returning an error if a colon is needed but not present. +func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value != ":" { + // Colon is optional when the field is a group or message. + needColon := true + switch props.Wire { + case "group": + needColon = false + case "bytes": + // A "bytes" field is either a message, a string, or a repeated field; + // those three become *T, *string and []T respectively, so we can check for + // this field being a pointer to a non-string. + if typ.Kind() == reflect.Ptr { + // *T or *string + if typ.Elem().Kind() == reflect.String { + break + } + } else if typ.Kind() == reflect.Slice { + // []T or []*T + if typ.Elem().Kind() != reflect.Ptr { + break + } + } else if typ.Kind() == reflect.String { + // The proto3 exception is for a string field, + // which requires a colon. + break + } + needColon = false + } + if needColon { + return p.errorf("expected ':', found %q", tok.value) + } + p.back() + } + return nil +} + +func (p *textParser) readStruct(sv reflect.Value, terminator string) error { + st := sv.Type() + reqCount := GetProperties(st).reqCount + var reqFieldErr error + fieldSet := make(map[string]bool) + // A struct is a sequence of "name: value", terminated by one of + // '>' or '}', or the end of the input. A name may also be + // "[extension]". + for { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value == terminator { + break + } + if tok.value == "[" { + // Looks like an extension. + // + // TODO: Check whether we need to handle + // namespace rooted names (e.g. ".something.Foo"). + tok = p.next() + if tok.err != nil { + return tok.err + } + var desc *ExtensionDesc + // This could be faster, but it's functional. + // TODO: Do something smarter than a linear scan. + for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) { + if d.Name == tok.value { + desc = d + break + } + } + if desc == nil { + return p.errorf("unrecognized extension %q", tok.value) + } + // Check the extension terminator. + tok = p.next() + if tok.err != nil { + return tok.err + } + if tok.value != "]" { + return p.errorf("unrecognized extension terminator %q", tok.value) + } + + props := &Properties{} + props.Parse(desc.Tag) + + typ := reflect.TypeOf(desc.ExtensionType) + if err := p.checkForColon(props, typ); err != nil { + return err + } + + rep := desc.repeated() + + // Read the extension structure, and set it in + // the value we're constructing. + var ext reflect.Value + if !rep { + ext = reflect.New(typ).Elem() + } else { + ext = reflect.New(typ.Elem()).Elem() + } + if err := p.readAny(ext, props); err != nil { + if _, ok := err.(*RequiredNotSetError); !ok { + return err + } + reqFieldErr = err + } + ep := sv.Addr().Interface().(extendableProto) + if !rep { + SetExtension(ep, desc, ext.Interface()) + } else { + old, err := GetExtension(ep, desc) + var sl reflect.Value + if err == nil { + sl = reflect.ValueOf(old) // existing slice + } else { + sl = reflect.MakeSlice(typ, 0, 1) + } + sl = reflect.Append(sl, ext) + SetExtension(ep, desc, sl.Interface()) + } + } else { + // This is a normal, non-extension field. + name := tok.value + fi, props, ok := structFieldByName(st, name) + if !ok { + return p.errorf("unknown field name %q in %v", name, st) + } + + dst := sv.Field(fi) + + if dst.Kind() == reflect.Map { + // Consume any colon. + if err := p.checkForColon(props, dst.Type()); err != nil { + return err + } + + // Construct the map if it doesn't already exist. + if dst.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) + } + key := reflect.New(dst.Type().Key()).Elem() + val := reflect.New(dst.Type().Elem()).Elem() + + // The map entry should be this sequence of tokens: + // < key : KEY value : VALUE > + // Technically the "key" and "value" could come in any order, + // but in practice they won't. + + tok := p.next() + var terminator string + switch tok.value { + case "<": + terminator = ">" + case "{": + terminator = "}" + default: + return p.errorf("expected '{' or '<', found %q", tok.value) + } + if err := p.consumeToken("key"); err != nil { + return err + } + if err := p.consumeToken(":"); err != nil { + return err + } + if err := p.readAny(key, props.mkeyprop); err != nil { + return err + } + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + if err := p.consumeToken("value"); err != nil { + return err + } + if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil { + return err + } + if err := p.readAny(val, props.mvalprop); err != nil { + return err + } + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + if err := p.consumeToken(terminator); err != nil { + return err + } + + dst.SetMapIndex(key, val) + continue + } + + // Check that it's not already set if it's not a repeated field. + if !props.Repeated && fieldSet[name] { + return p.errorf("non-repeated field %q was repeated", name) + } + + if err := p.checkForColon(props, st.Field(fi).Type); err != nil { + return err + } + + // Parse into the field. + fieldSet[name] = true + if err := p.readAny(dst, props); err != nil { + if _, ok := err.(*RequiredNotSetError); !ok { + return err + } + reqFieldErr = err + } else if props.Required { + reqCount-- + } + } + + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + + } + + if reqCount > 0 { + return p.missingRequiredFieldError(sv) + } + return reqFieldErr +} + +// consumeOptionalSeparator consumes an optional semicolon or comma. +// It is used in readStruct to provide backward compatibility. +func (p *textParser) consumeOptionalSeparator() error { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value != ";" && tok.value != "," { + p.back() + } + return nil +} + +func (p *textParser) readAny(v reflect.Value, props *Properties) error { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value == "" { + return p.errorf("unexpected EOF") + } + + switch fv := v; fv.Kind() { + case reflect.Slice: + at := v.Type() + if at.Elem().Kind() == reflect.Uint8 { + // Special case for []byte + if tok.value[0] != '"' && tok.value[0] != '\'' { + // Deliberately written out here, as the error after + // this switch statement would write "invalid []byte: ...", + // which is not as user-friendly. + return p.errorf("invalid string: %v", tok.value) + } + bytes := []byte(tok.unquoted) + fv.Set(reflect.ValueOf(bytes)) + return nil + } + // Repeated field. May already exist. + flen := fv.Len() + if flen == fv.Cap() { + nav := reflect.MakeSlice(at, flen, 2*flen+1) + reflect.Copy(nav, fv) + fv.Set(nav) + } + fv.SetLen(flen + 1) + + // Read one. + p.back() + return p.readAny(fv.Index(flen), props) + case reflect.Bool: + // Either "true", "false", 1 or 0. + switch tok.value { + case "true", "1": + fv.SetBool(true) + return nil + case "false", "0": + fv.SetBool(false) + return nil + } + case reflect.Float32, reflect.Float64: + v := tok.value + // Ignore 'f' for compatibility with output generated by C++, but don't + // remove 'f' when the value is "-inf" or "inf". + if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" { + v = v[:len(v)-1] + } + if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil { + fv.SetFloat(f) + return nil + } + case reflect.Int32: + if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil { + fv.SetInt(x) + return nil + } + + if len(props.Enum) == 0 { + break + } + m, ok := enumValueMaps[props.Enum] + if !ok { + break + } + x, ok := m[tok.value] + if !ok { + break + } + fv.SetInt(int64(x)) + return nil + case reflect.Int64: + if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil { + fv.SetInt(x) + return nil + } + + case reflect.Ptr: + // A basic field (indirected through pointer), or a repeated message/group + p.back() + fv.Set(reflect.New(fv.Type().Elem())) + return p.readAny(fv.Elem(), props) + case reflect.String: + if tok.value[0] == '"' || tok.value[0] == '\'' { + fv.SetString(tok.unquoted) + return nil + } + case reflect.Struct: + var terminator string + switch tok.value { + case "{": + terminator = "}" + case "<": + terminator = ">" + default: + return p.errorf("expected '{' or '<', found %q", tok.value) + } + // TODO: Handle nested messages which implement encoding.TextUnmarshaler. + return p.readStruct(fv, terminator) + case reflect.Uint32: + if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil { + fv.SetUint(uint64(x)) + return nil + } + case reflect.Uint64: + if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil { + fv.SetUint(x) + return nil + } + } + return p.errorf("invalid %v: %v", v.Type(), tok.value) +} + +// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb +// before starting to unmarshal, so any existing data in pb is always removed. +// If a required field is not set and no other error occurs, +// UnmarshalText returns *RequiredNotSetError. +func UnmarshalText(s string, pb Message) error { + if um, ok := pb.(encoding.TextUnmarshaler); ok { + err := um.UnmarshalText([]byte(s)) + return err + } + pb.Reset() + v := reflect.ValueOf(pb) + if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil { + return pe + } + return nil +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/text_parser_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/text_parser_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/text_parser_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/text_parser_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,511 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "math" + "reflect" + "testing" + + . "github.com/golang/protobuf/proto" + proto3pb "github.com/golang/protobuf/proto/proto3_proto" + . "github.com/golang/protobuf/proto/testdata" +) + +type UnmarshalTextTest struct { + in string + err string // if "", no error expected + out *MyMessage +} + +func buildExtStructTest(text string) UnmarshalTextTest { + msg := &MyMessage{ + Count: Int32(42), + } + SetExtension(msg, E_Ext_More, &Ext{ + Data: String("Hello, world!"), + }) + return UnmarshalTextTest{in: text, out: msg} +} + +func buildExtDataTest(text string) UnmarshalTextTest { + msg := &MyMessage{ + Count: Int32(42), + } + SetExtension(msg, E_Ext_Text, String("Hello, world!")) + SetExtension(msg, E_Ext_Number, Int32(1729)) + return UnmarshalTextTest{in: text, out: msg} +} + +func buildExtRepStringTest(text string) UnmarshalTextTest { + msg := &MyMessage{ + Count: Int32(42), + } + if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil { + panic(err) + } + return UnmarshalTextTest{in: text, out: msg} +} + +var unMarshalTextTests = []UnmarshalTextTest{ + // Basic + { + in: " count:42\n name:\"Dave\" ", + out: &MyMessage{ + Count: Int32(42), + Name: String("Dave"), + }, + }, + + // Empty quoted string + { + in: `count:42 name:""`, + out: &MyMessage{ + Count: Int32(42), + Name: String(""), + }, + }, + + // Quoted string concatenation + { + in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`, + out: &MyMessage{ + Count: Int32(42), + Name: String("My name is elsewhere"), + }, + }, + + // Quoted string with escaped apostrophe + { + in: `count:42 name: "HOLIDAY - New Year\'s Day"`, + out: &MyMessage{ + Count: Int32(42), + Name: String("HOLIDAY - New Year's Day"), + }, + }, + + // Quoted string with single quote + { + in: `count:42 name: 'Roger "The Ramster" Ramjet'`, + out: &MyMessage{ + Count: Int32(42), + Name: String(`Roger "The Ramster" Ramjet`), + }, + }, + + // Quoted string with all the accepted special characters from the C++ test + { + in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"", + out: &MyMessage{ + Count: Int32(42), + Name: String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"), + }, + }, + + // Quoted string with quoted backslash + { + in: `count:42 name: "\\'xyz"`, + out: &MyMessage{ + Count: Int32(42), + Name: String(`\'xyz`), + }, + }, + + // Quoted string with UTF-8 bytes. + { + in: "count:42 name: '\303\277\302\201\xAB'", + out: &MyMessage{ + Count: Int32(42), + Name: String("\303\277\302\201\xAB"), + }, + }, + + // Bad quoted string + { + in: `inner: < host: "\0" >` + "\n", + err: `line 1.15: invalid quoted string "\0"`, + }, + + // Number too large for int64 + { + in: "count: 1 others { key: 123456789012345678901 }", + err: "line 1.23: invalid int64: 123456789012345678901", + }, + + // Number too large for int32 + { + in: "count: 1234567890123", + err: "line 1.7: invalid int32: 1234567890123", + }, + + // Number in hexadecimal + { + in: "count: 0x2beef", + out: &MyMessage{ + Count: Int32(0x2beef), + }, + }, + + // Number in octal + { + in: "count: 024601", + out: &MyMessage{ + Count: Int32(024601), + }, + }, + + // Floating point number with "f" suffix + { + in: "count: 4 others:< weight: 17.0f >", + out: &MyMessage{ + Count: Int32(4), + Others: []*OtherMessage{ + { + Weight: Float32(17), + }, + }, + }, + }, + + // Floating point positive infinity + { + in: "count: 4 bigfloat: inf", + out: &MyMessage{ + Count: Int32(4), + Bigfloat: Float64(math.Inf(1)), + }, + }, + + // Floating point negative infinity + { + in: "count: 4 bigfloat: -inf", + out: &MyMessage{ + Count: Int32(4), + Bigfloat: Float64(math.Inf(-1)), + }, + }, + + // Number too large for float32 + { + in: "others:< weight: 12345678901234567890123456789012345678901234567890 >", + err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890", + }, + + // Number posing as a quoted string + { + in: `inner: < host: 12 >` + "\n", + err: `line 1.15: invalid string: 12`, + }, + + // Quoted string posing as int32 + { + in: `count: "12"`, + err: `line 1.7: invalid int32: "12"`, + }, + + // Quoted string posing a float32 + { + in: `others:< weight: "17.4" >`, + err: `line 1.17: invalid float32: "17.4"`, + }, + + // Enum + { + in: `count:42 bikeshed: BLUE`, + out: &MyMessage{ + Count: Int32(42), + Bikeshed: MyMessage_BLUE.Enum(), + }, + }, + + // Repeated field + { + in: `count:42 pet: "horsey" pet:"bunny"`, + out: &MyMessage{ + Count: Int32(42), + Pet: []string{"horsey", "bunny"}, + }, + }, + + // Repeated message with/without colon and <>/{} + { + in: `count:42 others:{} others{} others:<> others:{}`, + out: &MyMessage{ + Count: Int32(42), + Others: []*OtherMessage{ + {}, + {}, + {}, + {}, + }, + }, + }, + + // Missing colon for inner message + { + in: `count:42 inner < host: "cauchy.syd" >`, + out: &MyMessage{ + Count: Int32(42), + Inner: &InnerMessage{ + Host: String("cauchy.syd"), + }, + }, + }, + + // Missing colon for string field + { + in: `name "Dave"`, + err: `line 1.5: expected ':', found "\"Dave\""`, + }, + + // Missing colon for int32 field + { + in: `count 42`, + err: `line 1.6: expected ':', found "42"`, + }, + + // Missing required field + { + in: `name: "Pawel"`, + err: `proto: required field "testdata.MyMessage.count" not set`, + out: &MyMessage{ + Name: String("Pawel"), + }, + }, + + // Repeated non-repeated field + { + in: `name: "Rob" name: "Russ"`, + err: `line 1.12: non-repeated field "name" was repeated`, + }, + + // Group + { + in: `count: 17 SomeGroup { group_field: 12 }`, + out: &MyMessage{ + Count: Int32(17), + Somegroup: &MyMessage_SomeGroup{ + GroupField: Int32(12), + }, + }, + }, + + // Semicolon between fields + { + in: `count:3;name:"Calvin"`, + out: &MyMessage{ + Count: Int32(3), + Name: String("Calvin"), + }, + }, + // Comma between fields + { + in: `count:4,name:"Ezekiel"`, + out: &MyMessage{ + Count: Int32(4), + Name: String("Ezekiel"), + }, + }, + + // Extension + buildExtStructTest(`count: 42 [testdata.Ext.more]:`), + buildExtStructTest(`count: 42 [testdata.Ext.more] {data:"Hello, world!"}`), + buildExtDataTest(`count: 42 [testdata.Ext.text]:"Hello, world!" [testdata.Ext.number]:1729`), + buildExtRepStringTest(`count: 42 [testdata.greeting]:"bula" [testdata.greeting]:"hola"`), + + // Big all-in-one + { + in: "count:42 # Meaning\n" + + `name:"Dave" ` + + `quote:"\"I didn't want to go.\"" ` + + `pet:"bunny" ` + + `pet:"kitty" ` + + `pet:"horsey" ` + + `inner:<` + + ` host:"footrest.syd" ` + + ` port:7001 ` + + ` connected:true ` + + `> ` + + `others:<` + + ` key:3735928559 ` + + ` value:"\x01A\a\f" ` + + `> ` + + `others:<` + + " weight:58.9 # Atomic weight of Co\n" + + ` inner:<` + + ` host:"lesha.mtv" ` + + ` port:8002 ` + + ` >` + + `>`, + out: &MyMessage{ + Count: Int32(42), + Name: String("Dave"), + Quote: String(`"I didn't want to go."`), + Pet: []string{"bunny", "kitty", "horsey"}, + Inner: &InnerMessage{ + Host: String("footrest.syd"), + Port: Int32(7001), + Connected: Bool(true), + }, + Others: []*OtherMessage{ + { + Key: Int64(3735928559), + Value: []byte{0x1, 'A', '\a', '\f'}, + }, + { + Weight: Float32(58.9), + Inner: &InnerMessage{ + Host: String("lesha.mtv"), + Port: Int32(8002), + }, + }, + }, + }, + }, +} + +func TestUnmarshalText(t *testing.T) { + for i, test := range unMarshalTextTests { + pb := new(MyMessage) + err := UnmarshalText(test.in, pb) + if test.err == "" { + // We don't expect failure. + if err != nil { + t.Errorf("Test %d: Unexpected error: %v", i, err) + } else if !reflect.DeepEqual(pb, test.out) { + t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", + i, pb, test.out) + } + } else { + // We do expect failure. + if err == nil { + t.Errorf("Test %d: Didn't get expected error: %v", i, test.err) + } else if err.Error() != test.err { + t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v", + i, err.Error(), test.err) + } else if _, ok := err.(*RequiredNotSetError); ok && test.out != nil && !reflect.DeepEqual(pb, test.out) { + t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", + i, pb, test.out) + } + } + } +} + +func TestUnmarshalTextCustomMessage(t *testing.T) { + msg := &textMessage{} + if err := UnmarshalText("custom", msg); err != nil { + t.Errorf("Unexpected error from custom unmarshal: %v", err) + } + if UnmarshalText("not custom", msg) == nil { + t.Errorf("Didn't get expected error from custom unmarshal") + } +} + +// Regression test; this caused a panic. +func TestRepeatedEnum(t *testing.T) { + pb := new(RepeatedEnum) + if err := UnmarshalText("color: RED", pb); err != nil { + t.Fatal(err) + } + exp := &RepeatedEnum{ + Color: []RepeatedEnum_Color{RepeatedEnum_RED}, + } + if !Equal(pb, exp) { + t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp) + } +} + +func TestProto3TextParsing(t *testing.T) { + m := new(proto3pb.Message) + const in = `name: "Wallace" true_scotsman: true` + want := &proto3pb.Message{ + Name: "Wallace", + TrueScotsman: true, + } + if err := UnmarshalText(in, m); err != nil { + t.Fatal(err) + } + if !Equal(m, want) { + t.Errorf("\n got %v\nwant %v", m, want) + } +} + +func TestMapParsing(t *testing.T) { + m := new(MessageWithMap) + const in = `name_mapping: name_mapping:` + + `msg_mapping:,>` + // separating commas are okay + `msg_mapping>` + // no colon after "value" + `byte_mapping:` + want := &MessageWithMap{ + NameMapping: map[int32]string{ + 1: "Beatles", + 1234: "Feist", + }, + MsgMapping: map[int64]*FloatingPoint{ + -4: {F: Float64(2.0)}, + -2: {F: Float64(4.0)}, + }, + ByteMapping: map[bool][]byte{ + true: []byte("so be it"), + }, + } + if err := UnmarshalText(in, m); err != nil { + t.Fatal(err) + } + if !Equal(m, want) { + t.Errorf("\n got %v\nwant %v", m, want) + } +} + +var benchInput string + +func init() { + benchInput = "count: 4\n" + for i := 0; i < 1000; i++ { + benchInput += "pet: \"fido\"\n" + } + + // Check it is valid input. + pb := new(MyMessage) + err := UnmarshalText(benchInput, pb) + if err != nil { + panic("Bad benchmark input: " + err.Error()) + } +} + +func BenchmarkUnmarshalText(b *testing.B) { + pb := new(MyMessage) + for i := 0; i < b.N; i++ { + UnmarshalText(benchInput, pb) + } + b.SetBytes(int64(len(benchInput))) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/proto/text_test.go charm-2.2.0/src/github.com/golang/protobuf/proto/text_test.go --- charm-2.1.1/src/github.com/golang/protobuf/proto/text_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/proto/text_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,441 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "bytes" + "errors" + "io/ioutil" + "math" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + + proto3pb "github.com/golang/protobuf/proto/proto3_proto" + pb "github.com/golang/protobuf/proto/testdata" +) + +// textMessage implements the methods that allow it to marshal and unmarshal +// itself as text. +type textMessage struct { +} + +func (*textMessage) MarshalText() ([]byte, error) { + return []byte("custom"), nil +} + +func (*textMessage) UnmarshalText(bytes []byte) error { + if string(bytes) != "custom" { + return errors.New("expected 'custom'") + } + return nil +} + +func (*textMessage) Reset() {} +func (*textMessage) String() string { return "" } +func (*textMessage) ProtoMessage() {} + +func newTestMessage() *pb.MyMessage { + msg := &pb.MyMessage{ + Count: proto.Int32(42), + Name: proto.String("Dave"), + Quote: proto.String(`"I didn't want to go."`), + Pet: []string{"bunny", "kitty", "horsey"}, + Inner: &pb.InnerMessage{ + Host: proto.String("footrest.syd"), + Port: proto.Int32(7001), + Connected: proto.Bool(true), + }, + Others: []*pb.OtherMessage{ + { + Key: proto.Int64(0xdeadbeef), + Value: []byte{1, 65, 7, 12}, + }, + { + Weight: proto.Float32(6.022), + Inner: &pb.InnerMessage{ + Host: proto.String("lesha.mtv"), + Port: proto.Int32(8002), + }, + }, + }, + Bikeshed: pb.MyMessage_BLUE.Enum(), + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(8), + }, + // One normally wouldn't do this. + // This is an undeclared tag 13, as a varint (wire type 0) with value 4. + XXX_unrecognized: []byte{13<<3 | 0, 4}, + } + ext := &pb.Ext{ + Data: proto.String("Big gobs for big rats"), + } + if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil { + panic(err) + } + greetings := []string{"adg", "easy", "cow"} + if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil { + panic(err) + } + + // Add an unknown extension. We marshal a pb.Ext, and fake the ID. + b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")}) + if err != nil { + panic(err) + } + b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...) + proto.SetRawExtension(msg, 201, b) + + // Extensions can be plain fields, too, so let's test that. + b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19) + proto.SetRawExtension(msg, 202, b) + + return msg +} + +const text = `count: 42 +name: "Dave" +quote: "\"I didn't want to go.\"" +pet: "bunny" +pet: "kitty" +pet: "horsey" +inner: < + host: "footrest.syd" + port: 7001 + connected: true +> +others: < + key: 3735928559 + value: "\001A\007\014" +> +others: < + weight: 6.022 + inner: < + host: "lesha.mtv" + port: 8002 + > +> +bikeshed: BLUE +SomeGroup { + group_field: 8 +} +/* 2 unknown bytes */ +13: 4 +[testdata.Ext.more]: < + data: "Big gobs for big rats" +> +[testdata.greeting]: "adg" +[testdata.greeting]: "easy" +[testdata.greeting]: "cow" +/* 13 unknown bytes */ +201: "\t3G skiing" +/* 3 unknown bytes */ +202: 19 +` + +func TestMarshalText(t *testing.T) { + buf := new(bytes.Buffer) + if err := proto.MarshalText(buf, newTestMessage()); err != nil { + t.Fatalf("proto.MarshalText: %v", err) + } + s := buf.String() + if s != text { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text) + } +} + +func TestMarshalTextCustomMessage(t *testing.T) { + buf := new(bytes.Buffer) + if err := proto.MarshalText(buf, &textMessage{}); err != nil { + t.Fatalf("proto.MarshalText: %v", err) + } + s := buf.String() + if s != "custom" { + t.Errorf("Got %q, expected %q", s, "custom") + } +} +func TestMarshalTextNil(t *testing.T) { + want := "" + tests := []proto.Message{nil, (*pb.MyMessage)(nil)} + for i, test := range tests { + buf := new(bytes.Buffer) + if err := proto.MarshalText(buf, test); err != nil { + t.Fatal(err) + } + if got := buf.String(); got != want { + t.Errorf("%d: got %q want %q", i, got, want) + } + } +} + +func TestMarshalTextUnknownEnum(t *testing.T) { + // The Color enum only specifies values 0-2. + m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()} + got := m.String() + const want = `bikeshed:3 ` + if got != want { + t.Errorf("\n got %q\nwant %q", got, want) + } +} + +func BenchmarkMarshalTextBuffered(b *testing.B) { + buf := new(bytes.Buffer) + m := newTestMessage() + for i := 0; i < b.N; i++ { + buf.Reset() + proto.MarshalText(buf, m) + } +} + +func BenchmarkMarshalTextUnbuffered(b *testing.B) { + w := ioutil.Discard + m := newTestMessage() + for i := 0; i < b.N; i++ { + proto.MarshalText(w, m) + } +} + +func compact(src string) string { + // s/[ \n]+/ /g; s/ $//; + dst := make([]byte, len(src)) + space, comment := false, false + j := 0 + for i := 0; i < len(src); i++ { + if strings.HasPrefix(src[i:], "/*") { + comment = true + i++ + continue + } + if comment && strings.HasPrefix(src[i:], "*/") { + comment = false + i++ + continue + } + if comment { + continue + } + c := src[i] + if c == ' ' || c == '\n' { + space = true + continue + } + if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') { + space = false + } + if c == '{' { + space = false + } + if space { + dst[j] = ' ' + j++ + space = false + } + dst[j] = c + j++ + } + if space { + dst[j] = ' ' + j++ + } + return string(dst[0:j]) +} + +var compactText = compact(text) + +func TestCompactText(t *testing.T) { + s := proto.CompactTextString(newTestMessage()) + if s != compactText { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText) + } +} + +func TestStringEscaping(t *testing.T) { + testCases := []struct { + in *pb.Strings + out string + }{ + { + // Test data from C++ test (TextFormatTest.StringEscape). + // Single divergence: we don't escape apostrophes. + &pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")}, + "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n", + }, + { + // Test data from the same C++ test. + &pb.Strings{StringField: proto.String("\350\260\267\346\255\214")}, + "string_field: \"\\350\\260\\267\\346\\255\\214\"\n", + }, + { + // Some UTF-8. + &pb.Strings{StringField: proto.String("\x00\x01\xff\x81")}, + `string_field: "\000\001\377\201"` + "\n", + }, + } + + for i, tc := range testCases { + var buf bytes.Buffer + if err := proto.MarshalText(&buf, tc.in); err != nil { + t.Errorf("proto.MarsalText: %v", err) + continue + } + s := buf.String() + if s != tc.out { + t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out) + continue + } + + // Check round-trip. + pb := new(pb.Strings) + if err := proto.UnmarshalText(s, pb); err != nil { + t.Errorf("#%d: UnmarshalText: %v", i, err) + continue + } + if !proto.Equal(pb, tc.in) { + t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb) + } + } +} + +// A limitedWriter accepts some output before it fails. +// This is a proxy for something like a nearly-full or imminently-failing disk, +// or a network connection that is about to die. +type limitedWriter struct { + b bytes.Buffer + limit int +} + +var outOfSpace = errors.New("proto: insufficient space") + +func (w *limitedWriter) Write(p []byte) (n int, err error) { + var avail = w.limit - w.b.Len() + if avail <= 0 { + return 0, outOfSpace + } + if len(p) <= avail { + return w.b.Write(p) + } + n, _ = w.b.Write(p[:avail]) + return n, outOfSpace +} + +func TestMarshalTextFailing(t *testing.T) { + // Try lots of different sizes to exercise more error code-paths. + for lim := 0; lim < len(text); lim++ { + buf := new(limitedWriter) + buf.limit = lim + err := proto.MarshalText(buf, newTestMessage()) + // We expect a certain error, but also some partial results in the buffer. + if err != outOfSpace { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace) + } + s := buf.b.String() + x := text[:buf.limit] + if s != x { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x) + } + } +} + +func TestFloats(t *testing.T) { + tests := []struct { + f float64 + want string + }{ + {0, "0"}, + {4.7, "4.7"}, + {math.Inf(1), "inf"}, + {math.Inf(-1), "-inf"}, + {math.NaN(), "nan"}, + } + for _, test := range tests { + msg := &pb.FloatingPoint{F: &test.f} + got := strings.TrimSpace(msg.String()) + want := `f:` + test.want + if got != want { + t.Errorf("f=%f: got %q, want %q", test.f, got, want) + } + } +} + +func TestRepeatedNilText(t *testing.T) { + m := &pb.MessageList{ + Message: []*pb.MessageList_Message{ + nil, + &pb.MessageList_Message{ + Name: proto.String("Horse"), + }, + nil, + }, + } + want := `Message +Message { + name: "Horse" +} +Message +` + if s := proto.MarshalTextString(m); s != want { + t.Errorf(" got: %s\nwant: %s", s, want) + } +} + +func TestProto3Text(t *testing.T) { + tests := []struct { + m proto.Message + want string + }{ + // zero message + {&proto3pb.Message{}, ``}, + // zero message except for an empty byte slice + {&proto3pb.Message{Data: []byte{}}, ``}, + // trivial case + {&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`}, + // empty map + {&pb.MessageWithMap{}, ``}, + // non-empty map; current map format is the same as a repeated struct + { + &pb.MessageWithMap{NameMapping: map[int32]string{1234: "Feist"}}, + `name_mapping:`, + }, + // map with nil value; not well-defined, but we shouldn't crash + { + &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}}, + `msg_mapping:`, + }, + } + for _, test := range tests { + got := strings.TrimSpace(test.m.String()) + if got != test.want { + t.Errorf("\n got %s\nwant %s", got, test.want) + } + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,1635 @@ +// Code generated by protoc-gen-go. +// source: google/protobuf/descriptor.proto +// DO NOT EDIT! + +/* +Package google_protobuf is a generated protocol buffer package. + +It is generated from these files: + google/protobuf/descriptor.proto + +It has these top-level messages: + FileDescriptorSet + FileDescriptorProto + DescriptorProto + FieldDescriptorProto + OneofDescriptorProto + EnumDescriptorProto + EnumValueDescriptorProto + ServiceDescriptorProto + MethodDescriptorProto + FileOptions + MessageOptions + FieldOptions + EnumOptions + EnumValueOptions + ServiceOptions + MethodOptions + UninterpretedOption + SourceCodeInfo +*/ +package descriptor + +import proto "github.com/golang/protobuf/proto" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +type FieldDescriptorProto_Type int32 + +const ( + // 0 is reserved for errors. + // Order is weird for historical reasons. + FieldDescriptorProto_TYPE_DOUBLE FieldDescriptorProto_Type = 1 + FieldDescriptorProto_TYPE_FLOAT FieldDescriptorProto_Type = 2 + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + FieldDescriptorProto_TYPE_INT64 FieldDescriptorProto_Type = 3 + FieldDescriptorProto_TYPE_UINT64 FieldDescriptorProto_Type = 4 + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + FieldDescriptorProto_TYPE_INT32 FieldDescriptorProto_Type = 5 + FieldDescriptorProto_TYPE_FIXED64 FieldDescriptorProto_Type = 6 + FieldDescriptorProto_TYPE_FIXED32 FieldDescriptorProto_Type = 7 + FieldDescriptorProto_TYPE_BOOL FieldDescriptorProto_Type = 8 + FieldDescriptorProto_TYPE_STRING FieldDescriptorProto_Type = 9 + FieldDescriptorProto_TYPE_GROUP FieldDescriptorProto_Type = 10 + FieldDescriptorProto_TYPE_MESSAGE FieldDescriptorProto_Type = 11 + // New in version 2. + FieldDescriptorProto_TYPE_BYTES FieldDescriptorProto_Type = 12 + FieldDescriptorProto_TYPE_UINT32 FieldDescriptorProto_Type = 13 + FieldDescriptorProto_TYPE_ENUM FieldDescriptorProto_Type = 14 + FieldDescriptorProto_TYPE_SFIXED32 FieldDescriptorProto_Type = 15 + FieldDescriptorProto_TYPE_SFIXED64 FieldDescriptorProto_Type = 16 + FieldDescriptorProto_TYPE_SINT32 FieldDescriptorProto_Type = 17 + FieldDescriptorProto_TYPE_SINT64 FieldDescriptorProto_Type = 18 +) + +var FieldDescriptorProto_Type_name = map[int32]string{ + 1: "TYPE_DOUBLE", + 2: "TYPE_FLOAT", + 3: "TYPE_INT64", + 4: "TYPE_UINT64", + 5: "TYPE_INT32", + 6: "TYPE_FIXED64", + 7: "TYPE_FIXED32", + 8: "TYPE_BOOL", + 9: "TYPE_STRING", + 10: "TYPE_GROUP", + 11: "TYPE_MESSAGE", + 12: "TYPE_BYTES", + 13: "TYPE_UINT32", + 14: "TYPE_ENUM", + 15: "TYPE_SFIXED32", + 16: "TYPE_SFIXED64", + 17: "TYPE_SINT32", + 18: "TYPE_SINT64", +} +var FieldDescriptorProto_Type_value = map[string]int32{ + "TYPE_DOUBLE": 1, + "TYPE_FLOAT": 2, + "TYPE_INT64": 3, + "TYPE_UINT64": 4, + "TYPE_INT32": 5, + "TYPE_FIXED64": 6, + "TYPE_FIXED32": 7, + "TYPE_BOOL": 8, + "TYPE_STRING": 9, + "TYPE_GROUP": 10, + "TYPE_MESSAGE": 11, + "TYPE_BYTES": 12, + "TYPE_UINT32": 13, + "TYPE_ENUM": 14, + "TYPE_SFIXED32": 15, + "TYPE_SFIXED64": 16, + "TYPE_SINT32": 17, + "TYPE_SINT64": 18, +} + +func (x FieldDescriptorProto_Type) Enum() *FieldDescriptorProto_Type { + p := new(FieldDescriptorProto_Type) + *p = x + return p +} +func (x FieldDescriptorProto_Type) String() string { + return proto.EnumName(FieldDescriptorProto_Type_name, int32(x)) +} +func (x *FieldDescriptorProto_Type) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FieldDescriptorProto_Type_value, data, "FieldDescriptorProto_Type") + if err != nil { + return err + } + *x = FieldDescriptorProto_Type(value) + return nil +} + +type FieldDescriptorProto_Label int32 + +const ( + // 0 is reserved for errors + FieldDescriptorProto_LABEL_OPTIONAL FieldDescriptorProto_Label = 1 + FieldDescriptorProto_LABEL_REQUIRED FieldDescriptorProto_Label = 2 + FieldDescriptorProto_LABEL_REPEATED FieldDescriptorProto_Label = 3 +) + +var FieldDescriptorProto_Label_name = map[int32]string{ + 1: "LABEL_OPTIONAL", + 2: "LABEL_REQUIRED", + 3: "LABEL_REPEATED", +} +var FieldDescriptorProto_Label_value = map[string]int32{ + "LABEL_OPTIONAL": 1, + "LABEL_REQUIRED": 2, + "LABEL_REPEATED": 3, +} + +func (x FieldDescriptorProto_Label) Enum() *FieldDescriptorProto_Label { + p := new(FieldDescriptorProto_Label) + *p = x + return p +} +func (x FieldDescriptorProto_Label) String() string { + return proto.EnumName(FieldDescriptorProto_Label_name, int32(x)) +} +func (x *FieldDescriptorProto_Label) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FieldDescriptorProto_Label_value, data, "FieldDescriptorProto_Label") + if err != nil { + return err + } + *x = FieldDescriptorProto_Label(value) + return nil +} + +// Generated classes can be optimized for speed or code size. +type FileOptions_OptimizeMode int32 + +const ( + FileOptions_SPEED FileOptions_OptimizeMode = 1 + // etc. + FileOptions_CODE_SIZE FileOptions_OptimizeMode = 2 + FileOptions_LITE_RUNTIME FileOptions_OptimizeMode = 3 +) + +var FileOptions_OptimizeMode_name = map[int32]string{ + 1: "SPEED", + 2: "CODE_SIZE", + 3: "LITE_RUNTIME", +} +var FileOptions_OptimizeMode_value = map[string]int32{ + "SPEED": 1, + "CODE_SIZE": 2, + "LITE_RUNTIME": 3, +} + +func (x FileOptions_OptimizeMode) Enum() *FileOptions_OptimizeMode { + p := new(FileOptions_OptimizeMode) + *p = x + return p +} +func (x FileOptions_OptimizeMode) String() string { + return proto.EnumName(FileOptions_OptimizeMode_name, int32(x)) +} +func (x *FileOptions_OptimizeMode) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FileOptions_OptimizeMode_value, data, "FileOptions_OptimizeMode") + if err != nil { + return err + } + *x = FileOptions_OptimizeMode(value) + return nil +} + +type FieldOptions_CType int32 + +const ( + // Default mode. + FieldOptions_STRING FieldOptions_CType = 0 + FieldOptions_CORD FieldOptions_CType = 1 + FieldOptions_STRING_PIECE FieldOptions_CType = 2 +) + +var FieldOptions_CType_name = map[int32]string{ + 0: "STRING", + 1: "CORD", + 2: "STRING_PIECE", +} +var FieldOptions_CType_value = map[string]int32{ + "STRING": 0, + "CORD": 1, + "STRING_PIECE": 2, +} + +func (x FieldOptions_CType) Enum() *FieldOptions_CType { + p := new(FieldOptions_CType) + *p = x + return p +} +func (x FieldOptions_CType) String() string { + return proto.EnumName(FieldOptions_CType_name, int32(x)) +} +func (x *FieldOptions_CType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FieldOptions_CType_value, data, "FieldOptions_CType") + if err != nil { + return err + } + *x = FieldOptions_CType(value) + return nil +} + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +type FileDescriptorSet struct { + File []*FileDescriptorProto `protobuf:"bytes,1,rep,name=file" json:"file,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FileDescriptorSet) Reset() { *m = FileDescriptorSet{} } +func (m *FileDescriptorSet) String() string { return proto.CompactTextString(m) } +func (*FileDescriptorSet) ProtoMessage() {} + +func (m *FileDescriptorSet) GetFile() []*FileDescriptorProto { + if m != nil { + return m.File + } + return nil +} + +// Describes a complete .proto file. +type FileDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Package *string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"` + // Names of files imported by this file. + Dependency []string `protobuf:"bytes,3,rep,name=dependency" json:"dependency,omitempty"` + // Indexes of the public imported files in the dependency list above. + PublicDependency []int32 `protobuf:"varint,10,rep,name=public_dependency" json:"public_dependency,omitempty"` + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + WeakDependency []int32 `protobuf:"varint,11,rep,name=weak_dependency" json:"weak_dependency,omitempty"` + // All top-level definitions in this file. + MessageType []*DescriptorProto `protobuf:"bytes,4,rep,name=message_type" json:"message_type,omitempty"` + EnumType []*EnumDescriptorProto `protobuf:"bytes,5,rep,name=enum_type" json:"enum_type,omitempty"` + Service []*ServiceDescriptorProto `protobuf:"bytes,6,rep,name=service" json:"service,omitempty"` + Extension []*FieldDescriptorProto `protobuf:"bytes,7,rep,name=extension" json:"extension,omitempty"` + Options *FileOptions `protobuf:"bytes,8,opt,name=options" json:"options,omitempty"` + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + SourceCodeInfo *SourceCodeInfo `protobuf:"bytes,9,opt,name=source_code_info" json:"source_code_info,omitempty"` + // The syntax of the proto file. + // The supported values are "proto2" and "proto3". + Syntax *string `protobuf:"bytes,12,opt,name=syntax" json:"syntax,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FileDescriptorProto) Reset() { *m = FileDescriptorProto{} } +func (m *FileDescriptorProto) String() string { return proto.CompactTextString(m) } +func (*FileDescriptorProto) ProtoMessage() {} + +func (m *FileDescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *FileDescriptorProto) GetPackage() string { + if m != nil && m.Package != nil { + return *m.Package + } + return "" +} + +func (m *FileDescriptorProto) GetDependency() []string { + if m != nil { + return m.Dependency + } + return nil +} + +func (m *FileDescriptorProto) GetPublicDependency() []int32 { + if m != nil { + return m.PublicDependency + } + return nil +} + +func (m *FileDescriptorProto) GetWeakDependency() []int32 { + if m != nil { + return m.WeakDependency + } + return nil +} + +func (m *FileDescriptorProto) GetMessageType() []*DescriptorProto { + if m != nil { + return m.MessageType + } + return nil +} + +func (m *FileDescriptorProto) GetEnumType() []*EnumDescriptorProto { + if m != nil { + return m.EnumType + } + return nil +} + +func (m *FileDescriptorProto) GetService() []*ServiceDescriptorProto { + if m != nil { + return m.Service + } + return nil +} + +func (m *FileDescriptorProto) GetExtension() []*FieldDescriptorProto { + if m != nil { + return m.Extension + } + return nil +} + +func (m *FileDescriptorProto) GetOptions() *FileOptions { + if m != nil { + return m.Options + } + return nil +} + +func (m *FileDescriptorProto) GetSourceCodeInfo() *SourceCodeInfo { + if m != nil { + return m.SourceCodeInfo + } + return nil +} + +func (m *FileDescriptorProto) GetSyntax() string { + if m != nil && m.Syntax != nil { + return *m.Syntax + } + return "" +} + +// Describes a message type. +type DescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Field []*FieldDescriptorProto `protobuf:"bytes,2,rep,name=field" json:"field,omitempty"` + Extension []*FieldDescriptorProto `protobuf:"bytes,6,rep,name=extension" json:"extension,omitempty"` + NestedType []*DescriptorProto `protobuf:"bytes,3,rep,name=nested_type" json:"nested_type,omitempty"` + EnumType []*EnumDescriptorProto `protobuf:"bytes,4,rep,name=enum_type" json:"enum_type,omitempty"` + ExtensionRange []*DescriptorProto_ExtensionRange `protobuf:"bytes,5,rep,name=extension_range" json:"extension_range,omitempty"` + OneofDecl []*OneofDescriptorProto `protobuf:"bytes,8,rep,name=oneof_decl" json:"oneof_decl,omitempty"` + Options *MessageOptions `protobuf:"bytes,7,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DescriptorProto) Reset() { *m = DescriptorProto{} } +func (m *DescriptorProto) String() string { return proto.CompactTextString(m) } +func (*DescriptorProto) ProtoMessage() {} + +func (m *DescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *DescriptorProto) GetField() []*FieldDescriptorProto { + if m != nil { + return m.Field + } + return nil +} + +func (m *DescriptorProto) GetExtension() []*FieldDescriptorProto { + if m != nil { + return m.Extension + } + return nil +} + +func (m *DescriptorProto) GetNestedType() []*DescriptorProto { + if m != nil { + return m.NestedType + } + return nil +} + +func (m *DescriptorProto) GetEnumType() []*EnumDescriptorProto { + if m != nil { + return m.EnumType + } + return nil +} + +func (m *DescriptorProto) GetExtensionRange() []*DescriptorProto_ExtensionRange { + if m != nil { + return m.ExtensionRange + } + return nil +} + +func (m *DescriptorProto) GetOneofDecl() []*OneofDescriptorProto { + if m != nil { + return m.OneofDecl + } + return nil +} + +func (m *DescriptorProto) GetOptions() *MessageOptions { + if m != nil { + return m.Options + } + return nil +} + +type DescriptorProto_ExtensionRange struct { + Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` + End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DescriptorProto_ExtensionRange) Reset() { *m = DescriptorProto_ExtensionRange{} } +func (m *DescriptorProto_ExtensionRange) String() string { return proto.CompactTextString(m) } +func (*DescriptorProto_ExtensionRange) ProtoMessage() {} + +func (m *DescriptorProto_ExtensionRange) GetStart() int32 { + if m != nil && m.Start != nil { + return *m.Start + } + return 0 +} + +func (m *DescriptorProto_ExtensionRange) GetEnd() int32 { + if m != nil && m.End != nil { + return *m.End + } + return 0 +} + +// Describes a field within a message. +type FieldDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Number *int32 `protobuf:"varint,3,opt,name=number" json:"number,omitempty"` + Label *FieldDescriptorProto_Label `protobuf:"varint,4,opt,name=label,enum=google.protobuf.FieldDescriptorProto_Label" json:"label,omitempty"` + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + Type *FieldDescriptorProto_Type `protobuf:"varint,5,opt,name=type,enum=google.protobuf.FieldDescriptorProto_Type" json:"type,omitempty"` + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + TypeName *string `protobuf:"bytes,6,opt,name=type_name" json:"type_name,omitempty"` + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + Extendee *string `protobuf:"bytes,2,opt,name=extendee" json:"extendee,omitempty"` + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + // TODO(kenton): Base-64 encode? + DefaultValue *string `protobuf:"bytes,7,opt,name=default_value" json:"default_value,omitempty"` + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. Extensions of a oneof should + // not set this since the oneof to which they belong will be inferred based + // on the extension range containing the extension's field number. + OneofIndex *int32 `protobuf:"varint,9,opt,name=oneof_index" json:"oneof_index,omitempty"` + Options *FieldOptions `protobuf:"bytes,8,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FieldDescriptorProto) Reset() { *m = FieldDescriptorProto{} } +func (m *FieldDescriptorProto) String() string { return proto.CompactTextString(m) } +func (*FieldDescriptorProto) ProtoMessage() {} + +func (m *FieldDescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *FieldDescriptorProto) GetNumber() int32 { + if m != nil && m.Number != nil { + return *m.Number + } + return 0 +} + +func (m *FieldDescriptorProto) GetLabel() FieldDescriptorProto_Label { + if m != nil && m.Label != nil { + return *m.Label + } + return FieldDescriptorProto_LABEL_OPTIONAL +} + +func (m *FieldDescriptorProto) GetType() FieldDescriptorProto_Type { + if m != nil && m.Type != nil { + return *m.Type + } + return FieldDescriptorProto_TYPE_DOUBLE +} + +func (m *FieldDescriptorProto) GetTypeName() string { + if m != nil && m.TypeName != nil { + return *m.TypeName + } + return "" +} + +func (m *FieldDescriptorProto) GetExtendee() string { + if m != nil && m.Extendee != nil { + return *m.Extendee + } + return "" +} + +func (m *FieldDescriptorProto) GetDefaultValue() string { + if m != nil && m.DefaultValue != nil { + return *m.DefaultValue + } + return "" +} + +func (m *FieldDescriptorProto) GetOneofIndex() int32 { + if m != nil && m.OneofIndex != nil { + return *m.OneofIndex + } + return 0 +} + +func (m *FieldDescriptorProto) GetOptions() *FieldOptions { + if m != nil { + return m.Options + } + return nil +} + +// Describes a oneof. +type OneofDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OneofDescriptorProto) Reset() { *m = OneofDescriptorProto{} } +func (m *OneofDescriptorProto) String() string { return proto.CompactTextString(m) } +func (*OneofDescriptorProto) ProtoMessage() {} + +func (m *OneofDescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +// Describes an enum type. +type EnumDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Value []*EnumValueDescriptorProto `protobuf:"bytes,2,rep,name=value" json:"value,omitempty"` + Options *EnumOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EnumDescriptorProto) Reset() { *m = EnumDescriptorProto{} } +func (m *EnumDescriptorProto) String() string { return proto.CompactTextString(m) } +func (*EnumDescriptorProto) ProtoMessage() {} + +func (m *EnumDescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *EnumDescriptorProto) GetValue() []*EnumValueDescriptorProto { + if m != nil { + return m.Value + } + return nil +} + +func (m *EnumDescriptorProto) GetOptions() *EnumOptions { + if m != nil { + return m.Options + } + return nil +} + +// Describes a value within an enum. +type EnumValueDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Number *int32 `protobuf:"varint,2,opt,name=number" json:"number,omitempty"` + Options *EnumValueOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EnumValueDescriptorProto) Reset() { *m = EnumValueDescriptorProto{} } +func (m *EnumValueDescriptorProto) String() string { return proto.CompactTextString(m) } +func (*EnumValueDescriptorProto) ProtoMessage() {} + +func (m *EnumValueDescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *EnumValueDescriptorProto) GetNumber() int32 { + if m != nil && m.Number != nil { + return *m.Number + } + return 0 +} + +func (m *EnumValueDescriptorProto) GetOptions() *EnumValueOptions { + if m != nil { + return m.Options + } + return nil +} + +// Describes a service. +type ServiceDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Method []*MethodDescriptorProto `protobuf:"bytes,2,rep,name=method" json:"method,omitempty"` + Options *ServiceOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ServiceDescriptorProto) Reset() { *m = ServiceDescriptorProto{} } +func (m *ServiceDescriptorProto) String() string { return proto.CompactTextString(m) } +func (*ServiceDescriptorProto) ProtoMessage() {} + +func (m *ServiceDescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *ServiceDescriptorProto) GetMethod() []*MethodDescriptorProto { + if m != nil { + return m.Method + } + return nil +} + +func (m *ServiceDescriptorProto) GetOptions() *ServiceOptions { + if m != nil { + return m.Options + } + return nil +} + +// Describes a method of a service. +type MethodDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + InputType *string `protobuf:"bytes,2,opt,name=input_type" json:"input_type,omitempty"` + OutputType *string `protobuf:"bytes,3,opt,name=output_type" json:"output_type,omitempty"` + Options *MethodOptions `protobuf:"bytes,4,opt,name=options" json:"options,omitempty"` + // Identifies if client streams multiple client messages + ClientStreaming *bool `protobuf:"varint,5,opt,name=client_streaming,def=0" json:"client_streaming,omitempty"` + // Identifies if server streams multiple server messages + ServerStreaming *bool `protobuf:"varint,6,opt,name=server_streaming,def=0" json:"server_streaming,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MethodDescriptorProto) Reset() { *m = MethodDescriptorProto{} } +func (m *MethodDescriptorProto) String() string { return proto.CompactTextString(m) } +func (*MethodDescriptorProto) ProtoMessage() {} + +const Default_MethodDescriptorProto_ClientStreaming bool = false +const Default_MethodDescriptorProto_ServerStreaming bool = false + +func (m *MethodDescriptorProto) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *MethodDescriptorProto) GetInputType() string { + if m != nil && m.InputType != nil { + return *m.InputType + } + return "" +} + +func (m *MethodDescriptorProto) GetOutputType() string { + if m != nil && m.OutputType != nil { + return *m.OutputType + } + return "" +} + +func (m *MethodDescriptorProto) GetOptions() *MethodOptions { + if m != nil { + return m.Options + } + return nil +} + +func (m *MethodDescriptorProto) GetClientStreaming() bool { + if m != nil && m.ClientStreaming != nil { + return *m.ClientStreaming + } + return Default_MethodDescriptorProto_ClientStreaming +} + +func (m *MethodDescriptorProto) GetServerStreaming() bool { + if m != nil && m.ServerStreaming != nil { + return *m.ServerStreaming + } + return Default_MethodDescriptorProto_ServerStreaming +} + +type FileOptions struct { + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + JavaPackage *string `protobuf:"bytes,1,opt,name=java_package" json:"java_package,omitempty"` + // If set, all the classes from the .proto file are wrapped in a single + // outer class with the given name. This applies to both Proto1 + // (equivalent to the old "--one_java_file" option) and Proto2 (where + // a .proto always translates to a single class, but you may want to + // explicitly choose the class name). + JavaOuterClassname *string `protobuf:"bytes,8,opt,name=java_outer_classname" json:"java_outer_classname,omitempty"` + // If set true, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the outer class + // named by java_outer_classname. However, the outer class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + JavaMultipleFiles *bool `protobuf:"varint,10,opt,name=java_multiple_files,def=0" json:"java_multiple_files,omitempty"` + // If set true, then the Java code generator will generate equals() and + // hashCode() methods for all messages defined in the .proto file. + // - In the full runtime, this is purely a speed optimization, as the + // AbstractMessage base class includes reflection-based implementations of + // these methods. + // - In the lite runtime, setting this option changes the semantics of + // equals() and hashCode() to more closely match those of the full runtime; + // the generated methods compute their results based on field values rather + // than object identity. (Implementations should not assume that hashcodes + // will be consistent across runtimes or versions of the protocol compiler.) + JavaGenerateEqualsAndHash *bool `protobuf:"varint,20,opt,name=java_generate_equals_and_hash,def=0" json:"java_generate_equals_and_hash,omitempty"` + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + JavaStringCheckUtf8 *bool `protobuf:"varint,27,opt,name=java_string_check_utf8,def=0" json:"java_string_check_utf8,omitempty"` + OptimizeFor *FileOptions_OptimizeMode `protobuf:"varint,9,opt,name=optimize_for,enum=google.protobuf.FileOptions_OptimizeMode,def=1" json:"optimize_for,omitempty"` + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + GoPackage *string `protobuf:"bytes,11,opt,name=go_package" json:"go_package,omitempty"` + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + CcGenericServices *bool `protobuf:"varint,16,opt,name=cc_generic_services,def=0" json:"cc_generic_services,omitempty"` + JavaGenericServices *bool `protobuf:"varint,17,opt,name=java_generic_services,def=0" json:"java_generic_services,omitempty"` + PyGenericServices *bool `protobuf:"varint,18,opt,name=py_generic_services,def=0" json:"py_generic_services,omitempty"` + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + Deprecated *bool `protobuf:"varint,23,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + CcEnableArenas *bool `protobuf:"varint,31,opt,name=cc_enable_arenas,def=0" json:"cc_enable_arenas,omitempty"` + // The parser stores options it doesn't recognize here. See above. + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FileOptions) Reset() { *m = FileOptions{} } +func (m *FileOptions) String() string { return proto.CompactTextString(m) } +func (*FileOptions) ProtoMessage() {} + +var extRange_FileOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*FileOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_FileOptions +} +func (m *FileOptions) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +const Default_FileOptions_JavaMultipleFiles bool = false +const Default_FileOptions_JavaGenerateEqualsAndHash bool = false +const Default_FileOptions_JavaStringCheckUtf8 bool = false +const Default_FileOptions_OptimizeFor FileOptions_OptimizeMode = FileOptions_SPEED +const Default_FileOptions_CcGenericServices bool = false +const Default_FileOptions_JavaGenericServices bool = false +const Default_FileOptions_PyGenericServices bool = false +const Default_FileOptions_Deprecated bool = false +const Default_FileOptions_CcEnableArenas bool = false + +func (m *FileOptions) GetJavaPackage() string { + if m != nil && m.JavaPackage != nil { + return *m.JavaPackage + } + return "" +} + +func (m *FileOptions) GetJavaOuterClassname() string { + if m != nil && m.JavaOuterClassname != nil { + return *m.JavaOuterClassname + } + return "" +} + +func (m *FileOptions) GetJavaMultipleFiles() bool { + if m != nil && m.JavaMultipleFiles != nil { + return *m.JavaMultipleFiles + } + return Default_FileOptions_JavaMultipleFiles +} + +func (m *FileOptions) GetJavaGenerateEqualsAndHash() bool { + if m != nil && m.JavaGenerateEqualsAndHash != nil { + return *m.JavaGenerateEqualsAndHash + } + return Default_FileOptions_JavaGenerateEqualsAndHash +} + +func (m *FileOptions) GetJavaStringCheckUtf8() bool { + if m != nil && m.JavaStringCheckUtf8 != nil { + return *m.JavaStringCheckUtf8 + } + return Default_FileOptions_JavaStringCheckUtf8 +} + +func (m *FileOptions) GetOptimizeFor() FileOptions_OptimizeMode { + if m != nil && m.OptimizeFor != nil { + return *m.OptimizeFor + } + return Default_FileOptions_OptimizeFor +} + +func (m *FileOptions) GetGoPackage() string { + if m != nil && m.GoPackage != nil { + return *m.GoPackage + } + return "" +} + +func (m *FileOptions) GetCcGenericServices() bool { + if m != nil && m.CcGenericServices != nil { + return *m.CcGenericServices + } + return Default_FileOptions_CcGenericServices +} + +func (m *FileOptions) GetJavaGenericServices() bool { + if m != nil && m.JavaGenericServices != nil { + return *m.JavaGenericServices + } + return Default_FileOptions_JavaGenericServices +} + +func (m *FileOptions) GetPyGenericServices() bool { + if m != nil && m.PyGenericServices != nil { + return *m.PyGenericServices + } + return Default_FileOptions_PyGenericServices +} + +func (m *FileOptions) GetDeprecated() bool { + if m != nil && m.Deprecated != nil { + return *m.Deprecated + } + return Default_FileOptions_Deprecated +} + +func (m *FileOptions) GetCcEnableArenas() bool { + if m != nil && m.CcEnableArenas != nil { + return *m.CcEnableArenas + } + return Default_FileOptions_CcEnableArenas +} + +func (m *FileOptions) GetUninterpretedOption() []*UninterpretedOption { + if m != nil { + return m.UninterpretedOption + } + return nil +} + +type MessageOptions struct { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + MessageSetWireFormat *bool `protobuf:"varint,1,opt,name=message_set_wire_format,def=0" json:"message_set_wire_format,omitempty"` + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + NoStandardDescriptorAccessor *bool `protobuf:"varint,2,opt,name=no_standard_descriptor_accessor,def=0" json:"no_standard_descriptor_accessor,omitempty"` + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + Deprecated *bool `protobuf:"varint,3,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementions still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + MapEntry *bool `protobuf:"varint,7,opt,name=map_entry" json:"map_entry,omitempty"` + // The parser stores options it doesn't recognize here. See above. + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageOptions) Reset() { *m = MessageOptions{} } +func (m *MessageOptions) String() string { return proto.CompactTextString(m) } +func (*MessageOptions) ProtoMessage() {} + +var extRange_MessageOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*MessageOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MessageOptions +} +func (m *MessageOptions) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +const Default_MessageOptions_MessageSetWireFormat bool = false +const Default_MessageOptions_NoStandardDescriptorAccessor bool = false +const Default_MessageOptions_Deprecated bool = false + +func (m *MessageOptions) GetMessageSetWireFormat() bool { + if m != nil && m.MessageSetWireFormat != nil { + return *m.MessageSetWireFormat + } + return Default_MessageOptions_MessageSetWireFormat +} + +func (m *MessageOptions) GetNoStandardDescriptorAccessor() bool { + if m != nil && m.NoStandardDescriptorAccessor != nil { + return *m.NoStandardDescriptorAccessor + } + return Default_MessageOptions_NoStandardDescriptorAccessor +} + +func (m *MessageOptions) GetDeprecated() bool { + if m != nil && m.Deprecated != nil { + return *m.Deprecated + } + return Default_MessageOptions_Deprecated +} + +func (m *MessageOptions) GetMapEntry() bool { + if m != nil && m.MapEntry != nil { + return *m.MapEntry + } + return false +} + +func (m *MessageOptions) GetUninterpretedOption() []*UninterpretedOption { + if m != nil { + return m.UninterpretedOption + } + return nil +} + +type FieldOptions struct { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is not yet implemented in the open source + // release -- sorry, we'll try to include it in a future version! + Ctype *FieldOptions_CType `protobuf:"varint,1,opt,name=ctype,enum=google.protobuf.FieldOptions_CType,def=0" json:"ctype,omitempty"` + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. + Packed *bool `protobuf:"varint,2,opt,name=packed" json:"packed,omitempty"` + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outher message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + Lazy *bool `protobuf:"varint,5,opt,name=lazy,def=0" json:"lazy,omitempty"` + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + Deprecated *bool `protobuf:"varint,3,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // For Google-internal migration only. Do not use. + Weak *bool `protobuf:"varint,10,opt,name=weak,def=0" json:"weak,omitempty"` + // The parser stores options it doesn't recognize here. See above. + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FieldOptions) Reset() { *m = FieldOptions{} } +func (m *FieldOptions) String() string { return proto.CompactTextString(m) } +func (*FieldOptions) ProtoMessage() {} + +var extRange_FieldOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*FieldOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_FieldOptions +} +func (m *FieldOptions) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +const Default_FieldOptions_Ctype FieldOptions_CType = FieldOptions_STRING +const Default_FieldOptions_Lazy bool = false +const Default_FieldOptions_Deprecated bool = false +const Default_FieldOptions_Weak bool = false + +func (m *FieldOptions) GetCtype() FieldOptions_CType { + if m != nil && m.Ctype != nil { + return *m.Ctype + } + return Default_FieldOptions_Ctype +} + +func (m *FieldOptions) GetPacked() bool { + if m != nil && m.Packed != nil { + return *m.Packed + } + return false +} + +func (m *FieldOptions) GetLazy() bool { + if m != nil && m.Lazy != nil { + return *m.Lazy + } + return Default_FieldOptions_Lazy +} + +func (m *FieldOptions) GetDeprecated() bool { + if m != nil && m.Deprecated != nil { + return *m.Deprecated + } + return Default_FieldOptions_Deprecated +} + +func (m *FieldOptions) GetWeak() bool { + if m != nil && m.Weak != nil { + return *m.Weak + } + return Default_FieldOptions_Weak +} + +func (m *FieldOptions) GetUninterpretedOption() []*UninterpretedOption { + if m != nil { + return m.UninterpretedOption + } + return nil +} + +type EnumOptions struct { + // Set this option to true to allow mapping different tag names to the same + // value. + AllowAlias *bool `protobuf:"varint,2,opt,name=allow_alias" json:"allow_alias,omitempty"` + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + Deprecated *bool `protobuf:"varint,3,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // The parser stores options it doesn't recognize here. See above. + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EnumOptions) Reset() { *m = EnumOptions{} } +func (m *EnumOptions) String() string { return proto.CompactTextString(m) } +func (*EnumOptions) ProtoMessage() {} + +var extRange_EnumOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*EnumOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_EnumOptions +} +func (m *EnumOptions) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +const Default_EnumOptions_Deprecated bool = false + +func (m *EnumOptions) GetAllowAlias() bool { + if m != nil && m.AllowAlias != nil { + return *m.AllowAlias + } + return false +} + +func (m *EnumOptions) GetDeprecated() bool { + if m != nil && m.Deprecated != nil { + return *m.Deprecated + } + return Default_EnumOptions_Deprecated +} + +func (m *EnumOptions) GetUninterpretedOption() []*UninterpretedOption { + if m != nil { + return m.UninterpretedOption + } + return nil +} + +type EnumValueOptions struct { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + Deprecated *bool `protobuf:"varint,1,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // The parser stores options it doesn't recognize here. See above. + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EnumValueOptions) Reset() { *m = EnumValueOptions{} } +func (m *EnumValueOptions) String() string { return proto.CompactTextString(m) } +func (*EnumValueOptions) ProtoMessage() {} + +var extRange_EnumValueOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*EnumValueOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_EnumValueOptions +} +func (m *EnumValueOptions) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +const Default_EnumValueOptions_Deprecated bool = false + +func (m *EnumValueOptions) GetDeprecated() bool { + if m != nil && m.Deprecated != nil { + return *m.Deprecated + } + return Default_EnumValueOptions_Deprecated +} + +func (m *EnumValueOptions) GetUninterpretedOption() []*UninterpretedOption { + if m != nil { + return m.UninterpretedOption + } + return nil +} + +type ServiceOptions struct { + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + Deprecated *bool `protobuf:"varint,33,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // The parser stores options it doesn't recognize here. See above. + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ServiceOptions) Reset() { *m = ServiceOptions{} } +func (m *ServiceOptions) String() string { return proto.CompactTextString(m) } +func (*ServiceOptions) ProtoMessage() {} + +var extRange_ServiceOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*ServiceOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_ServiceOptions +} +func (m *ServiceOptions) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +const Default_ServiceOptions_Deprecated bool = false + +func (m *ServiceOptions) GetDeprecated() bool { + if m != nil && m.Deprecated != nil { + return *m.Deprecated + } + return Default_ServiceOptions_Deprecated +} + +func (m *ServiceOptions) GetUninterpretedOption() []*UninterpretedOption { + if m != nil { + return m.UninterpretedOption + } + return nil +} + +type MethodOptions struct { + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + Deprecated *bool `protobuf:"varint,33,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + // The parser stores options it doesn't recognize here. See above. + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MethodOptions) Reset() { *m = MethodOptions{} } +func (m *MethodOptions) String() string { return proto.CompactTextString(m) } +func (*MethodOptions) ProtoMessage() {} + +var extRange_MethodOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*MethodOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MethodOptions +} +func (m *MethodOptions) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +const Default_MethodOptions_Deprecated bool = false + +func (m *MethodOptions) GetDeprecated() bool { + if m != nil && m.Deprecated != nil { + return *m.Deprecated + } + return Default_MethodOptions_Deprecated +} + +func (m *MethodOptions) GetUninterpretedOption() []*UninterpretedOption { + if m != nil { + return m.UninterpretedOption + } + return nil +} + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +type UninterpretedOption struct { + Name []*UninterpretedOption_NamePart `protobuf:"bytes,2,rep,name=name" json:"name,omitempty"` + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + IdentifierValue *string `protobuf:"bytes,3,opt,name=identifier_value" json:"identifier_value,omitempty"` + PositiveIntValue *uint64 `protobuf:"varint,4,opt,name=positive_int_value" json:"positive_int_value,omitempty"` + NegativeIntValue *int64 `protobuf:"varint,5,opt,name=negative_int_value" json:"negative_int_value,omitempty"` + DoubleValue *float64 `protobuf:"fixed64,6,opt,name=double_value" json:"double_value,omitempty"` + StringValue []byte `protobuf:"bytes,7,opt,name=string_value" json:"string_value,omitempty"` + AggregateValue *string `protobuf:"bytes,8,opt,name=aggregate_value" json:"aggregate_value,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *UninterpretedOption) Reset() { *m = UninterpretedOption{} } +func (m *UninterpretedOption) String() string { return proto.CompactTextString(m) } +func (*UninterpretedOption) ProtoMessage() {} + +func (m *UninterpretedOption) GetName() []*UninterpretedOption_NamePart { + if m != nil { + return m.Name + } + return nil +} + +func (m *UninterpretedOption) GetIdentifierValue() string { + if m != nil && m.IdentifierValue != nil { + return *m.IdentifierValue + } + return "" +} + +func (m *UninterpretedOption) GetPositiveIntValue() uint64 { + if m != nil && m.PositiveIntValue != nil { + return *m.PositiveIntValue + } + return 0 +} + +func (m *UninterpretedOption) GetNegativeIntValue() int64 { + if m != nil && m.NegativeIntValue != nil { + return *m.NegativeIntValue + } + return 0 +} + +func (m *UninterpretedOption) GetDoubleValue() float64 { + if m != nil && m.DoubleValue != nil { + return *m.DoubleValue + } + return 0 +} + +func (m *UninterpretedOption) GetStringValue() []byte { + if m != nil { + return m.StringValue + } + return nil +} + +func (m *UninterpretedOption) GetAggregateValue() string { + if m != nil && m.AggregateValue != nil { + return *m.AggregateValue + } + return "" +} + +// The name of the uninterpreted option. Each string represents a segment in +// a dot-separated name. is_extension is true iff a segment represents an +// extension (denoted with parentheses in options specs in .proto files). +// E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents +// "foo.(bar.baz).qux". +type UninterpretedOption_NamePart struct { + NamePart *string `protobuf:"bytes,1,req,name=name_part" json:"name_part,omitempty"` + IsExtension *bool `protobuf:"varint,2,req,name=is_extension" json:"is_extension,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *UninterpretedOption_NamePart) Reset() { *m = UninterpretedOption_NamePart{} } +func (m *UninterpretedOption_NamePart) String() string { return proto.CompactTextString(m) } +func (*UninterpretedOption_NamePart) ProtoMessage() {} + +func (m *UninterpretedOption_NamePart) GetNamePart() string { + if m != nil && m.NamePart != nil { + return *m.NamePart + } + return "" +} + +func (m *UninterpretedOption_NamePart) GetIsExtension() bool { + if m != nil && m.IsExtension != nil { + return *m.IsExtension + } + return false +} + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +type SourceCodeInfo struct { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendent. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + Location []*SourceCodeInfo_Location `protobuf:"bytes,1,rep,name=location" json:"location,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SourceCodeInfo) Reset() { *m = SourceCodeInfo{} } +func (m *SourceCodeInfo) String() string { return proto.CompactTextString(m) } +func (*SourceCodeInfo) ProtoMessage() {} + +func (m *SourceCodeInfo) GetLocation() []*SourceCodeInfo_Location { + if m != nil { + return m.Location + } + return nil +} + +type SourceCodeInfo_Location struct { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition. For + // example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + Path []int32 `protobuf:"varint,1,rep,packed,name=path" json:"path,omitempty"` + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + Span []int32 `protobuf:"varint,2,rep,packed,name=span" json:"span,omitempty"` + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to qux. + // // + // // Another line attached to qux. + // optional double qux = 4; + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + LeadingComments *string `protobuf:"bytes,3,opt,name=leading_comments" json:"leading_comments,omitempty"` + TrailingComments *string `protobuf:"bytes,4,opt,name=trailing_comments" json:"trailing_comments,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SourceCodeInfo_Location) Reset() { *m = SourceCodeInfo_Location{} } +func (m *SourceCodeInfo_Location) String() string { return proto.CompactTextString(m) } +func (*SourceCodeInfo_Location) ProtoMessage() {} + +func (m *SourceCodeInfo_Location) GetPath() []int32 { + if m != nil { + return m.Path + } + return nil +} + +func (m *SourceCodeInfo_Location) GetSpan() []int32 { + if m != nil { + return m.Span + } + return nil +} + +func (m *SourceCodeInfo_Location) GetLeadingComments() string { + if m != nil && m.LeadingComments != nil { + return *m.LeadingComments + } + return "" +} + +func (m *SourceCodeInfo_Location) GetTrailingComments() string { + if m != nil && m.TrailingComments != nil { + return *m.TrailingComments + } + return "" +} + +func init() { + proto.RegisterEnum("google.protobuf.FieldDescriptorProto_Type", FieldDescriptorProto_Type_name, FieldDescriptorProto_Type_value) + proto.RegisterEnum("google.protobuf.FieldDescriptorProto_Label", FieldDescriptorProto_Label_name, FieldDescriptorProto_Label_value) + proto.RegisterEnum("google.protobuf.FileOptions_OptimizeMode", FileOptions_OptimizeMode_name, FileOptions_OptimizeMode_value) + proto.RegisterEnum("google.protobuf.FieldOptions_CType", FieldOptions_CType_name, FieldOptions_CType_value) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.golden charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.golden --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.golden 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.golden 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,1024 @@ +// Code generated by protoc-gen-go. +// source: google/protobuf/descriptor.proto +// DO NOT EDIT! + +package google_protobuf + +import proto "github.com/golang/protobuf/proto" +import "math" + +// Reference proto and math imports to suppress error if they are not otherwise used. +var _ = proto.GetString +var _ = math.Inf + +type FieldDescriptorProto_Type int32 + +const ( + FieldDescriptorProto_TYPE_DOUBLE FieldDescriptorProto_Type = 1 + FieldDescriptorProto_TYPE_FLOAT FieldDescriptorProto_Type = 2 + FieldDescriptorProto_TYPE_INT64 FieldDescriptorProto_Type = 3 + FieldDescriptorProto_TYPE_UINT64 FieldDescriptorProto_Type = 4 + FieldDescriptorProto_TYPE_INT32 FieldDescriptorProto_Type = 5 + FieldDescriptorProto_TYPE_FIXED64 FieldDescriptorProto_Type = 6 + FieldDescriptorProto_TYPE_FIXED32 FieldDescriptorProto_Type = 7 + FieldDescriptorProto_TYPE_BOOL FieldDescriptorProto_Type = 8 + FieldDescriptorProto_TYPE_STRING FieldDescriptorProto_Type = 9 + FieldDescriptorProto_TYPE_GROUP FieldDescriptorProto_Type = 10 + FieldDescriptorProto_TYPE_MESSAGE FieldDescriptorProto_Type = 11 + FieldDescriptorProto_TYPE_BYTES FieldDescriptorProto_Type = 12 + FieldDescriptorProto_TYPE_UINT32 FieldDescriptorProto_Type = 13 + FieldDescriptorProto_TYPE_ENUM FieldDescriptorProto_Type = 14 + FieldDescriptorProto_TYPE_SFIXED32 FieldDescriptorProto_Type = 15 + FieldDescriptorProto_TYPE_SFIXED64 FieldDescriptorProto_Type = 16 + FieldDescriptorProto_TYPE_SINT32 FieldDescriptorProto_Type = 17 + FieldDescriptorProto_TYPE_SINT64 FieldDescriptorProto_Type = 18 +) + +var FieldDescriptorProto_Type_name = map[int32]string{ + 1: "TYPE_DOUBLE", + 2: "TYPE_FLOAT", + 3: "TYPE_INT64", + 4: "TYPE_UINT64", + 5: "TYPE_INT32", + 6: "TYPE_FIXED64", + 7: "TYPE_FIXED32", + 8: "TYPE_BOOL", + 9: "TYPE_STRING", + 10: "TYPE_GROUP", + 11: "TYPE_MESSAGE", + 12: "TYPE_BYTES", + 13: "TYPE_UINT32", + 14: "TYPE_ENUM", + 15: "TYPE_SFIXED32", + 16: "TYPE_SFIXED64", + 17: "TYPE_SINT32", + 18: "TYPE_SINT64", +} +var FieldDescriptorProto_Type_value = map[string]int32{ + "TYPE_DOUBLE": 1, + "TYPE_FLOAT": 2, + "TYPE_INT64": 3, + "TYPE_UINT64": 4, + "TYPE_INT32": 5, + "TYPE_FIXED64": 6, + "TYPE_FIXED32": 7, + "TYPE_BOOL": 8, + "TYPE_STRING": 9, + "TYPE_GROUP": 10, + "TYPE_MESSAGE": 11, + "TYPE_BYTES": 12, + "TYPE_UINT32": 13, + "TYPE_ENUM": 14, + "TYPE_SFIXED32": 15, + "TYPE_SFIXED64": 16, + "TYPE_SINT32": 17, + "TYPE_SINT64": 18, +} + +// NewFieldDescriptorProto_Type is deprecated. Use x.Enum() instead. +func NewFieldDescriptorProto_Type(x FieldDescriptorProto_Type) *FieldDescriptorProto_Type { + e := FieldDescriptorProto_Type(x) + return &e +} +func (x FieldDescriptorProto_Type) Enum() *FieldDescriptorProto_Type { + p := new(FieldDescriptorProto_Type) + *p = x + return p +} +func (x FieldDescriptorProto_Type) String() string { + return proto.EnumName(FieldDescriptorProto_Type_name, int32(x)) +} + +type FieldDescriptorProto_Label int32 + +const ( + FieldDescriptorProto_LABEL_OPTIONAL FieldDescriptorProto_Label = 1 + FieldDescriptorProto_LABEL_REQUIRED FieldDescriptorProto_Label = 2 + FieldDescriptorProto_LABEL_REPEATED FieldDescriptorProto_Label = 3 +) + +var FieldDescriptorProto_Label_name = map[int32]string{ + 1: "LABEL_OPTIONAL", + 2: "LABEL_REQUIRED", + 3: "LABEL_REPEATED", +} +var FieldDescriptorProto_Label_value = map[string]int32{ + "LABEL_OPTIONAL": 1, + "LABEL_REQUIRED": 2, + "LABEL_REPEATED": 3, +} + +// NewFieldDescriptorProto_Label is deprecated. Use x.Enum() instead. +func NewFieldDescriptorProto_Label(x FieldDescriptorProto_Label) *FieldDescriptorProto_Label { + e := FieldDescriptorProto_Label(x) + return &e +} +func (x FieldDescriptorProto_Label) Enum() *FieldDescriptorProto_Label { + p := new(FieldDescriptorProto_Label) + *p = x + return p +} +func (x FieldDescriptorProto_Label) String() string { + return proto.EnumName(FieldDescriptorProto_Label_name, int32(x)) +} + +type FileOptions_OptimizeMode int32 + +const ( + FileOptions_SPEED FileOptions_OptimizeMode = 1 + FileOptions_CODE_SIZE FileOptions_OptimizeMode = 2 + FileOptions_LITE_RUNTIME FileOptions_OptimizeMode = 3 +) + +var FileOptions_OptimizeMode_name = map[int32]string{ + 1: "SPEED", + 2: "CODE_SIZE", + 3: "LITE_RUNTIME", +} +var FileOptions_OptimizeMode_value = map[string]int32{ + "SPEED": 1, + "CODE_SIZE": 2, + "LITE_RUNTIME": 3, +} + +// NewFileOptions_OptimizeMode is deprecated. Use x.Enum() instead. +func NewFileOptions_OptimizeMode(x FileOptions_OptimizeMode) *FileOptions_OptimizeMode { + e := FileOptions_OptimizeMode(x) + return &e +} +func (x FileOptions_OptimizeMode) Enum() *FileOptions_OptimizeMode { + p := new(FileOptions_OptimizeMode) + *p = x + return p +} +func (x FileOptions_OptimizeMode) String() string { + return proto.EnumName(FileOptions_OptimizeMode_name, int32(x)) +} + +type FieldOptions_CType int32 + +const ( + FieldOptions_STRING FieldOptions_CType = 0 + FieldOptions_CORD FieldOptions_CType = 1 + FieldOptions_STRING_PIECE FieldOptions_CType = 2 +) + +var FieldOptions_CType_name = map[int32]string{ + 0: "STRING", + 1: "CORD", + 2: "STRING_PIECE", +} +var FieldOptions_CType_value = map[string]int32{ + "STRING": 0, + "CORD": 1, + "STRING_PIECE": 2, +} + +// NewFieldOptions_CType is deprecated. Use x.Enum() instead. +func NewFieldOptions_CType(x FieldOptions_CType) *FieldOptions_CType { + e := FieldOptions_CType(x) + return &e +} +func (x FieldOptions_CType) Enum() *FieldOptions_CType { + p := new(FieldOptions_CType) + *p = x + return p +} +func (x FieldOptions_CType) String() string { + return proto.EnumName(FieldOptions_CType_name, int32(x)) +} + +type StreamOptions_TokenUnit int32 + +const ( + StreamOptions_MESSAGE StreamOptions_TokenUnit = 0 + StreamOptions_BYTE StreamOptions_TokenUnit = 1 +) + +var StreamOptions_TokenUnit_name = map[int32]string{ + 0: "MESSAGE", + 1: "BYTE", +} +var StreamOptions_TokenUnit_value = map[string]int32{ + "MESSAGE": 0, + "BYTE": 1, +} + +// NewStreamOptions_TokenUnit is deprecated. Use x.Enum() instead. +func NewStreamOptions_TokenUnit(x StreamOptions_TokenUnit) *StreamOptions_TokenUnit { + e := StreamOptions_TokenUnit(x) + return &e +} +func (x StreamOptions_TokenUnit) Enum() *StreamOptions_TokenUnit { + p := new(StreamOptions_TokenUnit) + *p = x + return p +} +func (x StreamOptions_TokenUnit) String() string { + return proto.EnumName(StreamOptions_TokenUnit_name, int32(x)) +} + +type FileDescriptorSet struct { + File []*FileDescriptorProto `protobuf:"bytes,1,rep,name=file" json:"file,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *FileDescriptorSet) Reset() { *this = FileDescriptorSet{} } +func (this *FileDescriptorSet) String() string { return proto.CompactTextString(this) } +func (*FileDescriptorSet) ProtoMessage() {} + +type FileDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Package *string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"` + Dependency []string `protobuf:"bytes,3,rep,name=dependency" json:"dependency,omitempty"` + PublicDependency []int32 `protobuf:"varint,10,rep,name=public_dependency" json:"public_dependency,omitempty"` + WeakDependency []int32 `protobuf:"varint,11,rep,name=weak_dependency" json:"weak_dependency,omitempty"` + MessageType []*DescriptorProto `protobuf:"bytes,4,rep,name=message_type" json:"message_type,omitempty"` + EnumType []*EnumDescriptorProto `protobuf:"bytes,5,rep,name=enum_type" json:"enum_type,omitempty"` + Service []*ServiceDescriptorProto `protobuf:"bytes,6,rep,name=service" json:"service,omitempty"` + Extension []*FieldDescriptorProto `protobuf:"bytes,7,rep,name=extension" json:"extension,omitempty"` + Options *FileOptions `protobuf:"bytes,8,opt,name=options" json:"options,omitempty"` + SourceCodeInfo *SourceCodeInfo `protobuf:"bytes,9,opt,name=source_code_info" json:"source_code_info,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *FileDescriptorProto) Reset() { *this = FileDescriptorProto{} } +func (this *FileDescriptorProto) String() string { return proto.CompactTextString(this) } +func (*FileDescriptorProto) ProtoMessage() {} + +func (this *FileDescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *FileDescriptorProto) GetPackage() string { + if this != nil && this.Package != nil { + return *this.Package + } + return "" +} + +func (this *FileDescriptorProto) GetOptions() *FileOptions { + if this != nil { + return this.Options + } + return nil +} + +func (this *FileDescriptorProto) GetSourceCodeInfo() *SourceCodeInfo { + if this != nil { + return this.SourceCodeInfo + } + return nil +} + +type DescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Field []*FieldDescriptorProto `protobuf:"bytes,2,rep,name=field" json:"field,omitempty"` + Extension []*FieldDescriptorProto `protobuf:"bytes,6,rep,name=extension" json:"extension,omitempty"` + NestedType []*DescriptorProto `protobuf:"bytes,3,rep,name=nested_type" json:"nested_type,omitempty"` + EnumType []*EnumDescriptorProto `protobuf:"bytes,4,rep,name=enum_type" json:"enum_type,omitempty"` + ExtensionRange []*DescriptorProto_ExtensionRange `protobuf:"bytes,5,rep,name=extension_range" json:"extension_range,omitempty"` + Options *MessageOptions `protobuf:"bytes,7,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *DescriptorProto) Reset() { *this = DescriptorProto{} } +func (this *DescriptorProto) String() string { return proto.CompactTextString(this) } +func (*DescriptorProto) ProtoMessage() {} + +func (this *DescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *DescriptorProto) GetOptions() *MessageOptions { + if this != nil { + return this.Options + } + return nil +} + +type DescriptorProto_ExtensionRange struct { + Start *int32 `protobuf:"varint,1,opt,name=start" json:"start,omitempty"` + End *int32 `protobuf:"varint,2,opt,name=end" json:"end,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *DescriptorProto_ExtensionRange) Reset() { *this = DescriptorProto_ExtensionRange{} } +func (this *DescriptorProto_ExtensionRange) String() string { return proto.CompactTextString(this) } +func (*DescriptorProto_ExtensionRange) ProtoMessage() {} + +func (this *DescriptorProto_ExtensionRange) GetStart() int32 { + if this != nil && this.Start != nil { + return *this.Start + } + return 0 +} + +func (this *DescriptorProto_ExtensionRange) GetEnd() int32 { + if this != nil && this.End != nil { + return *this.End + } + return 0 +} + +type FieldDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Number *int32 `protobuf:"varint,3,opt,name=number" json:"number,omitempty"` + Label *FieldDescriptorProto_Label `protobuf:"varint,4,opt,name=label,enum=proto2.FieldDescriptorProto_Label" json:"label,omitempty"` + Type *FieldDescriptorProto_Type `protobuf:"varint,5,opt,name=type,enum=proto2.FieldDescriptorProto_Type" json:"type,omitempty"` + TypeName *string `protobuf:"bytes,6,opt,name=type_name" json:"type_name,omitempty"` + Extendee *string `protobuf:"bytes,2,opt,name=extendee" json:"extendee,omitempty"` + DefaultValue *string `protobuf:"bytes,7,opt,name=default_value" json:"default_value,omitempty"` + Options *FieldOptions `protobuf:"bytes,8,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *FieldDescriptorProto) Reset() { *this = FieldDescriptorProto{} } +func (this *FieldDescriptorProto) String() string { return proto.CompactTextString(this) } +func (*FieldDescriptorProto) ProtoMessage() {} + +func (this *FieldDescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *FieldDescriptorProto) GetNumber() int32 { + if this != nil && this.Number != nil { + return *this.Number + } + return 0 +} + +func (this *FieldDescriptorProto) GetLabel() FieldDescriptorProto_Label { + if this != nil && this.Label != nil { + return *this.Label + } + return 0 +} + +func (this *FieldDescriptorProto) GetType() FieldDescriptorProto_Type { + if this != nil && this.Type != nil { + return *this.Type + } + return 0 +} + +func (this *FieldDescriptorProto) GetTypeName() string { + if this != nil && this.TypeName != nil { + return *this.TypeName + } + return "" +} + +func (this *FieldDescriptorProto) GetExtendee() string { + if this != nil && this.Extendee != nil { + return *this.Extendee + } + return "" +} + +func (this *FieldDescriptorProto) GetDefaultValue() string { + if this != nil && this.DefaultValue != nil { + return *this.DefaultValue + } + return "" +} + +func (this *FieldDescriptorProto) GetOptions() *FieldOptions { + if this != nil { + return this.Options + } + return nil +} + +type EnumDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Value []*EnumValueDescriptorProto `protobuf:"bytes,2,rep,name=value" json:"value,omitempty"` + Options *EnumOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *EnumDescriptorProto) Reset() { *this = EnumDescriptorProto{} } +func (this *EnumDescriptorProto) String() string { return proto.CompactTextString(this) } +func (*EnumDescriptorProto) ProtoMessage() {} + +func (this *EnumDescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *EnumDescriptorProto) GetOptions() *EnumOptions { + if this != nil { + return this.Options + } + return nil +} + +type EnumValueDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Number *int32 `protobuf:"varint,2,opt,name=number" json:"number,omitempty"` + Options *EnumValueOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *EnumValueDescriptorProto) Reset() { *this = EnumValueDescriptorProto{} } +func (this *EnumValueDescriptorProto) String() string { return proto.CompactTextString(this) } +func (*EnumValueDescriptorProto) ProtoMessage() {} + +func (this *EnumValueDescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *EnumValueDescriptorProto) GetNumber() int32 { + if this != nil && this.Number != nil { + return *this.Number + } + return 0 +} + +func (this *EnumValueDescriptorProto) GetOptions() *EnumValueOptions { + if this != nil { + return this.Options + } + return nil +} + +type ServiceDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Method []*MethodDescriptorProto `protobuf:"bytes,2,rep,name=method" json:"method,omitempty"` + Stream []*StreamDescriptorProto `protobuf:"bytes,4,rep,name=stream" json:"stream,omitempty"` + Options *ServiceOptions `protobuf:"bytes,3,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *ServiceDescriptorProto) Reset() { *this = ServiceDescriptorProto{} } +func (this *ServiceDescriptorProto) String() string { return proto.CompactTextString(this) } +func (*ServiceDescriptorProto) ProtoMessage() {} + +func (this *ServiceDescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *ServiceDescriptorProto) GetOptions() *ServiceOptions { + if this != nil { + return this.Options + } + return nil +} + +type MethodDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + InputType *string `protobuf:"bytes,2,opt,name=input_type" json:"input_type,omitempty"` + OutputType *string `protobuf:"bytes,3,opt,name=output_type" json:"output_type,omitempty"` + Options *MethodOptions `protobuf:"bytes,4,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *MethodDescriptorProto) Reset() { *this = MethodDescriptorProto{} } +func (this *MethodDescriptorProto) String() string { return proto.CompactTextString(this) } +func (*MethodDescriptorProto) ProtoMessage() {} + +func (this *MethodDescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *MethodDescriptorProto) GetInputType() string { + if this != nil && this.InputType != nil { + return *this.InputType + } + return "" +} + +func (this *MethodDescriptorProto) GetOutputType() string { + if this != nil && this.OutputType != nil { + return *this.OutputType + } + return "" +} + +func (this *MethodDescriptorProto) GetOptions() *MethodOptions { + if this != nil { + return this.Options + } + return nil +} + +type StreamDescriptorProto struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + ClientMessageType *string `protobuf:"bytes,2,opt,name=client_message_type" json:"client_message_type,omitempty"` + ServerMessageType *string `protobuf:"bytes,3,opt,name=server_message_type" json:"server_message_type,omitempty"` + Options *StreamOptions `protobuf:"bytes,4,opt,name=options" json:"options,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *StreamDescriptorProto) Reset() { *this = StreamDescriptorProto{} } +func (this *StreamDescriptorProto) String() string { return proto.CompactTextString(this) } +func (*StreamDescriptorProto) ProtoMessage() {} + +func (this *StreamDescriptorProto) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *StreamDescriptorProto) GetClientMessageType() string { + if this != nil && this.ClientMessageType != nil { + return *this.ClientMessageType + } + return "" +} + +func (this *StreamDescriptorProto) GetServerMessageType() string { + if this != nil && this.ServerMessageType != nil { + return *this.ServerMessageType + } + return "" +} + +func (this *StreamDescriptorProto) GetOptions() *StreamOptions { + if this != nil { + return this.Options + } + return nil +} + +type FileOptions struct { + JavaPackage *string `protobuf:"bytes,1,opt,name=java_package" json:"java_package,omitempty"` + JavaOuterClassname *string `protobuf:"bytes,8,opt,name=java_outer_classname" json:"java_outer_classname,omitempty"` + JavaMultipleFiles *bool `protobuf:"varint,10,opt,name=java_multiple_files,def=0" json:"java_multiple_files,omitempty"` + JavaGenerateEqualsAndHash *bool `protobuf:"varint,20,opt,name=java_generate_equals_and_hash,def=0" json:"java_generate_equals_and_hash,omitempty"` + OptimizeFor *FileOptions_OptimizeMode `protobuf:"varint,9,opt,name=optimize_for,enum=proto2.FileOptions_OptimizeMode,def=1" json:"optimize_for,omitempty"` + GoPackage *string `protobuf:"bytes,11,opt,name=go_package" json:"go_package,omitempty"` + CcGenericServices *bool `protobuf:"varint,16,opt,name=cc_generic_services,def=0" json:"cc_generic_services,omitempty"` + JavaGenericServices *bool `protobuf:"varint,17,opt,name=java_generic_services,def=0" json:"java_generic_services,omitempty"` + PyGenericServices *bool `protobuf:"varint,18,opt,name=py_generic_services,def=0" json:"py_generic_services,omitempty"` + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *FileOptions) Reset() { *this = FileOptions{} } +func (this *FileOptions) String() string { return proto.CompactTextString(this) } +func (*FileOptions) ProtoMessage() {} + +var extRange_FileOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*FileOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_FileOptions +} +func (this *FileOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +const Default_FileOptions_JavaMultipleFiles bool = false +const Default_FileOptions_JavaGenerateEqualsAndHash bool = false +const Default_FileOptions_OptimizeFor FileOptions_OptimizeMode = FileOptions_SPEED +const Default_FileOptions_CcGenericServices bool = false +const Default_FileOptions_JavaGenericServices bool = false +const Default_FileOptions_PyGenericServices bool = false + +func (this *FileOptions) GetJavaPackage() string { + if this != nil && this.JavaPackage != nil { + return *this.JavaPackage + } + return "" +} + +func (this *FileOptions) GetJavaOuterClassname() string { + if this != nil && this.JavaOuterClassname != nil { + return *this.JavaOuterClassname + } + return "" +} + +func (this *FileOptions) GetJavaMultipleFiles() bool { + if this != nil && this.JavaMultipleFiles != nil { + return *this.JavaMultipleFiles + } + return Default_FileOptions_JavaMultipleFiles +} + +func (this *FileOptions) GetJavaGenerateEqualsAndHash() bool { + if this != nil && this.JavaGenerateEqualsAndHash != nil { + return *this.JavaGenerateEqualsAndHash + } + return Default_FileOptions_JavaGenerateEqualsAndHash +} + +func (this *FileOptions) GetOptimizeFor() FileOptions_OptimizeMode { + if this != nil && this.OptimizeFor != nil { + return *this.OptimizeFor + } + return Default_FileOptions_OptimizeFor +} + +func (this *FileOptions) GetGoPackage() string { + if this != nil && this.GoPackage != nil { + return *this.GoPackage + } + return "" +} + +func (this *FileOptions) GetCcGenericServices() bool { + if this != nil && this.CcGenericServices != nil { + return *this.CcGenericServices + } + return Default_FileOptions_CcGenericServices +} + +func (this *FileOptions) GetJavaGenericServices() bool { + if this != nil && this.JavaGenericServices != nil { + return *this.JavaGenericServices + } + return Default_FileOptions_JavaGenericServices +} + +func (this *FileOptions) GetPyGenericServices() bool { + if this != nil && this.PyGenericServices != nil { + return *this.PyGenericServices + } + return Default_FileOptions_PyGenericServices +} + +type MessageOptions struct { + MessageSetWireFormat *bool `protobuf:"varint,1,opt,name=message_set_wire_format,def=0" json:"message_set_wire_format,omitempty"` + NoStandardDescriptorAccessor *bool `protobuf:"varint,2,opt,name=no_standard_descriptor_accessor,def=0" json:"no_standard_descriptor_accessor,omitempty"` + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *MessageOptions) Reset() { *this = MessageOptions{} } +func (this *MessageOptions) String() string { return proto.CompactTextString(this) } +func (*MessageOptions) ProtoMessage() {} + +var extRange_MessageOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*MessageOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MessageOptions +} +func (this *MessageOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +const Default_MessageOptions_MessageSetWireFormat bool = false +const Default_MessageOptions_NoStandardDescriptorAccessor bool = false + +func (this *MessageOptions) GetMessageSetWireFormat() bool { + if this != nil && this.MessageSetWireFormat != nil { + return *this.MessageSetWireFormat + } + return Default_MessageOptions_MessageSetWireFormat +} + +func (this *MessageOptions) GetNoStandardDescriptorAccessor() bool { + if this != nil && this.NoStandardDescriptorAccessor != nil { + return *this.NoStandardDescriptorAccessor + } + return Default_MessageOptions_NoStandardDescriptorAccessor +} + +type FieldOptions struct { + Ctype *FieldOptions_CType `protobuf:"varint,1,opt,name=ctype,enum=proto2.FieldOptions_CType,def=0" json:"ctype,omitempty"` + Packed *bool `protobuf:"varint,2,opt,name=packed" json:"packed,omitempty"` + Lazy *bool `protobuf:"varint,5,opt,name=lazy,def=0" json:"lazy,omitempty"` + Deprecated *bool `protobuf:"varint,3,opt,name=deprecated,def=0" json:"deprecated,omitempty"` + ExperimentalMapKey *string `protobuf:"bytes,9,opt,name=experimental_map_key" json:"experimental_map_key,omitempty"` + Weak *bool `protobuf:"varint,10,opt,name=weak,def=0" json:"weak,omitempty"` + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *FieldOptions) Reset() { *this = FieldOptions{} } +func (this *FieldOptions) String() string { return proto.CompactTextString(this) } +func (*FieldOptions) ProtoMessage() {} + +var extRange_FieldOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*FieldOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_FieldOptions +} +func (this *FieldOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +const Default_FieldOptions_Ctype FieldOptions_CType = FieldOptions_STRING +const Default_FieldOptions_Lazy bool = false +const Default_FieldOptions_Deprecated bool = false +const Default_FieldOptions_Weak bool = false + +func (this *FieldOptions) GetCtype() FieldOptions_CType { + if this != nil && this.Ctype != nil { + return *this.Ctype + } + return Default_FieldOptions_Ctype +} + +func (this *FieldOptions) GetPacked() bool { + if this != nil && this.Packed != nil { + return *this.Packed + } + return false +} + +func (this *FieldOptions) GetLazy() bool { + if this != nil && this.Lazy != nil { + return *this.Lazy + } + return Default_FieldOptions_Lazy +} + +func (this *FieldOptions) GetDeprecated() bool { + if this != nil && this.Deprecated != nil { + return *this.Deprecated + } + return Default_FieldOptions_Deprecated +} + +func (this *FieldOptions) GetExperimentalMapKey() string { + if this != nil && this.ExperimentalMapKey != nil { + return *this.ExperimentalMapKey + } + return "" +} + +func (this *FieldOptions) GetWeak() bool { + if this != nil && this.Weak != nil { + return *this.Weak + } + return Default_FieldOptions_Weak +} + +type EnumOptions struct { + AllowAlias *bool `protobuf:"varint,2,opt,name=allow_alias,def=1" json:"allow_alias,omitempty"` + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *EnumOptions) Reset() { *this = EnumOptions{} } +func (this *EnumOptions) String() string { return proto.CompactTextString(this) } +func (*EnumOptions) ProtoMessage() {} + +var extRange_EnumOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*EnumOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_EnumOptions +} +func (this *EnumOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +const Default_EnumOptions_AllowAlias bool = true + +func (this *EnumOptions) GetAllowAlias() bool { + if this != nil && this.AllowAlias != nil { + return *this.AllowAlias + } + return Default_EnumOptions_AllowAlias +} + +type EnumValueOptions struct { + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *EnumValueOptions) Reset() { *this = EnumValueOptions{} } +func (this *EnumValueOptions) String() string { return proto.CompactTextString(this) } +func (*EnumValueOptions) ProtoMessage() {} + +var extRange_EnumValueOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*EnumValueOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_EnumValueOptions +} +func (this *EnumValueOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +type ServiceOptions struct { + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *ServiceOptions) Reset() { *this = ServiceOptions{} } +func (this *ServiceOptions) String() string { return proto.CompactTextString(this) } +func (*ServiceOptions) ProtoMessage() {} + +var extRange_ServiceOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*ServiceOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_ServiceOptions +} +func (this *ServiceOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +type MethodOptions struct { + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *MethodOptions) Reset() { *this = MethodOptions{} } +func (this *MethodOptions) String() string { return proto.CompactTextString(this) } +func (*MethodOptions) ProtoMessage() {} + +var extRange_MethodOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*MethodOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MethodOptions +} +func (this *MethodOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +type StreamOptions struct { + UninterpretedOption []*UninterpretedOption `protobuf:"bytes,999,rep,name=uninterpreted_option" json:"uninterpreted_option,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *StreamOptions) Reset() { *this = StreamOptions{} } +func (this *StreamOptions) String() string { return proto.CompactTextString(this) } +func (*StreamOptions) ProtoMessage() {} + +var extRange_StreamOptions = []proto.ExtensionRange{ + {1000, 536870911}, +} + +func (*StreamOptions) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_StreamOptions +} +func (this *StreamOptions) ExtensionMap() map[int32]proto.Extension { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32]proto.Extension) + } + return this.XXX_extensions +} + +type UninterpretedOption struct { + Name []*UninterpretedOption_NamePart `protobuf:"bytes,2,rep,name=name" json:"name,omitempty"` + IdentifierValue *string `protobuf:"bytes,3,opt,name=identifier_value" json:"identifier_value,omitempty"` + PositiveIntValue *uint64 `protobuf:"varint,4,opt,name=positive_int_value" json:"positive_int_value,omitempty"` + NegativeIntValue *int64 `protobuf:"varint,5,opt,name=negative_int_value" json:"negative_int_value,omitempty"` + DoubleValue *float64 `protobuf:"fixed64,6,opt,name=double_value" json:"double_value,omitempty"` + StringValue []byte `protobuf:"bytes,7,opt,name=string_value" json:"string_value,omitempty"` + AggregateValue *string `protobuf:"bytes,8,opt,name=aggregate_value" json:"aggregate_value,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *UninterpretedOption) Reset() { *this = UninterpretedOption{} } +func (this *UninterpretedOption) String() string { return proto.CompactTextString(this) } +func (*UninterpretedOption) ProtoMessage() {} + +func (this *UninterpretedOption) GetIdentifierValue() string { + if this != nil && this.IdentifierValue != nil { + return *this.IdentifierValue + } + return "" +} + +func (this *UninterpretedOption) GetPositiveIntValue() uint64 { + if this != nil && this.PositiveIntValue != nil { + return *this.PositiveIntValue + } + return 0 +} + +func (this *UninterpretedOption) GetNegativeIntValue() int64 { + if this != nil && this.NegativeIntValue != nil { + return *this.NegativeIntValue + } + return 0 +} + +func (this *UninterpretedOption) GetDoubleValue() float64 { + if this != nil && this.DoubleValue != nil { + return *this.DoubleValue + } + return 0 +} + +func (this *UninterpretedOption) GetStringValue() []byte { + if this != nil { + return this.StringValue + } + return nil +} + +func (this *UninterpretedOption) GetAggregateValue() string { + if this != nil && this.AggregateValue != nil { + return *this.AggregateValue + } + return "" +} + +type UninterpretedOption_NamePart struct { + NamePart *string `protobuf:"bytes,1,req,name=name_part" json:"name_part,omitempty"` + IsExtension *bool `protobuf:"varint,2,req,name=is_extension" json:"is_extension,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *UninterpretedOption_NamePart) Reset() { *this = UninterpretedOption_NamePart{} } +func (this *UninterpretedOption_NamePart) String() string { return proto.CompactTextString(this) } +func (*UninterpretedOption_NamePart) ProtoMessage() {} + +func (this *UninterpretedOption_NamePart) GetNamePart() string { + if this != nil && this.NamePart != nil { + return *this.NamePart + } + return "" +} + +func (this *UninterpretedOption_NamePart) GetIsExtension() bool { + if this != nil && this.IsExtension != nil { + return *this.IsExtension + } + return false +} + +type SourceCodeInfo struct { + Location []*SourceCodeInfo_Location `protobuf:"bytes,1,rep,name=location" json:"location,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *SourceCodeInfo) Reset() { *this = SourceCodeInfo{} } +func (this *SourceCodeInfo) String() string { return proto.CompactTextString(this) } +func (*SourceCodeInfo) ProtoMessage() {} + +type SourceCodeInfo_Location struct { + Path []int32 `protobuf:"varint,1,rep,packed,name=path" json:"path,omitempty"` + Span []int32 `protobuf:"varint,2,rep,packed,name=span" json:"span,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *SourceCodeInfo_Location) Reset() { *this = SourceCodeInfo_Location{} } +func (this *SourceCodeInfo_Location) String() string { return proto.CompactTextString(this) } +func (*SourceCodeInfo_Location) ProtoMessage() {} + +func init() { + proto.RegisterEnum("google_protobuf.FieldDescriptorProto_Type", FieldDescriptorProto_Type_name, FieldDescriptorProto_Type_value) + proto.RegisterEnum("google_protobuf.FieldDescriptorProto_Label", FieldDescriptorProto_Label_name, FieldDescriptorProto_Label_value) + proto.RegisterEnum("google_protobuf.FileOptions_OptimizeMode", FileOptions_OptimizeMode_name, FileOptions_OptimizeMode_value) + proto.RegisterEnum("google_protobuf.FieldOptions_CType", FieldOptions_CType_name, FieldOptions_CType_value) + proto.RegisterEnum("google_protobuf.StreamOptions_TokenUnit", StreamOptions_TokenUnit_name, StreamOptions_TokenUnit_value) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,45 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Not stored here, but descriptor.proto is in https://github.com/google/protobuf/ +# at src/google/protobuf/descriptor.proto +regenerate: + echo WARNING! THIS RULE IS PROBABLY NOT RIGHT FOR YOUR INSTALLATION + cd $(HOME)/src/protobuf/src && \ + protoc --go_out=. ./google/protobuf/descriptor.proto && \ + sed -i 's,^package google_protobuf,package descriptor,' google/protobuf/descriptor.pb.go && \ + cp ./google/protobuf/descriptor.pb.go $(GOPATH)/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go + +restore: + cp descriptor.pb.golden descriptor.pb.go + +preserve: + cp descriptor.pb.go descriptor.pb.golden diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/doc.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/doc.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/doc.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/doc.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,51 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* + A plugin for the Google protocol buffer compiler to generate Go code. + Run it by building this program and putting it in your path with the name + protoc-gen-go + That word 'go' at the end becomes part of the option string set for the + protocol compiler, so once the protocol compiler (protoc) is installed + you can run + protoc --go_out=output_directory input_directory/file.proto + to generate Go bindings for the protocol defined by file.proto. + With that input, the output will be written to + output_directory/file.pb.go + + The generated code is documented in the package comment for + the library. + + See the README and documentation for protocol buffers to learn more: + https://developers.google.com/protocol-buffers/ + +*/ +package documentation diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/generator/generator.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/generator/generator.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/generator/generator.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/generator/generator.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,2071 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* + The code generator for the plugin for the Google protocol buffer compiler. + It generates Go code from the protocol buffer description files read by the + main routine. +*/ +package generator + +import ( + "bytes" + "fmt" + "go/parser" + "go/printer" + "go/token" + "log" + "os" + "path" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "github.com/golang/protobuf/proto" + + "github.com/golang/protobuf/protoc-gen-go/descriptor" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" +) + +// A Plugin provides functionality to add to the output during Go code generation, +// such as to produce RPC stubs. +type Plugin interface { + // Name identifies the plugin. + Name() string + // Init is called once after data structures are built but before + // code generation begins. + Init(g *Generator) + // Generate produces the code generated by the plugin for this file, + // except for the imports, by calling the generator's methods P, In, and Out. + Generate(file *FileDescriptor) + // GenerateImports produces the import declarations for this file. + // It is called after Generate. + GenerateImports(file *FileDescriptor) +} + +var plugins []Plugin + +// RegisterPlugin installs a (second-order) plugin to be run when the Go output is generated. +// It is typically called during initialization. +func RegisterPlugin(p Plugin) { + plugins = append(plugins, p) +} + +// Each type we import as a protocol buffer (other than FileDescriptorProto) needs +// a pointer to the FileDescriptorProto that represents it. These types achieve that +// wrapping by placing each Proto inside a struct with the pointer to its File. The +// structs have the same names as their contents, with "Proto" removed. +// FileDescriptor is used to store the things that it points to. + +// The file and package name method are common to messages and enums. +type common struct { + file *descriptor.FileDescriptorProto // File this object comes from. +} + +// PackageName is name in the package clause in the generated file. +func (c *common) PackageName() string { return uniquePackageOf(c.file) } + +func (c *common) File() *descriptor.FileDescriptorProto { return c.file } + +func fileIsProto3(file *descriptor.FileDescriptorProto) bool { + return file.GetSyntax() == "proto3" +} + +func (c *common) proto3() bool { return fileIsProto3(c.file) } + +// Descriptor represents a protocol buffer message. +type Descriptor struct { + common + *descriptor.DescriptorProto + parent *Descriptor // The containing message, if any. + nested []*Descriptor // Inner messages, if any. + ext []*ExtensionDescriptor // Extensions, if any. + typename []string // Cached typename vector. + index int // The index into the container, whether the file or another message. + path string // The SourceCodeInfo path as comma-separated integers. + group bool +} + +// TypeName returns the elements of the dotted type name. +// The package name is not part of this name. +func (d *Descriptor) TypeName() []string { + if d.typename != nil { + return d.typename + } + n := 0 + for parent := d; parent != nil; parent = parent.parent { + n++ + } + s := make([]string, n, n) + for parent := d; parent != nil; parent = parent.parent { + n-- + s[n] = parent.GetName() + } + d.typename = s + return s +} + +// EnumDescriptor describes an enum. If it's at top level, its parent will be nil. +// Otherwise it will be the descriptor of the message in which it is defined. +type EnumDescriptor struct { + common + *descriptor.EnumDescriptorProto + parent *Descriptor // The containing message, if any. + typename []string // Cached typename vector. + index int // The index into the container, whether the file or a message. + path string // The SourceCodeInfo path as comma-separated integers. +} + +// TypeName returns the elements of the dotted type name. +// The package name is not part of this name. +func (e *EnumDescriptor) TypeName() (s []string) { + if e.typename != nil { + return e.typename + } + name := e.GetName() + if e.parent == nil { + s = make([]string, 1) + } else { + pname := e.parent.TypeName() + s = make([]string, len(pname)+1) + copy(s, pname) + } + s[len(s)-1] = name + e.typename = s + return s +} + +// Everything but the last element of the full type name, CamelCased. +// The values of type Foo.Bar are call Foo_value1... not Foo_Bar_value1... . +func (e *EnumDescriptor) prefix() string { + if e.parent == nil { + // If the enum is not part of a message, the prefix is just the type name. + return CamelCase(*e.Name) + "_" + } + typeName := e.TypeName() + return CamelCaseSlice(typeName[0:len(typeName)-1]) + "_" +} + +// The integer value of the named constant in this enumerated type. +func (e *EnumDescriptor) integerValueAsString(name string) string { + for _, c := range e.Value { + if c.GetName() == name { + return fmt.Sprint(c.GetNumber()) + } + } + log.Fatal("cannot find value for enum constant") + return "" +} + +// ExtensionDescriptor describes an extension. If it's at top level, its parent will be nil. +// Otherwise it will be the descriptor of the message in which it is defined. +type ExtensionDescriptor struct { + common + *descriptor.FieldDescriptorProto + parent *Descriptor // The containing message, if any. +} + +// TypeName returns the elements of the dotted type name. +// The package name is not part of this name. +func (e *ExtensionDescriptor) TypeName() (s []string) { + name := e.GetName() + if e.parent == nil { + // top-level extension + s = make([]string, 1) + } else { + pname := e.parent.TypeName() + s = make([]string, len(pname)+1) + copy(s, pname) + } + s[len(s)-1] = name + return s +} + +// DescName returns the variable name used for the generated descriptor. +func (e *ExtensionDescriptor) DescName() string { + // The full type name. + typeName := e.TypeName() + // Each scope of the extension is individually CamelCased, and all are joined with "_" with an "E_" prefix. + for i, s := range typeName { + typeName[i] = CamelCase(s) + } + return "E_" + strings.Join(typeName, "_") +} + +// ImportedDescriptor describes a type that has been publicly imported from another file. +type ImportedDescriptor struct { + common + o Object +} + +func (id *ImportedDescriptor) TypeName() []string { return id.o.TypeName() } + +// FileDescriptor describes an protocol buffer descriptor file (.proto). +// It includes slices of all the messages and enums defined within it. +// Those slices are constructed by WrapTypes. +type FileDescriptor struct { + *descriptor.FileDescriptorProto + desc []*Descriptor // All the messages defined in this file. + enum []*EnumDescriptor // All the enums defined in this file. + ext []*ExtensionDescriptor // All the top-level extensions defined in this file. + imp []*ImportedDescriptor // All types defined in files publicly imported by this file. + + // Comments, stored as a map of path (comma-separated integers) to the comment. + comments map[string]*descriptor.SourceCodeInfo_Location + + // The full list of symbols that are exported, + // as a map from the exported object to its symbols. + // This is used for supporting public imports. + exported map[Object][]symbol + + index int // The index of this file in the list of files to generate code for + + proto3 bool // whether to generate proto3 code for this file +} + +// PackageName is the package name we'll use in the generated code to refer to this file. +func (d *FileDescriptor) PackageName() string { return uniquePackageOf(d.FileDescriptorProto) } + +// goPackageName returns the Go package name to use in the +// generated Go file. The result explicit reports whether the name +// came from an option go_package statement. If explicit is false, +// the name was derived from the protocol buffer's package statement +// or the input file name. +func (d *FileDescriptor) goPackageName() (name string, explicit bool) { + // Does the file have a "go_package" option? + if opts := d.Options; opts != nil { + if pkg := opts.GetGoPackage(); pkg != "" { + return pkg, true + } + } + + // Does the file have a package clause? + if pkg := d.GetPackage(); pkg != "" { + return pkg, false + } + // Use the file base name. + return baseName(d.GetName()), false +} + +func (d *FileDescriptor) addExport(obj Object, sym symbol) { + d.exported[obj] = append(d.exported[obj], sym) +} + +// symbol is an interface representing an exported Go symbol. +type symbol interface { + // GenerateAlias should generate an appropriate alias + // for the symbol from the named package. + GenerateAlias(g *Generator, pkg string) +} + +type messageSymbol struct { + sym string + hasExtensions, isMessageSet bool + getters []getterSymbol +} + +type getterSymbol struct { + name string + typ string + typeName string // canonical name in proto world; empty for proto.Message and similar + genType bool // whether typ is a generated type (message/group/enum) +} + +func (ms *messageSymbol) GenerateAlias(g *Generator, pkg string) { + remoteSym := pkg + "." + ms.sym + + g.P("type ", ms.sym, " ", remoteSym) + g.P("func (m *", ms.sym, ") Reset() { (*", remoteSym, ")(m).Reset() }") + g.P("func (m *", ms.sym, ") String() string { return (*", remoteSym, ")(m).String() }") + g.P("func (*", ms.sym, ") ProtoMessage() {}") + if ms.hasExtensions { + g.P("func (*", ms.sym, ") ExtensionRangeArray() []", g.Pkg["proto"], ".ExtensionRange ", + "{ return (*", remoteSym, ")(nil).ExtensionRangeArray() }") + g.P("func (m *", ms.sym, ") ExtensionMap() map[int32]", g.Pkg["proto"], ".Extension ", + "{ return (*", remoteSym, ")(m).ExtensionMap() }") + if ms.isMessageSet { + g.P("func (m *", ms.sym, ") Marshal() ([]byte, error) ", + "{ return (*", remoteSym, ")(m).Marshal() }") + g.P("func (m *", ms.sym, ") Unmarshal(buf []byte) error ", + "{ return (*", remoteSym, ")(m).Unmarshal(buf) }") + } + } + for _, get := range ms.getters { + + if get.typeName != "" { + g.RecordTypeUse(get.typeName) + } + typ := get.typ + val := "(*" + remoteSym + ")(m)." + get.name + "()" + if get.genType { + // typ will be "*pkg.T" (message/group) or "pkg.T" (enum). + // Either of those might have a "[]" prefix if it is repeated. + // Drop the package qualifier since we have hoisted the type into this package. + rep := strings.HasPrefix(typ, "[]") + if rep { + typ = typ[2:] + } + star := typ[0] == '*' + typ = typ[strings.Index(typ, ".")+1:] + if star { + typ = "*" + typ + } + if rep { + // Go does not permit conversion between slice types where both + // element types are named. That means we need to generate a bit + // of code in this situation. + // typ is the element type. + // val is the expression to get the slice from the imported type. + + ctyp := typ // conversion type expression; "Foo" or "(*Foo)" + if star { + ctyp = "(" + typ + ")" + } + + g.P("func (m *", ms.sym, ") ", get.name, "() []", typ, " {") + g.In() + g.P("o := ", val) + g.P("if o == nil {") + g.In() + g.P("return nil") + g.Out() + g.P("}") + g.P("s := make([]", typ, ", len(o))") + g.P("for i, x := range o {") + g.In() + g.P("s[i] = ", ctyp, "(x)") + g.Out() + g.P("}") + g.P("return s") + g.Out() + g.P("}") + continue + } + // Convert imported type into the forwarding type. + val = "(" + typ + ")(" + val + ")" + } + + g.P("func (m *", ms.sym, ") ", get.name, "() ", typ, " { return ", val, " }") + } + +} + +type enumSymbol struct { + name string + proto3 bool // Whether this came from a proto3 file. +} + +func (es enumSymbol) GenerateAlias(g *Generator, pkg string) { + s := es.name + g.P("type ", s, " ", pkg, ".", s) + g.P("var ", s, "_name = ", pkg, ".", s, "_name") + g.P("var ", s, "_value = ", pkg, ".", s, "_value") + g.P("func (x ", s, ") String() string { return (", pkg, ".", s, ")(x).String() }") + if !es.proto3 { + g.P("func (x ", s, ") Enum() *", s, "{ return (*", s, ")((", pkg, ".", s, ")(x).Enum()) }") + g.P("func (x *", s, ") UnmarshalJSON(data []byte) error { return (*", pkg, ".", s, ")(x).UnmarshalJSON(data) }") + } +} + +type constOrVarSymbol struct { + sym string + typ string // either "const" or "var" + cast string // if non-empty, a type cast is required (used for enums) +} + +func (cs constOrVarSymbol) GenerateAlias(g *Generator, pkg string) { + v := pkg + "." + cs.sym + if cs.cast != "" { + v = cs.cast + "(" + v + ")" + } + g.P(cs.typ, " ", cs.sym, " = ", v) +} + +// Object is an interface abstracting the abilities shared by enums, messages, extensions and imported objects. +type Object interface { + PackageName() string // The name we use in our output (a_b_c), possibly renamed for uniqueness. + TypeName() []string + File() *descriptor.FileDescriptorProto +} + +// Each package name we generate must be unique. The package we're generating +// gets its own name but every other package must have a unique name that does +// not conflict in the code we generate. These names are chosen globally (although +// they don't have to be, it simplifies things to do them globally). +func uniquePackageOf(fd *descriptor.FileDescriptorProto) string { + s, ok := uniquePackageName[fd] + if !ok { + log.Fatal("internal error: no package name defined for " + fd.GetName()) + } + return s +} + +// Generator is the type whose methods generate the output, stored in the associated response structure. +type Generator struct { + *bytes.Buffer + + Request *plugin.CodeGeneratorRequest // The input. + Response *plugin.CodeGeneratorResponse // The output. + + Param map[string]string // Command-line parameters. + PackageImportPath string // Go import path of the package we're generating code for + ImportPrefix string // String to prefix to imported package file names. + ImportMap map[string]string // Mapping from import name to generated name + + Pkg map[string]string // The names under which we import support packages + + packageName string // What we're calling ourselves. + allFiles []*FileDescriptor // All files in the tree + genFiles []*FileDescriptor // Those files we will generate output for. + file *FileDescriptor // The file we are compiling now. + usedPackages map[string]bool // Names of packages used in current file. + typeNameToObject map[string]Object // Key is a fully-qualified name in input syntax. + indent string +} + +// New creates a new generator and allocates the request and response protobufs. +func New() *Generator { + g := new(Generator) + g.Buffer = new(bytes.Buffer) + g.Request = new(plugin.CodeGeneratorRequest) + g.Response = new(plugin.CodeGeneratorResponse) + return g +} + +// Error reports a problem, including an error, and exits the program. +func (g *Generator) Error(err error, msgs ...string) { + s := strings.Join(msgs, " ") + ":" + err.Error() + log.Print("protoc-gen-go: error:", s) + os.Exit(1) +} + +// Fail reports a problem and exits the program. +func (g *Generator) Fail(msgs ...string) { + s := strings.Join(msgs, " ") + log.Print("protoc-gen-go: error:", s) + os.Exit(1) +} + +// CommandLineParameters breaks the comma-separated list of key=value pairs +// in the parameter (a member of the request protobuf) into a key/value map. +// It then sets file name mappings defined by those entries. +func (g *Generator) CommandLineParameters(parameter string) { + g.Param = make(map[string]string) + for _, p := range strings.Split(parameter, ",") { + if i := strings.Index(p, "="); i < 0 { + g.Param[p] = "" + } else { + g.Param[p[0:i]] = p[i+1:] + } + } + + g.ImportMap = make(map[string]string) + pluginList := "none" // Default list of plugin names to enable (empty means all). + for k, v := range g.Param { + switch k { + case "import_prefix": + g.ImportPrefix = v + case "import_path": + g.PackageImportPath = v + case "plugins": + pluginList = v + default: + if len(k) > 0 && k[0] == 'M' { + g.ImportMap[k[1:]] = v + } + } + } + + if pluginList != "" { + // Amend the set of plugins. + enabled := make(map[string]bool) + for _, name := range strings.Split(pluginList, "+") { + enabled[name] = true + } + var nplugins []Plugin + for _, p := range plugins { + if enabled[p.Name()] { + nplugins = append(nplugins, p) + } + } + plugins = nplugins + } +} + +// DefaultPackageName returns the package name printed for the object. +// If its file is in a different package, it returns the package name we're using for this file, plus ".". +// Otherwise it returns the empty string. +func (g *Generator) DefaultPackageName(obj Object) string { + pkg := obj.PackageName() + if pkg == g.packageName { + return "" + } + return pkg + "." +} + +// For each input file, the unique package name to use, underscored. +var uniquePackageName = make(map[*descriptor.FileDescriptorProto]string) + +// Package names already registered. Key is the name from the .proto file; +// value is the name that appears in the generated code. +var pkgNamesInUse = make(map[string]bool) + +// Create and remember a guaranteed unique package name for this file descriptor. +// Pkg is the candidate name. If f is nil, it's a builtin package like "proto" and +// has no file descriptor. +func RegisterUniquePackageName(pkg string, f *FileDescriptor) string { + // Convert dots to underscores before finding a unique alias. + pkg = strings.Map(badToUnderscore, pkg) + + for i, orig := 1, pkg; pkgNamesInUse[pkg]; i++ { + // It's a duplicate; must rename. + pkg = orig + strconv.Itoa(i) + } + // Install it. + pkgNamesInUse[pkg] = true + if f != nil { + uniquePackageName[f.FileDescriptorProto] = pkg + } + return pkg +} + +var isGoKeyword = map[string]bool{ + "break": true, + "case": true, + "chan": true, + "const": true, + "continue": true, + "default": true, + "else": true, + "defer": true, + "fallthrough": true, + "for": true, + "func": true, + "go": true, + "goto": true, + "if": true, + "import": true, + "interface": true, + "map": true, + "package": true, + "range": true, + "return": true, + "select": true, + "struct": true, + "switch": true, + "type": true, + "var": true, +} + +// defaultGoPackage returns the package name to use, +// derived from the import path of the package we're building code for. +func (g *Generator) defaultGoPackage() string { + p := g.PackageImportPath + if i := strings.LastIndex(p, "/"); i >= 0 { + p = p[i+1:] + } + if p == "" { + return "" + } + + p = strings.Map(badToUnderscore, p) + // Identifier must not be keyword: insert _. + if isGoKeyword[p] { + p = "_" + p + } + // Identifier must not begin with digit: insert _. + if r, _ := utf8.DecodeRuneInString(p); unicode.IsDigit(r) { + p = "_" + p + } + return p +} + +// SetPackageNames sets the package name for this run. +// The package name must agree across all files being generated. +// It also defines unique package names for all imported files. +func (g *Generator) SetPackageNames() { + // Register the name for this package. It will be the first name + // registered so is guaranteed to be unmodified. + pkg, explicit := g.genFiles[0].goPackageName() + + // Check all files for an explicit go_package option. + for _, f := range g.genFiles { + thisPkg, thisExplicit := f.goPackageName() + if thisExplicit { + if !explicit { + // Let this file's go_package option serve for all input files. + pkg, explicit = thisPkg, true + } else if thisPkg != pkg { + g.Fail("inconsistent package names:", thisPkg, pkg) + } + } + } + + // If we don't have an explicit go_package option but we have an + // import path, use that. + if !explicit { + p := g.defaultGoPackage() + if p != "" { + pkg, explicit = p, true + } + } + + // If there was no go_package and no import path to use, + // double-check that all the inputs have the same implicit + // Go package name. + if !explicit { + for _, f := range g.genFiles { + thisPkg, _ := f.goPackageName() + if thisPkg != pkg { + g.Fail("inconsistent package names:", thisPkg, pkg) + } + } + } + + g.packageName = RegisterUniquePackageName(pkg, g.genFiles[0]) + + // Register the support package names. They might collide with the + // name of a package we import. + g.Pkg = map[string]string{ + "math": RegisterUniquePackageName("math", nil), + "proto": RegisterUniquePackageName("proto", nil), + } + +AllFiles: + for _, f := range g.allFiles { + for _, genf := range g.genFiles { + if f == genf { + // In this package already. + uniquePackageName[f.FileDescriptorProto] = g.packageName + continue AllFiles + } + } + // The file is a dependency, so we want to ignore its go_package option + // because that is only relevant for its specific generated output. + pkg := f.GetPackage() + if pkg == "" { + pkg = baseName(*f.Name) + } + RegisterUniquePackageName(pkg, f) + } +} + +// WrapTypes walks the incoming data, wrapping DescriptorProtos, EnumDescriptorProtos +// and FileDescriptorProtos into file-referenced objects within the Generator. +// It also creates the list of files to generate and so should be called before GenerateAllFiles. +func (g *Generator) WrapTypes() { + g.allFiles = make([]*FileDescriptor, len(g.Request.ProtoFile)) + for i, f := range g.Request.ProtoFile { + // We must wrap the descriptors before we wrap the enums + descs := wrapDescriptors(f) + g.buildNestedDescriptors(descs) + enums := wrapEnumDescriptors(f, descs) + exts := wrapExtensions(f) + imps := wrapImported(f, g) + fd := &FileDescriptor{ + FileDescriptorProto: f, + desc: descs, + enum: enums, + ext: exts, + imp: imps, + exported: make(map[Object][]symbol), + proto3: fileIsProto3(f), + } + extractComments(fd) + g.allFiles[i] = fd + } + + g.genFiles = make([]*FileDescriptor, len(g.Request.FileToGenerate)) +FindFiles: + for i, fileName := range g.Request.FileToGenerate { + // Search the list. This algorithm is n^2 but n is tiny. + for _, file := range g.allFiles { + if fileName == file.GetName() { + g.genFiles[i] = file + file.index = i + continue FindFiles + } + } + g.Fail("could not find file named", fileName) + } + g.Response.File = make([]*plugin.CodeGeneratorResponse_File, len(g.genFiles)) +} + +// Scan the descriptors in this file. For each one, build the slice of nested descriptors +func (g *Generator) buildNestedDescriptors(descs []*Descriptor) { + for _, desc := range descs { + if len(desc.NestedType) != 0 { + desc.nested = make([]*Descriptor, len(desc.NestedType)) + n := 0 + for _, nest := range descs { + if nest.parent == desc { + desc.nested[n] = nest + n++ + } + } + if n != len(desc.NestedType) { + g.Fail("internal error: nesting failure for", desc.GetName()) + } + } + } +} + +// Construct the Descriptor +func newDescriptor(desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) *Descriptor { + d := &Descriptor{ + common: common{file}, + DescriptorProto: desc, + parent: parent, + index: index, + } + if parent == nil { + d.path = fmt.Sprintf("%d,%d", messagePath, index) + } else { + d.path = fmt.Sprintf("%s,%d,%d", parent.path, messageMessagePath, index) + } + + // The only way to distinguish a group from a message is whether + // the containing message has a TYPE_GROUP field that matches. + if parent != nil { + parts := d.TypeName() + if file.Package != nil { + parts = append([]string{*file.Package}, parts...) + } + exp := "." + strings.Join(parts, ".") + for _, field := range parent.Field { + if field.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP && field.GetTypeName() == exp { + d.group = true + break + } + } + } + + d.ext = make([]*ExtensionDescriptor, len(desc.Extension)) + for i, field := range desc.Extension { + d.ext[i] = &ExtensionDescriptor{common{file}, field, d} + } + + return d +} + +// Return a slice of all the Descriptors defined within this file +func wrapDescriptors(file *descriptor.FileDescriptorProto) []*Descriptor { + sl := make([]*Descriptor, 0, len(file.MessageType)+10) + for i, desc := range file.MessageType { + sl = wrapThisDescriptor(sl, desc, nil, file, i) + } + return sl +} + +// Wrap this Descriptor, recursively +func wrapThisDescriptor(sl []*Descriptor, desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) []*Descriptor { + sl = append(sl, newDescriptor(desc, parent, file, index)) + me := sl[len(sl)-1] + for i, nested := range desc.NestedType { + sl = wrapThisDescriptor(sl, nested, me, file, i) + } + return sl +} + +// Construct the EnumDescriptor +func newEnumDescriptor(desc *descriptor.EnumDescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) *EnumDescriptor { + ed := &EnumDescriptor{ + common: common{file}, + EnumDescriptorProto: desc, + parent: parent, + index: index, + } + if parent == nil { + ed.path = fmt.Sprintf("%d,%d", enumPath, index) + } else { + ed.path = fmt.Sprintf("%s,%d,%d", parent.path, messageEnumPath, index) + } + return ed +} + +// Return a slice of all the EnumDescriptors defined within this file +func wrapEnumDescriptors(file *descriptor.FileDescriptorProto, descs []*Descriptor) []*EnumDescriptor { + sl := make([]*EnumDescriptor, 0, len(file.EnumType)+10) + // Top-level enums. + for i, enum := range file.EnumType { + sl = append(sl, newEnumDescriptor(enum, nil, file, i)) + } + // Enums within messages. Enums within embedded messages appear in the outer-most message. + for _, nested := range descs { + for i, enum := range nested.EnumType { + sl = append(sl, newEnumDescriptor(enum, nested, file, i)) + } + } + return sl +} + +// Return a slice of all the top-level ExtensionDescriptors defined within this file. +func wrapExtensions(file *descriptor.FileDescriptorProto) []*ExtensionDescriptor { + sl := make([]*ExtensionDescriptor, len(file.Extension)) + for i, field := range file.Extension { + sl[i] = &ExtensionDescriptor{common{file}, field, nil} + } + return sl +} + +// Return a slice of all the types that are publicly imported into this file. +func wrapImported(file *descriptor.FileDescriptorProto, g *Generator) (sl []*ImportedDescriptor) { + for _, index := range file.PublicDependency { + df := g.fileByName(file.Dependency[index]) + for _, d := range df.desc { + sl = append(sl, &ImportedDescriptor{common{file}, d}) + } + for _, e := range df.enum { + sl = append(sl, &ImportedDescriptor{common{file}, e}) + } + for _, ext := range df.ext { + sl = append(sl, &ImportedDescriptor{common{file}, ext}) + } + } + return +} + +func extractComments(file *FileDescriptor) { + file.comments = make(map[string]*descriptor.SourceCodeInfo_Location) + for _, loc := range file.GetSourceCodeInfo().GetLocation() { + if loc.LeadingComments == nil { + continue + } + var p []string + for _, n := range loc.Path { + p = append(p, strconv.Itoa(int(n))) + } + file.comments[strings.Join(p, ",")] = loc + } +} + +// BuildTypeNameMap builds the map from fully qualified type names to objects. +// The key names for the map come from the input data, which puts a period at the beginning. +// It should be called after SetPackageNames and before GenerateAllFiles. +func (g *Generator) BuildTypeNameMap() { + g.typeNameToObject = make(map[string]Object) + for _, f := range g.allFiles { + // The names in this loop are defined by the proto world, not us, so the + // package name may be empty. If so, the dotted package name of X will + // be ".X"; otherwise it will be ".pkg.X". + dottedPkg := "." + f.GetPackage() + if dottedPkg != "." { + dottedPkg += "." + } + for _, enum := range f.enum { + name := dottedPkg + dottedSlice(enum.TypeName()) + g.typeNameToObject[name] = enum + } + for _, desc := range f.desc { + name := dottedPkg + dottedSlice(desc.TypeName()) + g.typeNameToObject[name] = desc + } + } +} + +// ObjectNamed, given a fully-qualified input type name as it appears in the input data, +// returns the descriptor for the message or enum with that name. +func (g *Generator) ObjectNamed(typeName string) Object { + o, ok := g.typeNameToObject[typeName] + if !ok { + g.Fail("can't find object with type", typeName) + } + + // If the file of this object isn't a direct dependency of the current file, + // or in the current file, then this object has been publicly imported into + // a dependency of the current file. + // We should return the ImportedDescriptor object for it instead. + direct := *o.File().Name == *g.file.Name + if !direct { + for _, dep := range g.file.Dependency { + if *g.fileByName(dep).Name == *o.File().Name { + direct = true + break + } + } + } + if !direct { + found := false + Loop: + for _, dep := range g.file.Dependency { + df := g.fileByName(*g.fileByName(dep).Name) + for _, td := range df.imp { + if td.o == o { + // Found it! + o = td + found = true + break Loop + } + } + } + if !found { + log.Printf("protoc-gen-go: WARNING: failed finding publicly imported dependency for %v, used in %v", typeName, *g.file.Name) + } + } + + return o +} + +// P prints the arguments to the generated output. It handles strings and int32s, plus +// handling indirections because they may be *string, etc. +func (g *Generator) P(str ...interface{}) { + g.WriteString(g.indent) + for _, v := range str { + switch s := v.(type) { + case string: + g.WriteString(s) + case *string: + g.WriteString(*s) + case bool: + g.WriteString(fmt.Sprintf("%t", s)) + case *bool: + g.WriteString(fmt.Sprintf("%t", *s)) + case int: + g.WriteString(fmt.Sprintf("%d", s)) + case *int32: + g.WriteString(fmt.Sprintf("%d", *s)) + case *int64: + g.WriteString(fmt.Sprintf("%d", *s)) + case float64: + g.WriteString(fmt.Sprintf("%g", s)) + case *float64: + g.WriteString(fmt.Sprintf("%g", *s)) + default: + g.Fail(fmt.Sprintf("unknown type in printer: %T", v)) + } + } + g.WriteByte('\n') +} + +// In Indents the output one tab stop. +func (g *Generator) In() { g.indent += "\t" } + +// Out unindents the output one tab stop. +func (g *Generator) Out() { + if len(g.indent) > 0 { + g.indent = g.indent[1:] + } +} + +// GenerateAllFiles generates the output for all the files we're outputting. +func (g *Generator) GenerateAllFiles() { + // Initialize the plugins + for _, p := range plugins { + p.Init(g) + } + // Generate the output. The generator runs for every file, even the files + // that we don't generate output for, so that we can collate the full list + // of exported symbols to support public imports. + genFileMap := make(map[*FileDescriptor]bool, len(g.genFiles)) + for _, file := range g.genFiles { + genFileMap[file] = true + } + i := 0 + for _, file := range g.allFiles { + g.Reset() + g.generate(file) + if _, ok := genFileMap[file]; !ok { + continue + } + g.Response.File[i] = new(plugin.CodeGeneratorResponse_File) + g.Response.File[i].Name = proto.String(goFileName(*file.Name)) + g.Response.File[i].Content = proto.String(g.String()) + i++ + } +} + +// Run all the plugins associated with the file. +func (g *Generator) runPlugins(file *FileDescriptor) { + for _, p := range plugins { + p.Generate(file) + } +} + +// FileOf return the FileDescriptor for this FileDescriptorProto. +func (g *Generator) FileOf(fd *descriptor.FileDescriptorProto) *FileDescriptor { + for _, file := range g.allFiles { + if file.FileDescriptorProto == fd { + return file + } + } + g.Fail("could not find file in table:", fd.GetName()) + return nil +} + +// Fill the response protocol buffer with the generated output for all the files we're +// supposed to generate. +func (g *Generator) generate(file *FileDescriptor) { + g.file = g.FileOf(file.FileDescriptorProto) + g.usedPackages = make(map[string]bool) + + for _, td := range g.file.imp { + g.generateImported(td) + } + for _, enum := range g.file.enum { + g.generateEnum(enum) + } + for _, desc := range g.file.desc { + // Don't generate virtual messages for maps. + if desc.GetOptions().GetMapEntry() { + continue + } + g.generateMessage(desc) + } + for _, ext := range g.file.ext { + g.generateExtension(ext) + } + g.generateInitFunction() + + // Run the plugins before the imports so we know which imports are necessary. + g.runPlugins(file) + + // Generate header and imports last, though they appear first in the output. + rem := g.Buffer + g.Buffer = new(bytes.Buffer) + g.generateHeader() + g.generateImports() + g.Write(rem.Bytes()) + + // Reformat generated code. + fset := token.NewFileSet() + ast, err := parser.ParseFile(fset, "", g, parser.ParseComments) + if err != nil { + g.Fail("bad Go source code was generated:", err.Error()) + return + } + g.Reset() + err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(g, fset, ast) + if err != nil { + g.Fail("generated Go source code could not be reformatted:", err.Error()) + } +} + +// Generate the header, including package definition +func (g *Generator) generateHeader() { + g.P("// Code generated by protoc-gen-go.") + g.P("// source: ", g.file.Name) + g.P("// DO NOT EDIT!") + g.P() + + name := g.file.PackageName() + + if g.file.index == 0 { + // Generate package docs for the first file in the package. + g.P("/*") + g.P("Package ", name, " is a generated protocol buffer package.") + g.P() + if loc, ok := g.file.comments[strconv.Itoa(packagePath)]; ok { + // not using g.PrintComments because this is a /* */ comment block. + text := strings.TrimSuffix(loc.GetLeadingComments(), "\n") + for _, line := range strings.Split(text, "\n") { + line = strings.TrimPrefix(line, " ") + // ensure we don't escape from the block comment + line = strings.Replace(line, "*/", "* /", -1) + g.P(line) + } + g.P() + } + g.P("It is generated from these files:") + for _, f := range g.genFiles { + g.P("\t", f.Name) + } + g.P() + g.P("It has these top-level messages:") + for _, msg := range g.file.desc { + if msg.parent != nil { + continue + } + g.P("\t", CamelCaseSlice(msg.TypeName())) + } + g.P("*/") + } + + g.P("package ", name) + g.P() +} + +// PrintComments prints any comments from the source .proto file. +// The path is a comma-separated list of integers. +// See descriptor.proto for its format. +func (g *Generator) PrintComments(path string) { + if loc, ok := g.file.comments[path]; ok { + text := strings.TrimSuffix(loc.GetLeadingComments(), "\n") + for _, line := range strings.Split(text, "\n") { + g.P("// ", strings.TrimPrefix(line, " ")) + } + } +} + +func (g *Generator) fileByName(filename string) *FileDescriptor { + for _, fd := range g.allFiles { + if fd.GetName() == filename { + return fd + } + } + return nil +} + +// weak returns whether the ith import of the current file is a weak import. +func (g *Generator) weak(i int32) bool { + for _, j := range g.file.WeakDependency { + if j == i { + return true + } + } + return false +} + +// Generate the imports +func (g *Generator) generateImports() { + // We almost always need a proto import. Rather than computing when we + // do, which is tricky when there's a plugin, just import it and + // reference it later. The same argument applies to the math package, + // for handling bit patterns for floating-point numbers. + g.P("import " + g.Pkg["proto"] + " " + strconv.Quote(g.ImportPrefix+"github.com/golang/protobuf/proto")) + if !g.file.proto3 { + g.P("import " + g.Pkg["math"] + ` "math"`) + } + for i, s := range g.file.Dependency { + fd := g.fileByName(s) + // Do not import our own package. + if fd.PackageName() == g.packageName { + continue + } + filename := goFileName(s) + // By default, import path is the dirname of the Go filename. + importPath := path.Dir(filename) + if substitution, ok := g.ImportMap[s]; ok { + importPath = substitution + } + importPath = g.ImportPrefix + importPath + // Skip weak imports. + if g.weak(int32(i)) { + g.P("// skipping weak import ", fd.PackageName(), " ", strconv.Quote(importPath)) + continue + } + if _, ok := g.usedPackages[fd.PackageName()]; ok { + g.P("import ", fd.PackageName(), " ", strconv.Quote(importPath)) + } else { + // TODO: Re-enable this when we are more feature-complete. + // For instance, some protos use foreign field extensions, which we don't support. + // Until then, this is just annoying spam. + //log.Printf("protoc-gen-go: discarding unused import from %v: %v", *g.file.Name, s) + g.P("// discarding unused import ", fd.PackageName(), " ", strconv.Quote(importPath)) + } + } + g.P() + // TODO: may need to worry about uniqueness across plugins + for _, p := range plugins { + p.GenerateImports(g.file) + g.P() + } + g.P("// Reference imports to suppress errors if they are not otherwise used.") + g.P("var _ = ", g.Pkg["proto"], ".Marshal") + if !g.file.proto3 { + g.P("var _ = ", g.Pkg["math"], ".Inf") + } + g.P() +} + +func (g *Generator) generateImported(id *ImportedDescriptor) { + // Don't generate public import symbols for files that we are generating + // code for, since those symbols will already be in this package. + // We can't simply avoid creating the ImportedDescriptor objects, + // because g.genFiles isn't populated at that stage. + tn := id.TypeName() + sn := tn[len(tn)-1] + df := g.FileOf(id.o.File()) + filename := *df.Name + for _, fd := range g.genFiles { + if *fd.Name == filename { + g.P("// Ignoring public import of ", sn, " from ", filename) + g.P() + return + } + } + g.P("// ", sn, " from public import ", filename) + g.usedPackages[df.PackageName()] = true + + for _, sym := range df.exported[id.o] { + sym.GenerateAlias(g, df.PackageName()) + } + + g.P() +} + +// Generate the enum definitions for this EnumDescriptor. +func (g *Generator) generateEnum(enum *EnumDescriptor) { + // The full type name + typeName := enum.TypeName() + // The full type name, CamelCased. + ccTypeName := CamelCaseSlice(typeName) + ccPrefix := enum.prefix() + + g.PrintComments(enum.path) + g.P("type ", ccTypeName, " int32") + g.file.addExport(enum, enumSymbol{ccTypeName, enum.proto3()}) + g.P("const (") + g.In() + for i, e := range enum.Value { + g.PrintComments(fmt.Sprintf("%s,%d,%d", enum.path, enumValuePath, i)) + + name := ccPrefix + *e.Name + g.P(name, " ", ccTypeName, " = ", e.Number) + g.file.addExport(enum, constOrVarSymbol{name, "const", ccTypeName}) + } + g.Out() + g.P(")") + g.P("var ", ccTypeName, "_name = map[int32]string{") + g.In() + generated := make(map[int32]bool) // avoid duplicate values + for _, e := range enum.Value { + duplicate := "" + if _, present := generated[*e.Number]; present { + duplicate = "// Duplicate value: " + } + g.P(duplicate, e.Number, ": ", strconv.Quote(*e.Name), ",") + generated[*e.Number] = true + } + g.Out() + g.P("}") + g.P("var ", ccTypeName, "_value = map[string]int32{") + g.In() + for _, e := range enum.Value { + g.P(strconv.Quote(*e.Name), ": ", e.Number, ",") + } + g.Out() + g.P("}") + + if !enum.proto3() { + g.P("func (x ", ccTypeName, ") Enum() *", ccTypeName, " {") + g.In() + g.P("p := new(", ccTypeName, ")") + g.P("*p = x") + g.P("return p") + g.Out() + g.P("}") + } + + g.P("func (x ", ccTypeName, ") String() string {") + g.In() + g.P("return ", g.Pkg["proto"], ".EnumName(", ccTypeName, "_name, int32(x))") + g.Out() + g.P("}") + + if !enum.proto3() { + g.P("func (x *", ccTypeName, ") UnmarshalJSON(data []byte) error {") + g.In() + g.P("value, err := ", g.Pkg["proto"], ".UnmarshalJSONEnum(", ccTypeName, `_value, data, "`, ccTypeName, `")`) + g.P("if err != nil {") + g.In() + g.P("return err") + g.Out() + g.P("}") + g.P("*x = ", ccTypeName, "(value)") + g.P("return nil") + g.Out() + g.P("}") + } + + g.P() +} + +// The tag is a string like "varint,2,opt,name=fieldname,def=7" that +// identifies details of the field for the protocol buffer marshaling and unmarshaling +// code. The fields are: +// wire encoding +// protocol tag number +// opt,req,rep for optional, required, or repeated +// packed whether the encoding is "packed" (optional; repeated primitives only) +// name= the original declared name +// enum= the name of the enum type if it is an enum-typed field. +// proto3 if this field is in a proto3 message +// def= string representation of the default value, if any. +// The default value must be in a representation that can be used at run-time +// to generate the default value. Thus bools become 0 and 1, for instance. +func (g *Generator) goTag(message *Descriptor, field *descriptor.FieldDescriptorProto, wiretype string) string { + optrepreq := "" + switch { + case isOptional(field): + optrepreq = "opt" + case isRequired(field): + optrepreq = "req" + case isRepeated(field): + optrepreq = "rep" + } + var defaultValue string + if dv := field.DefaultValue; dv != nil { // set means an explicit default + defaultValue = *dv + // Some types need tweaking. + switch *field.Type { + case descriptor.FieldDescriptorProto_TYPE_BOOL: + if defaultValue == "true" { + defaultValue = "1" + } else { + defaultValue = "0" + } + case descriptor.FieldDescriptorProto_TYPE_STRING, + descriptor.FieldDescriptorProto_TYPE_BYTES: + // Nothing to do. Quoting is done for the whole tag. + case descriptor.FieldDescriptorProto_TYPE_ENUM: + // For enums we need to provide the integer constant. + obj := g.ObjectNamed(field.GetTypeName()) + if id, ok := obj.(*ImportedDescriptor); ok { + // It is an enum that was publicly imported. + // We need the underlying type. + obj = id.o + } + enum, ok := obj.(*EnumDescriptor) + if !ok { + log.Printf("obj is a %T", obj) + if id, ok := obj.(*ImportedDescriptor); ok { + log.Printf("id.o is a %T", id.o) + } + g.Fail("unknown enum type", CamelCaseSlice(obj.TypeName())) + } + defaultValue = enum.integerValueAsString(defaultValue) + } + defaultValue = ",def=" + defaultValue + } + enum := "" + if *field.Type == descriptor.FieldDescriptorProto_TYPE_ENUM { + // We avoid using obj.PackageName(), because we want to use the + // original (proto-world) package name. + obj := g.ObjectNamed(field.GetTypeName()) + if id, ok := obj.(*ImportedDescriptor); ok { + obj = id.o + } + enum = ",enum=" + if pkg := obj.File().GetPackage(); pkg != "" { + enum += pkg + "." + } + enum += CamelCaseSlice(obj.TypeName()) + } + packed := "" + if field.Options != nil && field.Options.GetPacked() { + packed = ",packed" + } + fieldName := field.GetName() + name := fieldName + if *field.Type == descriptor.FieldDescriptorProto_TYPE_GROUP { + // We must use the type name for groups instead of + // the field name to preserve capitalization. + // type_name in FieldDescriptorProto is fully-qualified, + // but we only want the local part. + name = *field.TypeName + if i := strings.LastIndex(name, "."); i >= 0 { + name = name[i+1:] + } + } + if name == CamelCase(fieldName) { + name = "" + } else { + name = ",name=" + name + } + if message.proto3() { + // We only need the extra tag for []byte fields; + // no need to add noise for the others. + if *field.Type == descriptor.FieldDescriptorProto_TYPE_BYTES { + name += ",proto3" + } + } + return strconv.Quote(fmt.Sprintf("%s,%d,%s%s%s%s%s", + wiretype, + field.GetNumber(), + optrepreq, + packed, + name, + enum, + defaultValue)) +} + +func needsStar(typ descriptor.FieldDescriptorProto_Type) bool { + switch typ { + case descriptor.FieldDescriptorProto_TYPE_GROUP: + return false + case descriptor.FieldDescriptorProto_TYPE_MESSAGE: + return false + case descriptor.FieldDescriptorProto_TYPE_BYTES: + return false + } + return true +} + +// TypeName is the printed name appropriate for an item. If the object is in the current file, +// TypeName drops the package name and underscores the rest. +// Otherwise the object is from another package; and the result is the underscored +// package name followed by the item name. +// The result always has an initial capital. +func (g *Generator) TypeName(obj Object) string { + return g.DefaultPackageName(obj) + CamelCaseSlice(obj.TypeName()) +} + +// TypeNameWithPackage is like TypeName, but always includes the package +// name even if the object is in our own package. +func (g *Generator) TypeNameWithPackage(obj Object) string { + return obj.PackageName() + CamelCaseSlice(obj.TypeName()) +} + +// GoType returns a string representing the type name, and the wire type +func (g *Generator) GoType(message *Descriptor, field *descriptor.FieldDescriptorProto) (typ string, wire string) { + // TODO: Options. + switch *field.Type { + case descriptor.FieldDescriptorProto_TYPE_DOUBLE: + typ, wire = "float64", "fixed64" + case descriptor.FieldDescriptorProto_TYPE_FLOAT: + typ, wire = "float32", "fixed32" + case descriptor.FieldDescriptorProto_TYPE_INT64: + typ, wire = "int64", "varint" + case descriptor.FieldDescriptorProto_TYPE_UINT64: + typ, wire = "uint64", "varint" + case descriptor.FieldDescriptorProto_TYPE_INT32: + typ, wire = "int32", "varint" + case descriptor.FieldDescriptorProto_TYPE_UINT32: + typ, wire = "uint32", "varint" + case descriptor.FieldDescriptorProto_TYPE_FIXED64: + typ, wire = "uint64", "fixed64" + case descriptor.FieldDescriptorProto_TYPE_FIXED32: + typ, wire = "uint32", "fixed32" + case descriptor.FieldDescriptorProto_TYPE_BOOL: + typ, wire = "bool", "varint" + case descriptor.FieldDescriptorProto_TYPE_STRING: + typ, wire = "string", "bytes" + case descriptor.FieldDescriptorProto_TYPE_GROUP: + desc := g.ObjectNamed(field.GetTypeName()) + typ, wire = "*"+g.TypeName(desc), "group" + case descriptor.FieldDescriptorProto_TYPE_MESSAGE: + desc := g.ObjectNamed(field.GetTypeName()) + typ, wire = "*"+g.TypeName(desc), "bytes" + case descriptor.FieldDescriptorProto_TYPE_BYTES: + typ, wire = "[]byte", "bytes" + case descriptor.FieldDescriptorProto_TYPE_ENUM: + desc := g.ObjectNamed(field.GetTypeName()) + typ, wire = g.TypeName(desc), "varint" + case descriptor.FieldDescriptorProto_TYPE_SFIXED32: + typ, wire = "int32", "fixed32" + case descriptor.FieldDescriptorProto_TYPE_SFIXED64: + typ, wire = "int64", "fixed64" + case descriptor.FieldDescriptorProto_TYPE_SINT32: + typ, wire = "int32", "zigzag32" + case descriptor.FieldDescriptorProto_TYPE_SINT64: + typ, wire = "int64", "zigzag64" + default: + g.Fail("unknown type for", field.GetName()) + } + if isRepeated(field) { + typ = "[]" + typ + } else if message != nil && message.proto3() { + return + } else if needsStar(*field.Type) { + typ = "*" + typ + } + return +} + +func (g *Generator) RecordTypeUse(t string) { + if obj, ok := g.typeNameToObject[t]; ok { + // Call ObjectNamed to get the true object to record the use. + obj = g.ObjectNamed(t) + g.usedPackages[obj.PackageName()] = true + } +} + +// Method names that may be generated. Fields with these names get an +// underscore appended. +var methodNames = [...]string{ + "Reset", + "String", + "ProtoMessage", + "Marshal", + "Unmarshal", + "ExtensionRangeArray", + "ExtensionMap", + "Descriptor", +} + +// Generate the type and default constant definitions for this Descriptor. +func (g *Generator) generateMessage(message *Descriptor) { + // The full type name + typeName := message.TypeName() + // The full type name, CamelCased. + ccTypeName := CamelCaseSlice(typeName) + + usedNames := make(map[string]bool) + for _, n := range methodNames { + usedNames[n] = true + } + fieldNames := make(map[*descriptor.FieldDescriptorProto]string) + fieldGetterNames := make(map[*descriptor.FieldDescriptorProto]string) + mapFieldTypes := make(map[*descriptor.FieldDescriptorProto]string) + + g.PrintComments(message.path) + g.P("type ", ccTypeName, " struct {") + g.In() + + for i, field := range message.Field { + g.PrintComments(fmt.Sprintf("%s,%d,%d", message.path, messageFieldPath, i)) + + fieldName := CamelCase(*field.Name) + for usedNames[fieldName] { + fieldName += "_" + } + fieldGetterName := fieldName + usedNames[fieldName] = true + typename, wiretype := g.GoType(message, field) + jsonName := *field.Name + tag := fmt.Sprintf("protobuf:%s json:%q", g.goTag(message, field, wiretype), jsonName+",omitempty") + + if *field.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE { + desc := g.ObjectNamed(field.GetTypeName()) + if d, ok := desc.(*Descriptor); ok && d.GetOptions().GetMapEntry() { + // Figure out the Go types and tags for the key and value types. + keyField, valField := d.Field[0], d.Field[1] + keyType, keyWire := g.GoType(d, keyField) + valType, valWire := g.GoType(d, valField) + keyTag, valTag := g.goTag(d, keyField, keyWire), g.goTag(d, valField, valWire) + + // We don't use stars, except for message-typed values. + // Message and enum types are the only two possibly foreign types used in maps, + // so record their use. They are not permitted as map keys. + keyType = strings.TrimPrefix(keyType, "*") + switch *valField.Type { + case descriptor.FieldDescriptorProto_TYPE_ENUM: + valType = strings.TrimPrefix(valType, "*") + g.RecordTypeUse(valField.GetTypeName()) + case descriptor.FieldDescriptorProto_TYPE_MESSAGE: + g.RecordTypeUse(valField.GetTypeName()) + default: + valType = strings.TrimPrefix(valType, "*") + } + + typename = fmt.Sprintf("map[%s]%s", keyType, valType) + mapFieldTypes[field] = typename // record for the getter generation + + tag += fmt.Sprintf(" protobuf_key:%s protobuf_val:%s", keyTag, valTag) + } + } + + fieldNames[field] = fieldName + fieldGetterNames[field] = fieldGetterName + g.P(fieldName, "\t", typename, "\t`", tag, "`") + g.RecordTypeUse(field.GetTypeName()) + } + if len(message.ExtensionRange) > 0 { + g.P("XXX_extensions\t\tmap[int32]", g.Pkg["proto"], ".Extension `json:\"-\"`") + } + if !message.proto3() { + g.P("XXX_unrecognized\t[]byte `json:\"-\"`") + } + g.Out() + g.P("}") + + // Reset, String and ProtoMessage methods. + g.P("func (m *", ccTypeName, ") Reset() { *m = ", ccTypeName, "{} }") + g.P("func (m *", ccTypeName, ") String() string { return ", g.Pkg["proto"], ".CompactTextString(m) }") + g.P("func (*", ccTypeName, ") ProtoMessage() {}") + + // Extension support methods + var hasExtensions, isMessageSet bool + if len(message.ExtensionRange) > 0 { + hasExtensions = true + // message_set_wire_format only makes sense when extensions are defined. + if opts := message.Options; opts != nil && opts.GetMessageSetWireFormat() { + isMessageSet = true + g.P() + g.P("func (m *", ccTypeName, ") Marshal() ([]byte, error) {") + g.In() + g.P("return ", g.Pkg["proto"], ".MarshalMessageSet(m.ExtensionMap())") + g.Out() + g.P("}") + g.P("func (m *", ccTypeName, ") Unmarshal(buf []byte) error {") + g.In() + g.P("return ", g.Pkg["proto"], ".UnmarshalMessageSet(buf, m.ExtensionMap())") + g.Out() + g.P("}") + g.P("func (m *", ccTypeName, ") MarshalJSON() ([]byte, error) {") + g.In() + g.P("return ", g.Pkg["proto"], ".MarshalMessageSetJSON(m.XXX_extensions)") + g.Out() + g.P("}") + g.P("func (m *", ccTypeName, ") UnmarshalJSON(buf []byte) error {") + g.In() + g.P("return ", g.Pkg["proto"], ".UnmarshalMessageSetJSON(buf, m.XXX_extensions)") + g.Out() + g.P("}") + g.P("// ensure ", ccTypeName, " satisfies proto.Marshaler and proto.Unmarshaler") + g.P("var _ ", g.Pkg["proto"], ".Marshaler = (*", ccTypeName, ")(nil)") + g.P("var _ ", g.Pkg["proto"], ".Unmarshaler = (*", ccTypeName, ")(nil)") + } + + g.P() + g.P("var extRange_", ccTypeName, " = []", g.Pkg["proto"], ".ExtensionRange{") + g.In() + for _, r := range message.ExtensionRange { + end := fmt.Sprint(*r.End - 1) // make range inclusive on both ends + g.P("{", r.Start, ", ", end, "},") + } + g.Out() + g.P("}") + g.P("func (*", ccTypeName, ") ExtensionRangeArray() []", g.Pkg["proto"], ".ExtensionRange {") + g.In() + g.P("return extRange_", ccTypeName) + g.Out() + g.P("}") + g.P("func (m *", ccTypeName, ") ExtensionMap() map[int32]", g.Pkg["proto"], ".Extension {") + g.In() + g.P("if m.XXX_extensions == nil {") + g.In() + g.P("m.XXX_extensions = make(map[int32]", g.Pkg["proto"], ".Extension)") + g.Out() + g.P("}") + g.P("return m.XXX_extensions") + g.Out() + g.P("}") + } + + // Default constants + defNames := make(map[*descriptor.FieldDescriptorProto]string) + for _, field := range message.Field { + def := field.GetDefaultValue() + if def == "" { + continue + } + fieldname := "Default_" + ccTypeName + "_" + CamelCase(*field.Name) + defNames[field] = fieldname + typename, _ := g.GoType(message, field) + if typename[0] == '*' { + typename = typename[1:] + } + kind := "const " + switch { + case typename == "bool": + case typename == "string": + def = strconv.Quote(def) + case typename == "[]byte": + def = "[]byte(" + strconv.Quote(def) + ")" + kind = "var " + case def == "inf", def == "-inf", def == "nan": + // These names are known to, and defined by, the protocol language. + switch def { + case "inf": + def = "math.Inf(1)" + case "-inf": + def = "math.Inf(-1)" + case "nan": + def = "math.NaN()" + } + if *field.Type == descriptor.FieldDescriptorProto_TYPE_FLOAT { + def = "float32(" + def + ")" + } + kind = "var " + case *field.Type == descriptor.FieldDescriptorProto_TYPE_ENUM: + // Must be an enum. Need to construct the prefixed name. + obj := g.ObjectNamed(field.GetTypeName()) + var enum *EnumDescriptor + if id, ok := obj.(*ImportedDescriptor); ok { + // The enum type has been publicly imported. + enum, _ = id.o.(*EnumDescriptor) + } else { + enum, _ = obj.(*EnumDescriptor) + } + if enum == nil { + log.Printf("don't know how to generate constant for %s", fieldname) + continue + } + def = g.DefaultPackageName(obj) + enum.prefix() + def + } + g.P(kind, fieldname, " ", typename, " = ", def) + g.file.addExport(message, constOrVarSymbol{fieldname, kind, ""}) + } + g.P() + + // Field getters + var getters []getterSymbol + for _, field := range message.Field { + fname := fieldNames[field] + typename, _ := g.GoType(message, field) + if t, ok := mapFieldTypes[field]; ok { + typename = t + } + mname := "Get" + fieldGetterNames[field] + star := "" + if needsStar(*field.Type) && typename[0] == '*' { + typename = typename[1:] + star = "*" + } + + // In proto3, only generate getters for message fields. + if message.proto3() && *field.Type != descriptor.FieldDescriptorProto_TYPE_MESSAGE { + continue + } + + // Only export getter symbols for basic types, + // and for messages and enums in the same package. + // Groups are not exported. + // Foreign types can't be hoisted through a public import because + // the importer may not already be importing the defining .proto. + // As an example, imagine we have an import tree like this: + // A.proto -> B.proto -> C.proto + // If A publicly imports B, we need to generate the getters from B in A's output, + // but if one such getter returns something from C then we cannot do that + // because A is not importing C already. + var getter, genType bool + switch *field.Type { + case descriptor.FieldDescriptorProto_TYPE_GROUP: + getter = false + case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_ENUM: + // Only export getter if its return type is in this package. + getter = g.ObjectNamed(field.GetTypeName()).PackageName() == message.PackageName() + genType = true + default: + getter = true + } + if getter { + getters = append(getters, getterSymbol{ + name: mname, + typ: typename, + typeName: field.GetTypeName(), + genType: genType, + }) + } + + g.P("func (m *", ccTypeName, ") "+mname+"() "+typename+" {") + g.In() + def, hasDef := defNames[field] + typeDefaultIsNil := false // whether this field type's default value is a literal nil unless specified + switch *field.Type { + case descriptor.FieldDescriptorProto_TYPE_BYTES: + typeDefaultIsNil = !hasDef + case descriptor.FieldDescriptorProto_TYPE_GROUP, descriptor.FieldDescriptorProto_TYPE_MESSAGE: + typeDefaultIsNil = true + } + if isRepeated(field) { + typeDefaultIsNil = true + } + if typeDefaultIsNil { + // A bytes field with no explicit default needs less generated code, + // as does a message or group field, or a repeated field. + g.P("if m != nil {") + g.In() + g.P("return m." + fname) + g.Out() + g.P("}") + g.P("return nil") + g.Out() + g.P("}") + g.P() + continue + } + g.P("if m != nil && m." + fname + " != nil {") + g.In() + g.P("return " + star + "m." + fname) + g.Out() + g.P("}") + if hasDef { + if *field.Type != descriptor.FieldDescriptorProto_TYPE_BYTES { + g.P("return " + def) + } else { + // The default is a []byte var. + // Make a copy when returning it to be safe. + g.P("return append([]byte(nil), ", def, "...)") + } + } else { + switch *field.Type { + case descriptor.FieldDescriptorProto_TYPE_BOOL: + g.P("return false") + case descriptor.FieldDescriptorProto_TYPE_STRING: + g.P(`return ""`) + case descriptor.FieldDescriptorProto_TYPE_ENUM: + // The default default for an enum is the first value in the enum, + // not zero. + obj := g.ObjectNamed(field.GetTypeName()) + var enum *EnumDescriptor + if id, ok := obj.(*ImportedDescriptor); ok { + // The enum type has been publicly imported. + enum, _ = id.o.(*EnumDescriptor) + } else { + enum, _ = obj.(*EnumDescriptor) + } + if enum == nil { + log.Printf("don't know how to generate getter for %s", field.GetName()) + continue + } + if len(enum.Value) == 0 { + g.P("return 0 // empty enum") + } else { + first := enum.Value[0].GetName() + g.P("return ", g.DefaultPackageName(obj)+enum.prefix()+first) + } + default: + g.P("return 0") + } + } + g.Out() + g.P("}") + g.P() + } + + if !message.group { + ms := &messageSymbol{sym: ccTypeName, hasExtensions: hasExtensions, isMessageSet: isMessageSet, getters: getters} + g.file.addExport(message, ms) + } + + for _, ext := range message.ext { + g.generateExtension(ext) + } + +} + +func (g *Generator) generateExtension(ext *ExtensionDescriptor) { + ccTypeName := ext.DescName() + + extDesc := g.ObjectNamed(*ext.Extendee).(*Descriptor) + extendedType := "*" + g.TypeName(extDesc) + field := ext.FieldDescriptorProto + fieldType, wireType := g.GoType(ext.parent, field) + tag := g.goTag(extDesc, field, wireType) + g.RecordTypeUse(*ext.Extendee) + if n := ext.FieldDescriptorProto.TypeName; n != nil { + // foreign extension type + g.RecordTypeUse(*n) + } + + typeName := ext.TypeName() + + // Special case for proto2 message sets: If this extension is extending + // proto2_bridge.MessageSet, and its final name component is "message_set_extension", + // then drop that last component. + mset := false + if extendedType == "*proto2_bridge.MessageSet" && typeName[len(typeName)-1] == "message_set_extension" { + typeName = typeName[:len(typeName)-1] + mset = true + } + + // For text formatting, the package must be exactly what the .proto file declares, + // ignoring overrides such as the go_package option, and with no dot/underscore mapping. + extName := strings.Join(typeName, ".") + if g.file.Package != nil { + extName = *g.file.Package + "." + extName + } + + g.P("var ", ccTypeName, " = &", g.Pkg["proto"], ".ExtensionDesc{") + g.In() + g.P("ExtendedType: (", extendedType, ")(nil),") + g.P("ExtensionType: (", fieldType, ")(nil),") + g.P("Field: ", field.Number, ",") + g.P(`Name: "`, extName, `",`) + g.P("Tag: ", tag, ",") + + g.Out() + g.P("}") + g.P() + + if mset { + // Generate a bit more code to register with message_set.go. + g.P("func init() { ") + g.In() + g.P(g.Pkg["proto"], ".RegisterMessageSetType((", fieldType, ")(nil), ", field.Number, ", \"", extName, "\")") + g.Out() + g.P("}") + } + + g.file.addExport(ext, constOrVarSymbol{ccTypeName, "var", ""}) +} + +func (g *Generator) generateInitFunction() { + g.P("func init() {") + g.In() + for _, enum := range g.file.enum { + g.generateEnumRegistration(enum) + } + for _, d := range g.file.desc { + for _, ext := range d.ext { + g.generateExtensionRegistration(ext) + } + } + for _, ext := range g.file.ext { + g.generateExtensionRegistration(ext) + } + g.Out() + g.P("}") +} + +func (g *Generator) generateEnumRegistration(enum *EnumDescriptor) { + // // We always print the full (proto-world) package name here. + pkg := enum.File().GetPackage() + if pkg != "" { + pkg += "." + } + // The full type name + typeName := enum.TypeName() + // The full type name, CamelCased. + ccTypeName := CamelCaseSlice(typeName) + g.P(g.Pkg["proto"]+".RegisterEnum(", strconv.Quote(pkg+ccTypeName), ", ", ccTypeName+"_name, ", ccTypeName+"_value)") +} + +func (g *Generator) generateExtensionRegistration(ext *ExtensionDescriptor) { + g.P(g.Pkg["proto"]+".RegisterExtension(", ext.DescName(), ")") +} + +// And now lots of helper functions. + +// Is c an ASCII lower-case letter? +func isASCIILower(c byte) bool { + return 'a' <= c && c <= 'z' +} + +// Is c an ASCII digit? +func isASCIIDigit(c byte) bool { + return '0' <= c && c <= '9' +} + +// CamelCase returns the CamelCased name. +// If there is an interior underscore followed by a lower case letter, +// drop the underscore and convert the letter to upper case. +// There is a remote possibility of this rewrite causing a name collision, +// but it's so remote we're prepared to pretend it's nonexistent - since the +// C++ generator lowercases names, it's extremely unlikely to have two fields +// with different capitalizations. +// In short, _my_field_name_2 becomes XMyFieldName_2. +func CamelCase(s string) string { + if s == "" { + return "" + } + t := make([]byte, 0, 32) + i := 0 + if s[0] == '_' { + // Need a capital letter; drop the '_'. + t = append(t, 'X') + i++ + } + // Invariant: if the next letter is lower case, it must be converted + // to upper case. + // That is, we process a word at a time, where words are marked by _ or + // upper case letter. Digits are treated as words. + for ; i < len(s); i++ { + c := s[i] + if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { + continue // Skip the underscore in s. + } + if isASCIIDigit(c) { + t = append(t, c) + continue + } + // Assume we have a letter now - if not, it's a bogus identifier. + // The next word is a sequence of characters that must start upper case. + if isASCIILower(c) { + c ^= ' ' // Make it a capital letter. + } + t = append(t, c) // Guaranteed not lower case. + // Accept lower case sequence that follows. + for i+1 < len(s) && isASCIILower(s[i+1]) { + i++ + t = append(t, s[i]) + } + } + return string(t) +} + +// CamelCaseSlice is like CamelCase, but the argument is a slice of strings to +// be joined with "_". +func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) } + +// dottedSlice turns a sliced name into a dotted name. +func dottedSlice(elem []string) string { return strings.Join(elem, ".") } + +// Given a .proto file name, return the output name for the generated Go program. +func goFileName(name string) string { + ext := path.Ext(name) + if ext == ".proto" || ext == ".protodevel" { + name = name[0 : len(name)-len(ext)] + } + return name + ".pb.go" +} + +// Is this field optional? +func isOptional(field *descriptor.FieldDescriptorProto) bool { + return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_OPTIONAL +} + +// Is this field required? +func isRequired(field *descriptor.FieldDescriptorProto) bool { + return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_REQUIRED +} + +// Is this field repeated? +func isRepeated(field *descriptor.FieldDescriptorProto) bool { + return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED +} + +// badToUnderscore is the mapping function used to generate Go names from package names, +// which can be dotted in the input .proto file. It replaces non-identifier characters such as +// dot or dash with underscore. +func badToUnderscore(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { + return r + } + return '_' +} + +// baseName returns the last path element of the name, with the last dotted suffix removed. +func baseName(name string) string { + // First, find the last element + if i := strings.LastIndex(name, "/"); i >= 0 { + name = name[i+1:] + } + // Now drop the suffix + if i := strings.LastIndex(name, "."); i >= 0 { + name = name[0:i] + } + return name +} + +// The SourceCodeInfo message describes the location of elements of a parsed +// .proto file by way of a "path", which is a sequence of integers that +// describe the route from a FileDescriptorProto to the relevant submessage. +// The path alternates between a field number of a repeated field, and an index +// into that repeated field. The constants below define the field numbers that +// are used. +// +// See descriptor.proto for more information about this. +const ( + // tag numbers in FileDescriptorProto + packagePath = 2 // package + messagePath = 4 // message_type + enumPath = 5 // enum_type + // tag numbers in DescriptorProto + messageFieldPath = 2 // field + messageMessagePath = 3 // nested_type + messageEnumPath = 4 // enum_type + // tag numbers in EnumDescriptorProto + enumValuePath = 2 // value +) diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/generator/Makefile charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/generator/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/generator/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/generator/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,40 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include $(GOROOT)/src/Make.inc + +TARG=github.com/golang/protobuf/compiler/generator +GOFILES=\ + generator.go\ + +DEPS=../descriptor ../plugin ../../proto + +include $(GOROOT)/src/Make.pkg diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/generator/name_test.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/generator/name_test.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/generator/name_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/generator/name_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,56 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2013 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package generator + +import ( + "testing" +) + +func TestCamelCase(t *testing.T) { + tests := []struct { + in, want string + }{ + {"one", "One"}, + {"one_two", "OneTwo"}, + {"_my_field_name_2", "XMyFieldName_2"}, + {"Something_Capped", "Something_Capped"}, + {"my_Name", "My_Name"}, + {"OneTwo", "OneTwo"}, + {"_", "X"}, + {"_a_", "XA_"}, + } + for _, tc := range tests { + if got := CamelCase(tc.in); got != tc.want { + t.Errorf("CamelCase(%q) = %q, want %q", tc.in, got, tc.want) + } + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/internal/grpc/grpc.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/internal/grpc/grpc.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/internal/grpc/grpc.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/internal/grpc/grpc.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,436 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2015 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Package grpc outputs gRPC service descriptions in Go code. +// It runs as a plugin for the Go protocol buffer compiler plugin. +// It is linked in to protoc-gen-go. +package grpc + +import ( + "fmt" + "path" + "strconv" + "strings" + + pb "github.com/golang/protobuf/protoc-gen-go/descriptor" + "github.com/golang/protobuf/protoc-gen-go/generator" +) + +// Paths for packages used by code generated in this file, +// relative to the import_prefix of the generator.Generator. +const ( + contextPkgPath = "golang.org/x/net/context" + grpcPkgPath = "google.golang.org/grpc" +) + +func init() { + generator.RegisterPlugin(new(grpc)) +} + +// grpc is an implementation of the Go protocol buffer compiler's +// plugin architecture. It generates bindings for gRPC support. +type grpc struct { + gen *generator.Generator +} + +// Name returns the name of this plugin, "grpc". +func (g *grpc) Name() string { + return "grpc" +} + +// The names for packages imported in the generated code. +// They may vary from the final path component of the import path +// if the name is used by other packages. +var ( + contextPkg string + grpcPkg string +) + +// Init initializes the plugin. +func (g *grpc) Init(gen *generator.Generator) { + g.gen = gen + contextPkg = generator.RegisterUniquePackageName("context", nil) + grpcPkg = generator.RegisterUniquePackageName("grpc", nil) +} + +// Given a type name defined in a .proto, return its object. +// Also record that we're using it, to guarantee the associated import. +func (g *grpc) objectNamed(name string) generator.Object { + g.gen.RecordTypeUse(name) + return g.gen.ObjectNamed(name) +} + +// Given a type name defined in a .proto, return its name as we will print it. +func (g *grpc) typeName(str string) string { + return g.gen.TypeName(g.objectNamed(str)) +} + +// P forwards to g.gen.P. +func (g *grpc) P(args ...interface{}) { g.gen.P(args...) } + +// Generate generates code for the services in the given file. +func (g *grpc) Generate(file *generator.FileDescriptor) { + for i, service := range file.FileDescriptorProto.Service { + g.generateService(file, service, i) + } +} + +// GenerateImports generates the import declaration for this file. +func (g *grpc) GenerateImports(file *generator.FileDescriptor) { + if len(file.FileDescriptorProto.Service) == 0 { + return + } + g.P("import (") + g.P(contextPkg, " ", strconv.Quote(path.Join(g.gen.ImportPrefix, contextPkgPath))) + g.P(grpcPkg, " ", strconv.Quote(path.Join(g.gen.ImportPrefix, grpcPkgPath))) + g.P(")") + g.P() + g.P("// Reference imports to suppress errors if they are not otherwise used.") + g.P("var _ ", contextPkg, ".Context") + g.P("var _ ", grpcPkg, ".ClientConn") + g.P() +} + +// reservedClientName records whether a client name is reserved on the client side. +var reservedClientName = map[string]bool{ +// TODO: do we need any in gRPC? +} + +func unexport(s string) string { return strings.ToLower(s[:1]) + s[1:] } + +// generateService generates all the code for the named service. +func (g *grpc) generateService(file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int) { + path := fmt.Sprintf("6,%d", index) // 6 means service. + + origServName := service.GetName() + fullServName := file.GetPackage() + "." + origServName + servName := generator.CamelCase(origServName) + + g.P() + g.P("// Client API for ", servName, " service") + g.P() + + // Client interface. + g.P("type ", servName, "Client interface {") + for i, method := range service.Method { + g.gen.PrintComments(fmt.Sprintf("%s,2,%d", path, i)) // 2 means method in a service. + g.P(g.generateClientSignature(servName, method)) + } + g.P("}") + g.P() + + // Client structure. + g.P("type ", unexport(servName), "Client struct {") + g.P("cc *", grpcPkg, ".ClientConn") + g.P("}") + g.P() + + // NewClient factory. + g.P("func New", servName, "Client (cc *", grpcPkg, ".ClientConn) ", servName, "Client {") + g.P("return &", unexport(servName), "Client{cc}") + g.P("}") + g.P() + + var methodIndex, streamIndex int + serviceDescVar := "_" + servName + "_serviceDesc" + // Client method implementations. + for _, method := range service.Method { + var descExpr string + if !method.GetServerStreaming() && !method.GetClientStreaming() { + // Unary RPC method + descExpr = fmt.Sprintf("&%s.Methods[%d]", serviceDescVar, methodIndex) + methodIndex++ + } else { + // Streaming RPC method + descExpr = fmt.Sprintf("&%s.Streams[%d]", serviceDescVar, streamIndex) + streamIndex++ + } + g.generateClientMethod(servName, fullServName, serviceDescVar, method, descExpr) + } + + g.P("// Server API for ", servName, " service") + g.P() + + // Server interface. + serverType := servName + "Server" + g.P("type ", serverType, " interface {") + for i, method := range service.Method { + g.gen.PrintComments(fmt.Sprintf("%s,2,%d", path, i)) // 2 means method in a service. + g.P(g.generateServerSignature(servName, method)) + } + g.P("}") + g.P() + + // Server registration. + g.P("func Register", servName, "Server(s *", grpcPkg, ".Server, srv ", serverType, ") {") + g.P("s.RegisterService(&", serviceDescVar, `, srv)`) + g.P("}") + g.P() + + // Server handler implementations. + var handlerNames []string + for _, method := range service.Method { + hname := g.generateServerMethod(servName, method) + handlerNames = append(handlerNames, hname) + } + + // Service descriptor. + g.P("var ", serviceDescVar, " = ", grpcPkg, ".ServiceDesc {") + g.P("ServiceName: ", strconv.Quote(fullServName), ",") + g.P("HandlerType: (*", serverType, ")(nil),") + g.P("Methods: []", grpcPkg, ".MethodDesc{") + for i, method := range service.Method { + if method.GetServerStreaming() || method.GetClientStreaming() { + continue + } + g.P("{") + g.P("MethodName: ", strconv.Quote(method.GetName()), ",") + g.P("Handler: ", handlerNames[i], ",") + g.P("},") + } + g.P("},") + g.P("Streams: []", grpcPkg, ".StreamDesc{") + for i, method := range service.Method { + if !method.GetServerStreaming() && !method.GetClientStreaming() { + continue + } + g.P("{") + g.P("StreamName: ", strconv.Quote(method.GetName()), ",") + g.P("Handler: ", handlerNames[i], ",") + if method.GetServerStreaming() { + g.P("ServerStreams: true,") + } + if method.GetClientStreaming() { + g.P("ClientStreams: true,") + } + g.P("},") + } + g.P("},") + g.P("}") + g.P() +} + +// generateClientSignature returns the client-side signature for a method. +func (g *grpc) generateClientSignature(servName string, method *pb.MethodDescriptorProto) string { + origMethName := method.GetName() + methName := generator.CamelCase(origMethName) + if reservedClientName[methName] { + methName += "_" + } + reqArg := ", in *" + g.typeName(method.GetInputType()) + if method.GetClientStreaming() { + reqArg = "" + } + respName := "*" + g.typeName(method.GetOutputType()) + if method.GetServerStreaming() || method.GetClientStreaming() { + respName = servName + "_" + generator.CamelCase(origMethName) + "Client" + } + return fmt.Sprintf("%s(ctx %s.Context%s, opts ...%s.CallOption) (%s, error)", methName, contextPkg, reqArg, grpcPkg, respName) +} + +func (g *grpc) generateClientMethod(servName, fullServName, serviceDescVar string, method *pb.MethodDescriptorProto, descExpr string) { + sname := fmt.Sprintf("/%s/%s", fullServName, method.GetName()) + methName := generator.CamelCase(method.GetName()) + inType := g.typeName(method.GetInputType()) + outType := g.typeName(method.GetOutputType()) + + g.P("func (c *", unexport(servName), "Client) ", g.generateClientSignature(servName, method), "{") + if !method.GetServerStreaming() && !method.GetClientStreaming() { + g.P("out := new(", outType, ")") + // TODO: Pass descExpr to Invoke. + g.P("err := ", grpcPkg, `.Invoke(ctx, "`, sname, `", in, out, c.cc, opts...)`) + g.P("if err != nil { return nil, err }") + g.P("return out, nil") + g.P("}") + g.P() + return + } + streamType := unexport(servName) + methName + "Client" + g.P("stream, err := ", grpcPkg, ".NewClientStream(ctx, ", descExpr, `, c.cc, "`, sname, `", opts...)`) + g.P("if err != nil { return nil, err }") + g.P("x := &", streamType, "{stream}") + if !method.GetClientStreaming() { + g.P("if err := x.ClientStream.SendMsg(in); err != nil { return nil, err }") + g.P("if err := x.ClientStream.CloseSend(); err != nil { return nil, err }") + } + g.P("return x, nil") + g.P("}") + g.P() + + genSend := method.GetClientStreaming() + genRecv := method.GetServerStreaming() + genCloseAndRecv := !method.GetServerStreaming() + + // Stream auxiliary types and methods. + g.P("type ", servName, "_", methName, "Client interface {") + if genSend { + g.P("Send(*", inType, ") error") + } + if genRecv { + g.P("Recv() (*", outType, ", error)") + } + if genCloseAndRecv { + g.P("CloseAndRecv() (*", outType, ", error)") + } + g.P(grpcPkg, ".ClientStream") + g.P("}") + g.P() + + g.P("type ", streamType, " struct {") + g.P(grpcPkg, ".ClientStream") + g.P("}") + g.P() + + if genSend { + g.P("func (x *", streamType, ") Send(m *", inType, ") error {") + g.P("return x.ClientStream.SendMsg(m)") + g.P("}") + g.P() + } + if genRecv { + g.P("func (x *", streamType, ") Recv() (*", outType, ", error) {") + g.P("m := new(", outType, ")") + g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }") + g.P("return m, nil") + g.P("}") + g.P() + } + if genCloseAndRecv { + g.P("func (x *", streamType, ") CloseAndRecv() (*", outType, ", error) {") + g.P("if err := x.ClientStream.CloseSend(); err != nil { return nil, err }") + g.P("m := new(", outType, ")") + g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }") + g.P("return m, nil") + g.P("}") + g.P() + } +} + +// generateServerSignature returns the server-side signature for a method. +func (g *grpc) generateServerSignature(servName string, method *pb.MethodDescriptorProto) string { + origMethName := method.GetName() + methName := generator.CamelCase(origMethName) + if reservedClientName[methName] { + methName += "_" + } + + var reqArgs []string + ret := "error" + if !method.GetServerStreaming() && !method.GetClientStreaming() { + reqArgs = append(reqArgs, contextPkg+".Context") + ret = "(*" + g.typeName(method.GetOutputType()) + ", error)" + } + if !method.GetClientStreaming() { + reqArgs = append(reqArgs, "*"+g.typeName(method.GetInputType())) + } + if method.GetServerStreaming() || method.GetClientStreaming() { + reqArgs = append(reqArgs, servName+"_"+generator.CamelCase(origMethName)+"Server") + } + + return methName + "(" + strings.Join(reqArgs, ", ") + ") " + ret +} + +func (g *grpc) generateServerMethod(servName string, method *pb.MethodDescriptorProto) string { + methName := generator.CamelCase(method.GetName()) + hname := fmt.Sprintf("_%s_%s_Handler", servName, methName) + inType := g.typeName(method.GetInputType()) + outType := g.typeName(method.GetOutputType()) + + if !method.GetServerStreaming() && !method.GetClientStreaming() { + g.P("func ", hname, "(srv interface{}, ctx ", contextPkg, ".Context, codec ", grpcPkg, ".Codec, buf []byte) (interface{}, error) {") + g.P("in := new(", inType, ")") + g.P("if err := codec.Unmarshal(buf, in); err != nil { return nil, err }") + g.P("out, err := srv.(", servName, "Server).", methName, "(ctx, in)") + g.P("if err != nil { return nil, err }") + g.P("return out, nil") + g.P("}") + g.P() + return hname + } + streamType := unexport(servName) + methName + "Server" + g.P("func ", hname, "(srv interface{}, stream ", grpcPkg, ".ServerStream) error {") + if !method.GetClientStreaming() { + g.P("m := new(", inType, ")") + g.P("if err := stream.RecvMsg(m); err != nil { return err }") + g.P("return srv.(", servName, "Server).", methName, "(m, &", streamType, "{stream})") + } else { + g.P("return srv.(", servName, "Server).", methName, "(&", streamType, "{stream})") + } + g.P("}") + g.P() + + genSend := method.GetServerStreaming() + genSendAndClose := !method.GetServerStreaming() + genRecv := method.GetClientStreaming() + + // Stream auxiliary types and methods. + g.P("type ", servName, "_", methName, "Server interface {") + if genSend { + g.P("Send(*", outType, ") error") + } + if genSendAndClose { + g.P("SendAndClose(*", outType, ") error") + } + if genRecv { + g.P("Recv() (*", inType, ", error)") + } + g.P(grpcPkg, ".ServerStream") + g.P("}") + g.P() + + g.P("type ", streamType, " struct {") + g.P(grpcPkg, ".ServerStream") + g.P("}") + g.P() + + if genSend { + g.P("func (x *", streamType, ") Send(m *", outType, ") error {") + g.P("return x.ServerStream.SendMsg(m)") + g.P("}") + g.P() + } + if genSendAndClose { + g.P("func (x *", streamType, ") SendAndClose(m *", outType, ") error {") + g.P("return x.ServerStream.SendMsg(m)") + g.P("}") + g.P() + } + if genRecv { + g.P("func (x *", streamType, ") Recv() (*", inType, ", error) {") + g.P("m := new(", inType, ")") + g.P("if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err }") + g.P("return m, nil") + g.P("}") + g.P() + } + + return hname +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/link_grpc.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/link_grpc.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/link_grpc.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/link_grpc.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,34 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2015 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import _ "github.com/golang/protobuf/protoc-gen-go/internal/grpc" diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/main.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/main.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/main.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/main.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,98 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// protoc-gen-go is a plugin for the Google protocol buffer compiler to generate +// Go code. Run it by building this program and putting it in your path with +// the name +// protoc-gen-go +// That word 'go' at the end becomes part of the option string set for the +// protocol compiler, so once the protocol compiler (protoc) is installed +// you can run +// protoc --go_out=output_directory input_directory/file.proto +// to generate Go bindings for the protocol defined by file.proto. +// With that input, the output will be written to +// output_directory/file.pb.go +// +// The generated code is documented in the package comment for +// the library. +// +// See the README and documentation for protocol buffers to learn more: +// https://developers.google.com/protocol-buffers/ +package main + +import ( + "io/ioutil" + "os" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/generator" +) + +func main() { + // Begin by allocating a generator. The request and response structures are stored there + // so we can do error handling easily - the response structure contains the field to + // report failure. + g := generator.New() + + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + g.Error(err, "reading input") + } + + if err := proto.Unmarshal(data, g.Request); err != nil { + g.Error(err, "parsing input proto") + } + + if len(g.Request.FileToGenerate) == 0 { + g.Fail("no files to generate") + } + + g.CommandLineParameters(g.Request.GetParameter()) + + // Create a wrapped version of the Descriptors and EnumDescriptors that + // point to the file that defines them. + g.WrapTypes() + + g.SetPackageNames() + g.BuildTypeNameMap() + + g.GenerateAllFiles() + + // Send back the results. + data, err = proto.Marshal(g.Response) + if err != nil { + g.Error(err, "failed to marshal output proto") + } + _, err = os.Stdout.Write(data) + if err != nil { + g.Error(err, "failed to write output proto") + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/Makefile charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,35 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include $(GOROOT)/src/Make.cmd + +test: + cd testdata && make test diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/plugin/Makefile charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/plugin/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/plugin/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/plugin/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,46 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Not stored here, but plugin.proto is in https://github.com/google/protobuf/ +# at src/google/protobuf/compiler/plugin.proto +# Also we need to fix an import. +regenerate: + echo WARNING! THIS RULE IS PROBABLY NOT RIGHT FOR YOUR INSTALLATION + cd $(HOME)/src/protobuf/src && \ + protoc --go_out=Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. \ + ./google/protobuf/compiler/plugin.proto && \ + cat ./google/protobuf/compiler/plugin.pb.go > $(GOPATH)/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.go + +restore: + cp plugin.pb.golden plugin.pb.go + +preserve: + cp plugin.pb.go plugin.pb.golden diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go. +// source: google/protobuf/compiler/plugin.proto +// DO NOT EDIT! + +/* +Package google_protobuf_compiler is a generated protocol buffer package. + +It is generated from these files: + google/protobuf/compiler/plugin.proto + +It has these top-level messages: + CodeGeneratorRequest + CodeGeneratorResponse +*/ +package google_protobuf_compiler + +import proto "github.com/golang/protobuf/proto" +import math "math" +import google_protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +// An encoded CodeGeneratorRequest is written to the plugin's stdin. +type CodeGeneratorRequest struct { + // The .proto files that were explicitly listed on the command-line. The + // code generator should generate code only for these files. Each file's + // descriptor will be included in proto_file, below. + FileToGenerate []string `protobuf:"bytes,1,rep,name=file_to_generate" json:"file_to_generate,omitempty"` + // The generator parameter passed on the command-line. + Parameter *string `protobuf:"bytes,2,opt,name=parameter" json:"parameter,omitempty"` + // FileDescriptorProtos for all files in files_to_generate and everything + // they import. The files will appear in topological order, so each file + // appears before any file that imports it. + // + // protoc guarantees that all proto_files will be written after + // the fields above, even though this is not technically guaranteed by the + // protobuf wire format. This theoretically could allow a plugin to stream + // in the FileDescriptorProtos and handle them one by one rather than read + // the entire set into memory at once. However, as of this writing, this + // is not similarly optimized on protoc's end -- it will store all fields in + // memory at once before sending them to the plugin. + ProtoFile []*google_protobuf.FileDescriptorProto `protobuf:"bytes,15,rep,name=proto_file" json:"proto_file,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CodeGeneratorRequest) Reset() { *m = CodeGeneratorRequest{} } +func (m *CodeGeneratorRequest) String() string { return proto.CompactTextString(m) } +func (*CodeGeneratorRequest) ProtoMessage() {} + +func (m *CodeGeneratorRequest) GetFileToGenerate() []string { + if m != nil { + return m.FileToGenerate + } + return nil +} + +func (m *CodeGeneratorRequest) GetParameter() string { + if m != nil && m.Parameter != nil { + return *m.Parameter + } + return "" +} + +func (m *CodeGeneratorRequest) GetProtoFile() []*google_protobuf.FileDescriptorProto { + if m != nil { + return m.ProtoFile + } + return nil +} + +// The plugin writes an encoded CodeGeneratorResponse to stdout. +type CodeGeneratorResponse struct { + // Error message. If non-empty, code generation failed. The plugin process + // should exit with status code zero even if it reports an error in this way. + // + // This should be used to indicate errors in .proto files which prevent the + // code generator from generating correct code. Errors which indicate a + // problem in protoc itself -- such as the input CodeGeneratorRequest being + // unparseable -- should be reported by writing a message to stderr and + // exiting with a non-zero status code. + Error *string `protobuf:"bytes,1,opt,name=error" json:"error,omitempty"` + File []*CodeGeneratorResponse_File `protobuf:"bytes,15,rep,name=file" json:"file,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CodeGeneratorResponse) Reset() { *m = CodeGeneratorResponse{} } +func (m *CodeGeneratorResponse) String() string { return proto.CompactTextString(m) } +func (*CodeGeneratorResponse) ProtoMessage() {} + +func (m *CodeGeneratorResponse) GetError() string { + if m != nil && m.Error != nil { + return *m.Error + } + return "" +} + +func (m *CodeGeneratorResponse) GetFile() []*CodeGeneratorResponse_File { + if m != nil { + return m.File + } + return nil +} + +// Represents a single generated file. +type CodeGeneratorResponse_File struct { + // The file name, relative to the output directory. The name must not + // contain "." or ".." components and must be relative, not be absolute (so, + // the file cannot lie outside the output directory). "/" must be used as + // the path separator, not "\". + // + // If the name is omitted, the content will be appended to the previous + // file. This allows the generator to break large files into small chunks, + // and allows the generated text to be streamed back to protoc so that large + // files need not reside completely in memory at one time. Note that as of + // this writing protoc does not optimize for this -- it will read the entire + // CodeGeneratorResponse before writing files to disk. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // If non-empty, indicates that the named file should already exist, and the + // content here is to be inserted into that file at a defined insertion + // point. This feature allows a code generator to extend the output + // produced by another code generator. The original generator may provide + // insertion points by placing special annotations in the file that look + // like: + // @@protoc_insertion_point(NAME) + // The annotation can have arbitrary text before and after it on the line, + // which allows it to be placed in a comment. NAME should be replaced with + // an identifier naming the point -- this is what other generators will use + // as the insertion_point. Code inserted at this point will be placed + // immediately above the line containing the insertion point (thus multiple + // insertions to the same point will come out in the order they were added). + // The double-@ is intended to make it unlikely that the generated code + // could contain things that look like insertion points by accident. + // + // For example, the C++ code generator places the following line in the + // .pb.h files that it generates: + // // @@protoc_insertion_point(namespace_scope) + // This line appears within the scope of the file's package namespace, but + // outside of any particular class. Another plugin can then specify the + // insertion_point "namespace_scope" to generate additional classes or + // other declarations that should be placed in this scope. + // + // Note that if the line containing the insertion point begins with + // whitespace, the same whitespace will be added to every line of the + // inserted text. This is useful for languages like Python, where + // indentation matters. In these languages, the insertion point comment + // should be indented the same amount as any inserted code will need to be + // in order to work correctly in that context. + // + // The code generator that generates the initial file and the one which + // inserts into it must both run as part of a single invocation of protoc. + // Code generators are executed in the order in which they appear on the + // command line. + // + // If |insertion_point| is present, |name| must also be present. + InsertionPoint *string `protobuf:"bytes,2,opt,name=insertion_point" json:"insertion_point,omitempty"` + // The file contents. + Content *string `protobuf:"bytes,15,opt,name=content" json:"content,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CodeGeneratorResponse_File) Reset() { *m = CodeGeneratorResponse_File{} } +func (m *CodeGeneratorResponse_File) String() string { return proto.CompactTextString(m) } +func (*CodeGeneratorResponse_File) ProtoMessage() {} + +func (m *CodeGeneratorResponse_File) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *CodeGeneratorResponse_File) GetInsertionPoint() string { + if m != nil && m.InsertionPoint != nil { + return *m.InsertionPoint + } + return "" +} + +func (m *CodeGeneratorResponse_File) GetContent() string { + if m != nil && m.Content != nil { + return *m.Content + } + return "" +} + +func init() { +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.golden charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.golden --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.golden 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/plugin/plugin.pb.golden 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,83 @@ +// Code generated by protoc-gen-go. +// source: google/protobuf/compiler/plugin.proto +// DO NOT EDIT! + +package google_protobuf_compiler + +import proto "github.com/golang/protobuf/proto" +import "math" +import google_protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor" + +// Reference proto and math imports to suppress error if they are not otherwise used. +var _ = proto.GetString +var _ = math.Inf + +type CodeGeneratorRequest struct { + FileToGenerate []string `protobuf:"bytes,1,rep,name=file_to_generate" json:"file_to_generate,omitempty"` + Parameter *string `protobuf:"bytes,2,opt,name=parameter" json:"parameter,omitempty"` + ProtoFile []*google_protobuf.FileDescriptorProto `protobuf:"bytes,15,rep,name=proto_file" json:"proto_file,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *CodeGeneratorRequest) Reset() { *this = CodeGeneratorRequest{} } +func (this *CodeGeneratorRequest) String() string { return proto.CompactTextString(this) } +func (*CodeGeneratorRequest) ProtoMessage() {} + +func (this *CodeGeneratorRequest) GetParameter() string { + if this != nil && this.Parameter != nil { + return *this.Parameter + } + return "" +} + +type CodeGeneratorResponse struct { + Error *string `protobuf:"bytes,1,opt,name=error" json:"error,omitempty"` + File []*CodeGeneratorResponse_File `protobuf:"bytes,15,rep,name=file" json:"file,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *CodeGeneratorResponse) Reset() { *this = CodeGeneratorResponse{} } +func (this *CodeGeneratorResponse) String() string { return proto.CompactTextString(this) } +func (*CodeGeneratorResponse) ProtoMessage() {} + +func (this *CodeGeneratorResponse) GetError() string { + if this != nil && this.Error != nil { + return *this.Error + } + return "" +} + +type CodeGeneratorResponse_File struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + InsertionPoint *string `protobuf:"bytes,2,opt,name=insertion_point" json:"insertion_point,omitempty"` + Content *string `protobuf:"bytes,15,opt,name=content" json:"content,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (this *CodeGeneratorResponse_File) Reset() { *this = CodeGeneratorResponse_File{} } +func (this *CodeGeneratorResponse_File) String() string { return proto.CompactTextString(this) } +func (*CodeGeneratorResponse_File) ProtoMessage() {} + +func (this *CodeGeneratorResponse_File) GetName() string { + if this != nil && this.Name != nil { + return *this.Name + } + return "" +} + +func (this *CodeGeneratorResponse_File) GetInsertionPoint() string { + if this != nil && this.InsertionPoint != nil { + return *this.InsertionPoint + } + return "" +} + +func (this *CodeGeneratorResponse_File) GetContent() string { + if this != nil && this.Content != nil { + return *this.Content + } + return "" +} + +func init() { +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_base.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_base.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_base.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_base.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,46 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package extension_base; + +message BaseMessage { + optional int32 height = 1; + extensions 4 to 9; + extensions 16 to max; +} + +// Another message that may be extended, using message_set_wire_format. +message OldStyleMessage { + option message_set_wire_format = true; + extensions 100 to max; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_extra.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_extra.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_extra.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_extra.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,38 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package extension_extra; + +message ExtraMessage { + optional int32 width = 1; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_test.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_test.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,210 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Test that we can use protocol buffers that use extensions. + +package testdata + +/* + +import ( + "bytes" + "regexp" + "testing" + + "github.com/golang/protobuf/proto" + base "extension_base.pb" + user "extension_user.pb" +) + +func TestSingleFieldExtension(t *testing.T) { + bm := &base.BaseMessage{ + Height: proto.Int32(178), + } + + // Use extension within scope of another type. + vol := proto.Uint32(11) + err := proto.SetExtension(bm, user.E_LoudMessage_Volume, vol) + if err != nil { + t.Fatal("Failed setting extension:", err) + } + buf, err := proto.Marshal(bm) + if err != nil { + t.Fatal("Failed encoding message with extension:", err) + } + bm_new := new(base.BaseMessage) + if err := proto.Unmarshal(buf, bm_new); err != nil { + t.Fatal("Failed decoding message with extension:", err) + } + if !proto.HasExtension(bm_new, user.E_LoudMessage_Volume) { + t.Fatal("Decoded message didn't contain extension.") + } + vol_out, err := proto.GetExtension(bm_new, user.E_LoudMessage_Volume) + if err != nil { + t.Fatal("Failed getting extension:", err) + } + if v := vol_out.(*uint32); *v != *vol { + t.Errorf("vol_out = %v, expected %v", *v, *vol) + } + proto.ClearExtension(bm_new, user.E_LoudMessage_Volume) + if proto.HasExtension(bm_new, user.E_LoudMessage_Volume) { + t.Fatal("Failed clearing extension.") + } +} + +func TestMessageExtension(t *testing.T) { + bm := &base.BaseMessage{ + Height: proto.Int32(179), + } + + // Use extension that is itself a message. + um := &user.UserMessage{ + Name: proto.String("Dave"), + Rank: proto.String("Major"), + } + err := proto.SetExtension(bm, user.E_LoginMessage_UserMessage, um) + if err != nil { + t.Fatal("Failed setting extension:", err) + } + buf, err := proto.Marshal(bm) + if err != nil { + t.Fatal("Failed encoding message with extension:", err) + } + bm_new := new(base.BaseMessage) + if err := proto.Unmarshal(buf, bm_new); err != nil { + t.Fatal("Failed decoding message with extension:", err) + } + if !proto.HasExtension(bm_new, user.E_LoginMessage_UserMessage) { + t.Fatal("Decoded message didn't contain extension.") + } + um_out, err := proto.GetExtension(bm_new, user.E_LoginMessage_UserMessage) + if err != nil { + t.Fatal("Failed getting extension:", err) + } + if n := um_out.(*user.UserMessage).Name; *n != *um.Name { + t.Errorf("um_out.Name = %q, expected %q", *n, *um.Name) + } + if r := um_out.(*user.UserMessage).Rank; *r != *um.Rank { + t.Errorf("um_out.Rank = %q, expected %q", *r, *um.Rank) + } + proto.ClearExtension(bm_new, user.E_LoginMessage_UserMessage) + if proto.HasExtension(bm_new, user.E_LoginMessage_UserMessage) { + t.Fatal("Failed clearing extension.") + } +} + +func TestTopLevelExtension(t *testing.T) { + bm := &base.BaseMessage{ + Height: proto.Int32(179), + } + + width := proto.Int32(17) + err := proto.SetExtension(bm, user.E_Width, width) + if err != nil { + t.Fatal("Failed setting extension:", err) + } + buf, err := proto.Marshal(bm) + if err != nil { + t.Fatal("Failed encoding message with extension:", err) + } + bm_new := new(base.BaseMessage) + if err := proto.Unmarshal(buf, bm_new); err != nil { + t.Fatal("Failed decoding message with extension:", err) + } + if !proto.HasExtension(bm_new, user.E_Width) { + t.Fatal("Decoded message didn't contain extension.") + } + width_out, err := proto.GetExtension(bm_new, user.E_Width) + if err != nil { + t.Fatal("Failed getting extension:", err) + } + if w := width_out.(*int32); *w != *width { + t.Errorf("width_out = %v, expected %v", *w, *width) + } + proto.ClearExtension(bm_new, user.E_Width) + if proto.HasExtension(bm_new, user.E_Width) { + t.Fatal("Failed clearing extension.") + } +} + +func TestMessageSetWireFormat(t *testing.T) { + osm := new(base.OldStyleMessage) + osp := &user.OldStyleParcel{ + Name: proto.String("Dave"), + Height: proto.Int32(178), + } + + err := proto.SetExtension(osm, user.E_OldStyleParcel_MessageSetExtension, osp) + if err != nil { + t.Fatal("Failed setting extension:", err) + } + + buf, err := proto.Marshal(osm) + if err != nil { + t.Fatal("Failed encoding message:", err) + } + + // Data generated from Python implementation. + expected := []byte{ + 11, 16, 209, 15, 26, 9, 10, 4, 68, 97, 118, 101, 16, 178, 1, 12, + } + + if !bytes.Equal(expected, buf) { + t.Errorf("Encoding mismatch.\nwant %+v\n got %+v", expected, buf) + } + + // Check that it is restored correctly. + osm = new(base.OldStyleMessage) + if err := proto.Unmarshal(buf, osm); err != nil { + t.Fatal("Failed decoding message:", err) + } + osp_out, err := proto.GetExtension(osm, user.E_OldStyleParcel_MessageSetExtension) + if err != nil { + t.Fatal("Failed getting extension:", err) + } + osp = osp_out.(*user.OldStyleParcel) + if *osp.Name != "Dave" || *osp.Height != 178 { + t.Errorf("Retrieved extension from decoded message is not correct: %+v", osp) + } +} + +func main() { + // simpler than rigging up gotest + testing.Main(regexp.MatchString, []testing.InternalTest{ + {"TestSingleFieldExtension", TestSingleFieldExtension}, + {"TestMessageExtension", TestMessageExtension}, + {"TestTopLevelExtension", TestTopLevelExtension}, + }, + []testing.InternalBenchmark{}, + []testing.InternalExample{}) +} + +*/ diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_user.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_user.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_user.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/extension_user.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,100 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +import "extension_base.proto"; +import "extension_extra.proto"; + +package extension_user; + +message UserMessage { + optional string name = 1; + optional string rank = 2; +} + +// Extend with a message +extend extension_base.BaseMessage { + optional UserMessage user_message = 5; +} + +// Extend with a foreign message +extend extension_base.BaseMessage { + optional extension_extra.ExtraMessage extra_message = 9; +} + +// Extend with some primitive types +extend extension_base.BaseMessage { + optional int32 width = 6; + optional int64 area = 7; +} + +// Extend inside the scope of another type +message LoudMessage { + extend extension_base.BaseMessage { + optional uint32 volume = 8; + } + extensions 100 to max; +} + +// Extend inside the scope of another type, using a message. +message LoginMessage { + extend extension_base.BaseMessage { + optional UserMessage user_message = 16; + } +} + +// Extend with a repeated field +extend extension_base.BaseMessage { + repeated Detail detail = 17; +} + +message Detail { + optional string color = 1; +} + +// An extension of an extension +message Announcement { + optional string words = 1; + extend LoudMessage { + optional Announcement loud_ext = 100; + } +} + +// Something that can be put in a message set. +message OldStyleParcel { + extend extension_base.OldStyleMessage { + optional OldStyleParcel message_set_extension = 2001; + } + + required string name = 1; + optional int32 height = 2; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/golden_test.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/golden_test.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/golden_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/golden_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,86 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Verify that the compiler output for test.proto is unchanged. + +package testdata + +import ( + "crypto/sha1" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "testing" +) + +// sum returns in string form (for easy comparison) the SHA-1 hash of the named file. +func sum(t *testing.T, name string) string { + data, err := ioutil.ReadFile(name) + if err != nil { + t.Fatal(err) + } + t.Logf("sum(%q): length is %d", name, len(data)) + hash := sha1.New() + _, err = hash.Write(data) + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf("% x", hash.Sum(nil)) +} + +func run(t *testing.T, name string, args ...string) { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + t.Fatal(err) + } +} + +func TestGolden(t *testing.T) { + // Compute the original checksum. + goldenSum := sum(t, "my_test/test.pb.go") + // Run the proto compiler. + run(t, "protoc", "--go_out="+os.TempDir(), "my_test/test.proto") + newFile := filepath.Join(os.TempDir(), "my_test/test.pb.go") + defer os.Remove(newFile) + // Compute the new checksum. + newSum := sum(t, newFile) + // Verify + if newSum != goldenSum { + run(t, "diff", "-u", "my_test/test.pb.go", newFile) + t.Fatal("Code generated by protoc-gen-go has changed; update test.pb.go") + } +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/grpc.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/grpc.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/grpc.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/grpc.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,59 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2015 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package grpc.testing; + +message SimpleRequest { +} + +message SimpleResponse { +} + +message StreamMsg { +} + +message StreamMsg2 { +} + +service Test { + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // This RPC streams from the server only. + rpc Downstream(SimpleRequest) returns (stream StreamMsg); + + // This RPC streams from the client. + rpc Upstream(stream StreamMsg) returns (SimpleResponse); + + // This one streams in both directions. + rpc Bidi(stream StreamMsg) returns (stream StreamMsg2); +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp2.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp2.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp2.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp2.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,43 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package imp; + +message PubliclyImportedMessage { + optional int64 field = 1; +} + +enum PubliclyImportedEnum { + GLASSES = 1; + HAIR = 2; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp3.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp3.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp3.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp3.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,38 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package imp; + +message ForeignImportedMessage { + optional string tuber = 1; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.pb.go.golden charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.pb.go.golden --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.pb.go.golden 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.pb.go.golden 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,113 @@ +// Code generated by protoc-gen-go. +// source: imp.proto +// DO NOT EDIT! + +package imp + +import proto "github.com/golang/protobuf/proto" +import "math" +import "os" +import imp1 "imp2.pb" + +// Reference proto & math imports to suppress error if they are not otherwise used. +var _ = proto.GetString +var _ = math.Inf + +// Types from public import imp2.proto +type PubliclyImportedMessage imp1.PubliclyImportedMessage + +func (this *PubliclyImportedMessage) Reset() { (*imp1.PubliclyImportedMessage)(this).Reset() } +func (this *PubliclyImportedMessage) String() string { + return (*imp1.PubliclyImportedMessage)(this).String() +} + +// PubliclyImportedMessage from public import imp.proto + +type ImportedMessage_Owner int32 + +const ( + ImportedMessage_DAVE ImportedMessage_Owner = 1 + ImportedMessage_MIKE ImportedMessage_Owner = 2 +) + +var ImportedMessage_Owner_name = map[int32]string{ + 1: "DAVE", + 2: "MIKE", +} +var ImportedMessage_Owner_value = map[string]int32{ + "DAVE": 1, + "MIKE": 2, +} + +// NewImportedMessage_Owner is deprecated. Use x.Enum() instead. +func NewImportedMessage_Owner(x ImportedMessage_Owner) *ImportedMessage_Owner { + e := ImportedMessage_Owner(x) + return &e +} +func (x ImportedMessage_Owner) Enum() *ImportedMessage_Owner { + p := new(ImportedMessage_Owner) + *p = x + return p +} +func (x ImportedMessage_Owner) String() string { + return proto.EnumName(ImportedMessage_Owner_name, int32(x)) +} + +type ImportedMessage struct { + Field *int64 `protobuf:"varint,1,req,name=field" json:"field,omitempty"` + XXX_extensions map[int32][]byte `json:",omitempty"` + XXX_unrecognized []byte `json:",omitempty"` +} + +func (this *ImportedMessage) Reset() { *this = ImportedMessage{} } +func (this *ImportedMessage) String() string { return proto.CompactTextString(this) } + +var extRange_ImportedMessage = []proto.ExtensionRange{ + proto.ExtensionRange{90, 100}, +} + +func (*ImportedMessage) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_ImportedMessage +} +func (this *ImportedMessage) ExtensionMap() map[int32][]byte { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32][]byte) + } + return this.XXX_extensions +} + +type ImportedExtendable struct { + XXX_extensions map[int32][]byte `json:",omitempty"` + XXX_unrecognized []byte `json:",omitempty"` +} + +func (this *ImportedExtendable) Reset() { *this = ImportedExtendable{} } +func (this *ImportedExtendable) String() string { return proto.CompactTextString(this) } + +func (this *ImportedExtendable) Marshal() ([]byte, error) { + return proto.MarshalMessageSet(this.ExtensionMap()) +} +func (this *ImportedExtendable) Unmarshal(buf []byte) error { + return proto.UnmarshalMessageSet(buf, this.ExtensionMap()) +} +// ensure ImportedExtendable satisfies proto.Marshaler and proto.Unmarshaler +var _ proto.Marshaler = (*ImportedExtendable)(nil) +var _ proto.Unmarshaler = (*ImportedExtendable)(nil) + +var extRange_ImportedExtendable = []proto.ExtensionRange{ + proto.ExtensionRange{100, 536870911}, +} + +func (*ImportedExtendable) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_ImportedExtendable +} +func (this *ImportedExtendable) ExtensionMap() map[int32][]byte { + if this.XXX_extensions == nil { + this.XXX_extensions = make(map[int32][]byte) + } + return this.XXX_extensions +} + +func init() { + proto.RegisterEnum("imp.ImportedMessage_Owner", ImportedMessage_Owner_name, ImportedMessage_Owner_value) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/imp.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,65 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package imp; + +import "imp2.proto"; +import "imp3.proto"; + +message ImportedMessage { + required int64 field = 1; + + // The forwarded getters for these fields are fiddly to get right. + optional ImportedMessage2 local_msg = 2; + optional ForeignImportedMessage foreign_msg = 3; // in imp3.proto + optional Owner enum_field = 4; + + repeated string name = 5; + repeated Owner boss = 6; + repeated ImportedMessage2 memo = 7; + + enum Owner { + DAVE = 1; + MIKE = 2; + } + + extensions 90 to 100; +} + +message ImportedMessage2 { +} + +message ImportedExtendable { + option message_set_wire_format = true; + extensions 100 to max; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/main_test.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/main_test.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/main_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/main_test.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,46 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// A simple binary to link together the protocol buffers in this test. + +package testdata + +import ( + "testing" + + multipb "./multi" + mytestpb "./my_test" +) + +func TestLink(t *testing.T) { + _ = &multipb.Multi1{} + _ = &mytestpb.Request{} +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/Makefile charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/Makefile --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/Makefile 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,66 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# https://github.com/golang/protobuf +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +all: + @echo run make test + +include ../../Make.protobuf + +test: golden testbuild + +#test: golden testbuild extension_test +# ./extension_test +# @echo PASS + +golden: + make -B my_test/test.pb.go + diff -w my_test/test.pb.go my_test/test.pb.go.golden + +nuke: clean + +testbuild: buildprotos + go test + +buildprotos: + # Invoke protoc once to generate three independent .pb.go files in the same package. + protoc --go_out=. multi/multi{1,2,3}.proto + +#extension_test: extension_test.$O +# $(LD) -L. -o $@ $< + +#multi.a: multi3.pb.$O multi2.pb.$O multi1.pb.$O +# rm -f multi.a +# $(QUOTED_GOBIN)/gopack grc $@ $< + +#test.pb.go: imp.pb.go +#multi1.pb.go: multi2.pb.go multi3.pb.go +#main.$O: imp.pb.$O test.pb.$O multi.a +#extension_test.$O: extension_base.pb.$O extension_extra.pb.$O extension_user.pb.$O diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi1.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi1.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi1.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi1.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,44 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +import "multi/multi2.proto"; +import "multi/multi3.proto"; + +package multitest; + +message Multi1 { + required Multi2 multi2 = 1; + optional Multi2.Color color = 2; + optional Multi3.HatType hat_type = 3; +} + diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi2.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi2.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi2.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi2.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,46 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package multitest; + +message Multi2 { + required int32 required_value = 1; + + enum Color { + BLUE = 1; + GREEN = 2; + RED = 3; + }; + optional Color color = 2; +} + diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi3.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi3.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi3.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/multi/multi3.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,43 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +package multitest; + +message Multi3 { + enum HatType { + FEDORA = 1; + FEZ = 2; + }; + optional HatType hat_type = 1; +} + diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,487 @@ +// Code generated by protoc-gen-go. +// source: my_test/test.proto +// DO NOT EDIT! + +/* +Package my_test is a generated protocol buffer package. + +This package holds interesting messages. + +It is generated from these files: + my_test/test.proto + +It has these top-level messages: + Request + Reply + OtherBase + ReplyExtensions + OtherReplyExtensions + OldReply +*/ +package my_test + +import proto "github.com/golang/protobuf/proto" +import math "math" + +// discarding unused import multitest2 "multi" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +type HatType int32 + +const ( + // deliberately skipping 0 + HatType_FEDORA HatType = 1 + HatType_FEZ HatType = 2 +) + +var HatType_name = map[int32]string{ + 1: "FEDORA", + 2: "FEZ", +} +var HatType_value = map[string]int32{ + "FEDORA": 1, + "FEZ": 2, +} + +func (x HatType) Enum() *HatType { + p := new(HatType) + *p = x + return p +} +func (x HatType) String() string { + return proto.EnumName(HatType_name, int32(x)) +} +func (x *HatType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(HatType_value, data, "HatType") + if err != nil { + return err + } + *x = HatType(value) + return nil +} + +// This enum represents days of the week. +type Days int32 + +const ( + Days_MONDAY Days = 1 + Days_TUESDAY Days = 2 + Days_LUNDI Days = 1 +) + +var Days_name = map[int32]string{ + 1: "MONDAY", + 2: "TUESDAY", + // Duplicate value: 1: "LUNDI", +} +var Days_value = map[string]int32{ + "MONDAY": 1, + "TUESDAY": 2, + "LUNDI": 1, +} + +func (x Days) Enum() *Days { + p := new(Days) + *p = x + return p +} +func (x Days) String() string { + return proto.EnumName(Days_name, int32(x)) +} +func (x *Days) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Days_value, data, "Days") + if err != nil { + return err + } + *x = Days(value) + return nil +} + +type Request_Color int32 + +const ( + Request_RED Request_Color = 0 + Request_GREEN Request_Color = 1 + Request_BLUE Request_Color = 2 +) + +var Request_Color_name = map[int32]string{ + 0: "RED", + 1: "GREEN", + 2: "BLUE", +} +var Request_Color_value = map[string]int32{ + "RED": 0, + "GREEN": 1, + "BLUE": 2, +} + +func (x Request_Color) Enum() *Request_Color { + p := new(Request_Color) + *p = x + return p +} +func (x Request_Color) String() string { + return proto.EnumName(Request_Color_name, int32(x)) +} +func (x *Request_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Request_Color_value, data, "Request_Color") + if err != nil { + return err + } + *x = Request_Color(value) + return nil +} + +type Reply_Entry_Game int32 + +const ( + Reply_Entry_FOOTBALL Reply_Entry_Game = 1 + Reply_Entry_TENNIS Reply_Entry_Game = 2 +) + +var Reply_Entry_Game_name = map[int32]string{ + 1: "FOOTBALL", + 2: "TENNIS", +} +var Reply_Entry_Game_value = map[string]int32{ + "FOOTBALL": 1, + "TENNIS": 2, +} + +func (x Reply_Entry_Game) Enum() *Reply_Entry_Game { + p := new(Reply_Entry_Game) + *p = x + return p +} +func (x Reply_Entry_Game) String() string { + return proto.EnumName(Reply_Entry_Game_name, int32(x)) +} +func (x *Reply_Entry_Game) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Reply_Entry_Game_value, data, "Reply_Entry_Game") + if err != nil { + return err + } + *x = Reply_Entry_Game(value) + return nil +} + +// This is a message that might be sent somewhere. +type Request struct { + Key []int64 `protobuf:"varint,1,rep,name=key" json:"key,omitempty"` + // optional imp.ImportedMessage imported_message = 2; + Hue *Request_Color `protobuf:"varint,3,opt,name=hue,enum=my.test.Request_Color" json:"hue,omitempty"` + Hat *HatType `protobuf:"varint,4,opt,name=hat,enum=my.test.HatType,def=1" json:"hat,omitempty"` + // optional imp.ImportedMessage.Owner owner = 6; + Deadline *float32 `protobuf:"fixed32,7,opt,name=deadline,def=inf" json:"deadline,omitempty"` + Somegroup *Request_SomeGroup `protobuf:"group,8,opt,name=SomeGroup" json:"somegroup,omitempty"` + // This is a map field. It will generate map[int32]string. + NameMapping map[int32]string `protobuf:"bytes,14,rep,name=name_mapping" json:"name_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // This is a map field whose value type is a message. + MsgMapping map[int64]*Reply `protobuf:"bytes,15,rep,name=msg_mapping" json:"msg_mapping,omitempty" protobuf_key:"zigzag64,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Reset_ *int32 `protobuf:"varint,12,opt,name=reset" json:"reset,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} + +const Default_Request_Hat HatType = HatType_FEDORA + +var Default_Request_Deadline float32 = float32(math.Inf(1)) + +func (m *Request) GetKey() []int64 { + if m != nil { + return m.Key + } + return nil +} + +func (m *Request) GetHue() Request_Color { + if m != nil && m.Hue != nil { + return *m.Hue + } + return Request_RED +} + +func (m *Request) GetHat() HatType { + if m != nil && m.Hat != nil { + return *m.Hat + } + return Default_Request_Hat +} + +func (m *Request) GetDeadline() float32 { + if m != nil && m.Deadline != nil { + return *m.Deadline + } + return Default_Request_Deadline +} + +func (m *Request) GetSomegroup() *Request_SomeGroup { + if m != nil { + return m.Somegroup + } + return nil +} + +func (m *Request) GetNameMapping() map[int32]string { + if m != nil { + return m.NameMapping + } + return nil +} + +func (m *Request) GetMsgMapping() map[int64]*Reply { + if m != nil { + return m.MsgMapping + } + return nil +} + +func (m *Request) GetReset_() int32 { + if m != nil && m.Reset_ != nil { + return *m.Reset_ + } + return 0 +} + +type Request_SomeGroup struct { + GroupField *int32 `protobuf:"varint,9,opt,name=group_field" json:"group_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Request_SomeGroup) Reset() { *m = Request_SomeGroup{} } +func (m *Request_SomeGroup) String() string { return proto.CompactTextString(m) } +func (*Request_SomeGroup) ProtoMessage() {} + +func (m *Request_SomeGroup) GetGroupField() int32 { + if m != nil && m.GroupField != nil { + return *m.GroupField + } + return 0 +} + +type Reply struct { + Found []*Reply_Entry `protobuf:"bytes,1,rep,name=found" json:"found,omitempty"` + CompactKeys []int32 `protobuf:"varint,2,rep,packed,name=compact_keys" json:"compact_keys,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Reply) Reset() { *m = Reply{} } +func (m *Reply) String() string { return proto.CompactTextString(m) } +func (*Reply) ProtoMessage() {} + +var extRange_Reply = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*Reply) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_Reply +} +func (m *Reply) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *Reply) GetFound() []*Reply_Entry { + if m != nil { + return m.Found + } + return nil +} + +func (m *Reply) GetCompactKeys() []int32 { + if m != nil { + return m.CompactKeys + } + return nil +} + +type Reply_Entry struct { + KeyThatNeeds_1234Camel_CasIng *int64 `protobuf:"varint,1,req,name=key_that_needs_1234camel_CasIng" json:"key_that_needs_1234camel_CasIng,omitempty"` + Value *int64 `protobuf:"varint,2,opt,name=value,def=7" json:"value,omitempty"` + XMyFieldName_2 *int64 `protobuf:"varint,3,opt,name=_my_field_name_2" json:"_my_field_name_2,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Reply_Entry) Reset() { *m = Reply_Entry{} } +func (m *Reply_Entry) String() string { return proto.CompactTextString(m) } +func (*Reply_Entry) ProtoMessage() {} + +const Default_Reply_Entry_Value int64 = 7 + +func (m *Reply_Entry) GetKeyThatNeeds_1234Camel_CasIng() int64 { + if m != nil && m.KeyThatNeeds_1234Camel_CasIng != nil { + return *m.KeyThatNeeds_1234Camel_CasIng + } + return 0 +} + +func (m *Reply_Entry) GetValue() int64 { + if m != nil && m.Value != nil { + return *m.Value + } + return Default_Reply_Entry_Value +} + +func (m *Reply_Entry) GetXMyFieldName_2() int64 { + if m != nil && m.XMyFieldName_2 != nil { + return *m.XMyFieldName_2 + } + return 0 +} + +type OtherBase struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OtherBase) Reset() { *m = OtherBase{} } +func (m *OtherBase) String() string { return proto.CompactTextString(m) } +func (*OtherBase) ProtoMessage() {} + +var extRange_OtherBase = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*OtherBase) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_OtherBase +} +func (m *OtherBase) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *OtherBase) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +type ReplyExtensions struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *ReplyExtensions) Reset() { *m = ReplyExtensions{} } +func (m *ReplyExtensions) String() string { return proto.CompactTextString(m) } +func (*ReplyExtensions) ProtoMessage() {} + +var E_ReplyExtensions_Time = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*float64)(nil), + Field: 101, + Name: "my.test.ReplyExtensions.time", + Tag: "fixed64,101,opt,name=time", +} + +var E_ReplyExtensions_Carrot = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*ReplyExtensions)(nil), + Field: 105, + Name: "my.test.ReplyExtensions.carrot", + Tag: "bytes,105,opt,name=carrot", +} + +var E_ReplyExtensions_Donut = &proto.ExtensionDesc{ + ExtendedType: (*OtherBase)(nil), + ExtensionType: (*ReplyExtensions)(nil), + Field: 101, + Name: "my.test.ReplyExtensions.donut", + Tag: "bytes,101,opt,name=donut", +} + +type OtherReplyExtensions struct { + Key *int32 `protobuf:"varint,1,opt,name=key" json:"key,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OtherReplyExtensions) Reset() { *m = OtherReplyExtensions{} } +func (m *OtherReplyExtensions) String() string { return proto.CompactTextString(m) } +func (*OtherReplyExtensions) ProtoMessage() {} + +func (m *OtherReplyExtensions) GetKey() int32 { + if m != nil && m.Key != nil { + return *m.Key + } + return 0 +} + +type OldReply struct { + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OldReply) Reset() { *m = OldReply{} } +func (m *OldReply) String() string { return proto.CompactTextString(m) } +func (*OldReply) ProtoMessage() {} + +func (m *OldReply) Marshal() ([]byte, error) { + return proto.MarshalMessageSet(m.ExtensionMap()) +} +func (m *OldReply) Unmarshal(buf []byte) error { + return proto.UnmarshalMessageSet(buf, m.ExtensionMap()) +} +func (m *OldReply) MarshalJSON() ([]byte, error) { + return proto.MarshalMessageSetJSON(m.XXX_extensions) +} +func (m *OldReply) UnmarshalJSON(buf []byte) error { + return proto.UnmarshalMessageSetJSON(buf, m.XXX_extensions) +} + +// ensure OldReply satisfies proto.Marshaler and proto.Unmarshaler +var _ proto.Marshaler = (*OldReply)(nil) +var _ proto.Unmarshaler = (*OldReply)(nil) + +var extRange_OldReply = []proto.ExtensionRange{ + {100, 2147483646}, +} + +func (*OldReply) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_OldReply +} +func (m *OldReply) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +var E_Tag = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*string)(nil), + Field: 103, + Name: "my.test.tag", + Tag: "bytes,103,opt,name=tag", +} + +var E_Donut = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*OtherReplyExtensions)(nil), + Field: 106, + Name: "my.test.donut", + Tag: "bytes,106,opt,name=donut", +} + +func init() { + proto.RegisterEnum("my.test.HatType", HatType_name, HatType_value) + proto.RegisterEnum("my.test.Days", Days_name, Days_value) + proto.RegisterEnum("my.test.Request_Color", Request_Color_name, Request_Color_value) + proto.RegisterEnum("my.test.Reply_Entry_Game", Reply_Entry_Game_name, Reply_Entry_Game_value) + proto.RegisterExtension(E_ReplyExtensions_Time) + proto.RegisterExtension(E_ReplyExtensions_Carrot) + proto.RegisterExtension(E_ReplyExtensions_Donut) + proto.RegisterExtension(E_Tag) + proto.RegisterExtension(E_Donut) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go.golden charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go.golden --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go.golden 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go.golden 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,487 @@ +// Code generated by protoc-gen-go. +// source: my_test/test.proto +// DO NOT EDIT! + +/* +Package my_test is a generated protocol buffer package. + +This package holds interesting messages. + +It is generated from these files: + my_test/test.proto + +It has these top-level messages: + Request + Reply + OtherBase + ReplyExtensions + OtherReplyExtensions + OldReply +*/ +package my_test + +import proto "github.com/golang/protobuf/proto" +import math "math" + +// discarding unused import multitest2 "multi" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +type HatType int32 + +const ( + // deliberately skipping 0 + HatType_FEDORA HatType = 1 + HatType_FEZ HatType = 2 +) + +var HatType_name = map[int32]string{ + 1: "FEDORA", + 2: "FEZ", +} +var HatType_value = map[string]int32{ + "FEDORA": 1, + "FEZ": 2, +} + +func (x HatType) Enum() *HatType { + p := new(HatType) + *p = x + return p +} +func (x HatType) String() string { + return proto.EnumName(HatType_name, int32(x)) +} +func (x *HatType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(HatType_value, data, "HatType") + if err != nil { + return err + } + *x = HatType(value) + return nil +} + +// This enum represents days of the week. +type Days int32 + +const ( + Days_MONDAY Days = 1 + Days_TUESDAY Days = 2 + Days_LUNDI Days = 1 +) + +var Days_name = map[int32]string{ + 1: "MONDAY", + 2: "TUESDAY", + // Duplicate value: 1: "LUNDI", +} +var Days_value = map[string]int32{ + "MONDAY": 1, + "TUESDAY": 2, + "LUNDI": 1, +} + +func (x Days) Enum() *Days { + p := new(Days) + *p = x + return p +} +func (x Days) String() string { + return proto.EnumName(Days_name, int32(x)) +} +func (x *Days) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Days_value, data, "Days") + if err != nil { + return err + } + *x = Days(value) + return nil +} + +type Request_Color int32 + +const ( + Request_RED Request_Color = 0 + Request_GREEN Request_Color = 1 + Request_BLUE Request_Color = 2 +) + +var Request_Color_name = map[int32]string{ + 0: "RED", + 1: "GREEN", + 2: "BLUE", +} +var Request_Color_value = map[string]int32{ + "RED": 0, + "GREEN": 1, + "BLUE": 2, +} + +func (x Request_Color) Enum() *Request_Color { + p := new(Request_Color) + *p = x + return p +} +func (x Request_Color) String() string { + return proto.EnumName(Request_Color_name, int32(x)) +} +func (x *Request_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Request_Color_value, data, "Request_Color") + if err != nil { + return err + } + *x = Request_Color(value) + return nil +} + +type Reply_Entry_Game int32 + +const ( + Reply_Entry_FOOTBALL Reply_Entry_Game = 1 + Reply_Entry_TENNIS Reply_Entry_Game = 2 +) + +var Reply_Entry_Game_name = map[int32]string{ + 1: "FOOTBALL", + 2: "TENNIS", +} +var Reply_Entry_Game_value = map[string]int32{ + "FOOTBALL": 1, + "TENNIS": 2, +} + +func (x Reply_Entry_Game) Enum() *Reply_Entry_Game { + p := new(Reply_Entry_Game) + *p = x + return p +} +func (x Reply_Entry_Game) String() string { + return proto.EnumName(Reply_Entry_Game_name, int32(x)) +} +func (x *Reply_Entry_Game) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Reply_Entry_Game_value, data, "Reply_Entry_Game") + if err != nil { + return err + } + *x = Reply_Entry_Game(value) + return nil +} + +// This is a message that might be sent somewhere. +type Request struct { + Key []int64 `protobuf:"varint,1,rep,name=key" json:"key,omitempty"` + // optional imp.ImportedMessage imported_message = 2; + Hue *Request_Color `protobuf:"varint,3,opt,name=hue,enum=my.test.Request_Color" json:"hue,omitempty"` + Hat *HatType `protobuf:"varint,4,opt,name=hat,enum=my.test.HatType,def=1" json:"hat,omitempty"` + // optional imp.ImportedMessage.Owner owner = 6; + Deadline *float32 `protobuf:"fixed32,7,opt,name=deadline,def=inf" json:"deadline,omitempty"` + Somegroup *Request_SomeGroup `protobuf:"group,8,opt,name=SomeGroup" json:"somegroup,omitempty"` + // This is a map field. It will generate map[int32]string. + NameMapping map[int32]string `protobuf:"bytes,14,rep,name=name_mapping" json:"name_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // This is a map field whose value type is a message. + MsgMapping map[int64]*Reply `protobuf:"bytes,15,rep,name=msg_mapping" json:"msg_mapping,omitempty" protobuf_key:"zigzag64,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Reset_ *int32 `protobuf:"varint,12,opt,name=reset" json:"reset,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} + +const Default_Request_Hat HatType = HatType_FEDORA + +var Default_Request_Deadline float32 = float32(math.Inf(1)) + +func (m *Request) GetKey() []int64 { + if m != nil { + return m.Key + } + return nil +} + +func (m *Request) GetHue() Request_Color { + if m != nil && m.Hue != nil { + return *m.Hue + } + return Request_RED +} + +func (m *Request) GetHat() HatType { + if m != nil && m.Hat != nil { + return *m.Hat + } + return Default_Request_Hat +} + +func (m *Request) GetDeadline() float32 { + if m != nil && m.Deadline != nil { + return *m.Deadline + } + return Default_Request_Deadline +} + +func (m *Request) GetSomegroup() *Request_SomeGroup { + if m != nil { + return m.Somegroup + } + return nil +} + +func (m *Request) GetNameMapping() map[int32]string { + if m != nil { + return m.NameMapping + } + return nil +} + +func (m *Request) GetMsgMapping() map[int64]*Reply { + if m != nil { + return m.MsgMapping + } + return nil +} + +func (m *Request) GetReset_() int32 { + if m != nil && m.Reset_ != nil { + return *m.Reset_ + } + return 0 +} + +type Request_SomeGroup struct { + GroupField *int32 `protobuf:"varint,9,opt,name=group_field" json:"group_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Request_SomeGroup) Reset() { *m = Request_SomeGroup{} } +func (m *Request_SomeGroup) String() string { return proto.CompactTextString(m) } +func (*Request_SomeGroup) ProtoMessage() {} + +func (m *Request_SomeGroup) GetGroupField() int32 { + if m != nil && m.GroupField != nil { + return *m.GroupField + } + return 0 +} + +type Reply struct { + Found []*Reply_Entry `protobuf:"bytes,1,rep,name=found" json:"found,omitempty"` + CompactKeys []int32 `protobuf:"varint,2,rep,packed,name=compact_keys" json:"compact_keys,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Reply) Reset() { *m = Reply{} } +func (m *Reply) String() string { return proto.CompactTextString(m) } +func (*Reply) ProtoMessage() {} + +var extRange_Reply = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*Reply) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_Reply +} +func (m *Reply) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *Reply) GetFound() []*Reply_Entry { + if m != nil { + return m.Found + } + return nil +} + +func (m *Reply) GetCompactKeys() []int32 { + if m != nil { + return m.CompactKeys + } + return nil +} + +type Reply_Entry struct { + KeyThatNeeds_1234Camel_CasIng *int64 `protobuf:"varint,1,req,name=key_that_needs_1234camel_CasIng" json:"key_that_needs_1234camel_CasIng,omitempty"` + Value *int64 `protobuf:"varint,2,opt,name=value,def=7" json:"value,omitempty"` + XMyFieldName_2 *int64 `protobuf:"varint,3,opt,name=_my_field_name_2" json:"_my_field_name_2,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Reply_Entry) Reset() { *m = Reply_Entry{} } +func (m *Reply_Entry) String() string { return proto.CompactTextString(m) } +func (*Reply_Entry) ProtoMessage() {} + +const Default_Reply_Entry_Value int64 = 7 + +func (m *Reply_Entry) GetKeyThatNeeds_1234Camel_CasIng() int64 { + if m != nil && m.KeyThatNeeds_1234Camel_CasIng != nil { + return *m.KeyThatNeeds_1234Camel_CasIng + } + return 0 +} + +func (m *Reply_Entry) GetValue() int64 { + if m != nil && m.Value != nil { + return *m.Value + } + return Default_Reply_Entry_Value +} + +func (m *Reply_Entry) GetXMyFieldName_2() int64 { + if m != nil && m.XMyFieldName_2 != nil { + return *m.XMyFieldName_2 + } + return 0 +} + +type OtherBase struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OtherBase) Reset() { *m = OtherBase{} } +func (m *OtherBase) String() string { return proto.CompactTextString(m) } +func (*OtherBase) ProtoMessage() {} + +var extRange_OtherBase = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*OtherBase) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_OtherBase +} +func (m *OtherBase) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *OtherBase) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +type ReplyExtensions struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *ReplyExtensions) Reset() { *m = ReplyExtensions{} } +func (m *ReplyExtensions) String() string { return proto.CompactTextString(m) } +func (*ReplyExtensions) ProtoMessage() {} + +var E_ReplyExtensions_Time = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*float64)(nil), + Field: 101, + Name: "my.test.ReplyExtensions.time", + Tag: "fixed64,101,opt,name=time", +} + +var E_ReplyExtensions_Carrot = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*ReplyExtensions)(nil), + Field: 105, + Name: "my.test.ReplyExtensions.carrot", + Tag: "bytes,105,opt,name=carrot", +} + +var E_ReplyExtensions_Donut = &proto.ExtensionDesc{ + ExtendedType: (*OtherBase)(nil), + ExtensionType: (*ReplyExtensions)(nil), + Field: 101, + Name: "my.test.ReplyExtensions.donut", + Tag: "bytes,101,opt,name=donut", +} + +type OtherReplyExtensions struct { + Key *int32 `protobuf:"varint,1,opt,name=key" json:"key,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OtherReplyExtensions) Reset() { *m = OtherReplyExtensions{} } +func (m *OtherReplyExtensions) String() string { return proto.CompactTextString(m) } +func (*OtherReplyExtensions) ProtoMessage() {} + +func (m *OtherReplyExtensions) GetKey() int32 { + if m != nil && m.Key != nil { + return *m.Key + } + return 0 +} + +type OldReply struct { + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OldReply) Reset() { *m = OldReply{} } +func (m *OldReply) String() string { return proto.CompactTextString(m) } +func (*OldReply) ProtoMessage() {} + +func (m *OldReply) Marshal() ([]byte, error) { + return proto.MarshalMessageSet(m.ExtensionMap()) +} +func (m *OldReply) Unmarshal(buf []byte) error { + return proto.UnmarshalMessageSet(buf, m.ExtensionMap()) +} +func (m *OldReply) MarshalJSON() ([]byte, error) { + return proto.MarshalMessageSetJSON(m.XXX_extensions) +} +func (m *OldReply) UnmarshalJSON(buf []byte) error { + return proto.UnmarshalMessageSetJSON(buf, m.XXX_extensions) +} + +// ensure OldReply satisfies proto.Marshaler and proto.Unmarshaler +var _ proto.Marshaler = (*OldReply)(nil) +var _ proto.Unmarshaler = (*OldReply)(nil) + +var extRange_OldReply = []proto.ExtensionRange{ + {100, 2147483646}, +} + +func (*OldReply) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_OldReply +} +func (m *OldReply) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +var E_Tag = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*string)(nil), + Field: 103, + Name: "my.test.tag", + Tag: "bytes,103,opt,name=tag", +} + +var E_Donut = &proto.ExtensionDesc{ + ExtendedType: (*Reply)(nil), + ExtensionType: (*OtherReplyExtensions)(nil), + Field: 106, + Name: "my.test.donut", + Tag: "bytes,106,opt,name=donut", +} + +func init() { + proto.RegisterEnum("my.test.HatType", HatType_name, HatType_value) + proto.RegisterEnum("my.test.Days", Days_name, Days_value) + proto.RegisterEnum("my.test.Request_Color", Request_Color_name, Request_Color_value) + proto.RegisterEnum("my.test.Reply_Entry_Game", Reply_Entry_Game_name, Reply_Entry_Game_value) + proto.RegisterExtension(E_ReplyExtensions_Time) + proto.RegisterExtension(E_ReplyExtensions_Carrot) + proto.RegisterExtension(E_ReplyExtensions_Donut) + proto.RegisterExtension(E_Tag) + proto.RegisterExtension(E_Donut) +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/my_test/test.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,132 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +// This package holds interesting messages. +package my.test; // dotted package name + +//import "imp.proto"; +import "multi/multi1.proto"; // unused import + +enum HatType { + // deliberately skipping 0 + FEDORA = 1; + FEZ = 2; +} + +// This enum represents days of the week. +enum Days { + option allow_alias = true; + + MONDAY = 1; + TUESDAY = 2; + LUNDI = 1; // same value as MONDAY +} + +// This is a message that might be sent somewhere. +message Request { + enum Color { + RED = 0; + GREEN = 1; + BLUE = 2; + } + repeated int64 key = 1; +// optional imp.ImportedMessage imported_message = 2; + optional Color hue = 3; // no default + optional HatType hat = 4 [default=FEDORA]; +// optional imp.ImportedMessage.Owner owner = 6; + optional float deadline = 7 [default=inf]; + optional group SomeGroup = 8 { + optional int32 group_field = 9; + } + + // These foreign types are in imp2.proto, + // which is publicly imported by imp.proto. +// optional imp.PubliclyImportedMessage pub = 10; +// optional imp.PubliclyImportedEnum pub_enum = 13 [default=HAIR]; + + + // This is a map field. It will generate map[int32]string. + map name_mapping = 14; + // This is a map field whose value type is a message. + map msg_mapping = 15; + + optional int32 reset = 12; +} + +message Reply { + message Entry { + required int64 key_that_needs_1234camel_CasIng = 1; + optional int64 value = 2 [default=7]; + optional int64 _my_field_name_2 = 3; + enum Game { + FOOTBALL = 1; + TENNIS = 2; + } + } + repeated Entry found = 1; + repeated int32 compact_keys = 2 [packed=true]; + extensions 100 to max; +} + +message OtherBase { + optional string name = 1; + extensions 100 to max; +} + +message ReplyExtensions { + extend Reply { + optional double time = 101; + optional ReplyExtensions carrot = 105; + } + extend OtherBase { + optional ReplyExtensions donut = 101; + } +} + +message OtherReplyExtensions { + optional int32 key = 1; +} + +// top-level extension +extend Reply { + optional string tag = 103; + optional OtherReplyExtensions donut = 106; +// optional imp.ImportedMessage elephant = 107; // extend with message from another file. +} + +message OldReply { + // Extensions will be encoded in MessageSet wire format. + option message_set_wire_format = true; + extensions 100 to max; +} + diff -Nru charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/proto3.proto charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/proto3.proto --- charm-2.1.1/src/github.com/golang/protobuf/protoc-gen-go/testdata/proto3.proto 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/protoc-gen-go/testdata/proto3.proto 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,52 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package proto3; + +message Request { + enum Flavour { + SWEET = 0; + SOUR = 1; + UMAMI = 2; + GOPHERLICIOUS = 3; + } + string name = 1; // "optional" may be omitted + repeated int64 key = 2; + optional Flavour taste = 3; + Book book = 4; +} + +message Book { + string title = 1; + bytes raw_data = 2; +} diff -Nru charm-2.1.1/src/github.com/golang/protobuf/README charm-2.2.0/src/github.com/golang/protobuf/README --- charm-2.1.1/src/github.com/golang/protobuf/README 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/golang/protobuf/README 2016-09-03 01:24:55.000000000 +0000 @@ -0,0 +1,148 @@ +Go support for Protocol Buffers - Google's data interchange format +Copyright 2010 The Go Authors. +https://github.com/golang/protobuf + +This package and the code it generates requires at least Go 1.2. + +This software implements Go bindings for protocol buffers. For +information about protocol buffers themselves, see + https://developers.google.com/protocol-buffers/ +To use this software, you must first install the standard C++ +implementation of protocol buffers from + https://developers.google.com/protocol-buffers/ +And of course you must also install the Go compiler and tools from + https://golang.org/ +See + https://golang.org/doc/install +for details or, if you are using gccgo, follow the instructions at + https://golang.org/doc/install/gccgo + +This software has two parts: a 'protocol compiler plugin' that +generates Go source files that, once compiled, can access and manage +protocol buffers; and a library that implements run-time support for +encoding (marshaling), decoding (unmarshaling), and accessing protocol +buffers. + +There is no support for RPC in Go using protocol buffers. It may come +once a standard RPC protocol develops for protobufs. + +There are no insertion points in the plugin. + +To install this code: + +The simplest way is to run go get. + + # Grab the code from the repository and install the proto package. + go get -u github.com/golang/protobuf/{proto,protoc-gen-go} + +The compiler plugin, protoc-gen-go, will be installed in $GOBIN, +defaulting to $GOPATH/bin. It must be in your $PATH for the protocol +compiler, protoc, to find it. + +Once the software is installed, there are two steps to using it. +First you must compile the protocol buffer definitions and then import +them, with the support library, into your program. + +To compile the protocol buffer definition, run protoc with the --go_out +parameter set to the directory you want to output the Go code to. + + protoc --go_out=. *.proto + +The generated files will be suffixed .pb.go. See the Test code below +for an example using such a file. + + +The package comment for the proto library contains text describing +the interface provided in Go for protocol buffers. Here is an edited +version. + +========== + +The proto package converts data structures to and from the +wire format of protocol buffers. It works in concert with the +Go source code generated for .proto files by the protocol compiler. + +A summary of the properties of the protocol buffer interface +for a protocol buffer variable v: + + - Names are turned from camel_case to CamelCase for export. + - There are no methods on v to set fields; just treat + them as structure fields. + - There are getters that return a field's value if set, + and return the field's default value if unset. + The getters work even if the receiver is a nil message. + - The zero value for a struct is its correct initialization state. + All desired fields must be set before marshaling. + - A Reset() method will restore a protobuf struct to its zero state. + - Non-repeated fields are pointers to the values; nil means unset. + That is, optional or required field int32 f becomes F *int32. + - Repeated fields are slices. + - Helper functions are available to aid the setting of fields. + Helpers for getting values are superseded by the + GetFoo methods and their use is deprecated. + msg.Foo = proto.String("hello") // set field + - Constants are defined to hold the default values of all fields that + have them. They have the form Default_StructName_FieldName. + Because the getter methods handle defaulted values, + direct use of these constants should be rare. + - Enums are given type names and maps from names to values. + Enum values are prefixed with the enum's type name. Enum types have + a String method, and a Enum method to assist in message construction. + - Nested groups and enums have type names prefixed with the name of + the surrounding message type. + - Extensions are given descriptor names that start with E_, + followed by an underscore-delimited list of the nested messages + that contain it (if any) followed by the CamelCased name of the + extension field itself. HasExtension, ClearExtension, GetExtension + and SetExtension are functions for manipulating extensions. + - Marshal and Unmarshal are functions to encode and decode the wire format. + +Consider file test.proto, containing + + package example; + + enum FOO { X = 17; }; + + message Test { + required string label = 1; + optional int32 type = 2 [default=77]; + repeated int64 reps = 3; + optional group OptionalGroup = 4 { + required string RequiredField = 5; + } + } + +To create and play with a Test object from the example package, + + package main + + import ( + "log" + + "github.com/golang/protobuf/proto" + "path/to/example" + ) + + func main() { + test := &example.Test { + Label: proto.String("hello"), + Type: proto.Int32(17), + Optionalgroup: &example.Test_OptionalGroup { + RequiredField: proto.String("good bye"), + }, + } + data, err := proto.Marshal(test) + if err != nil { + log.Fatal("marshaling error: ", err) + } + newTest := &example.Test{} + err = proto.Unmarshal(data, newTest) + if err != nil { + log.Fatal("unmarshaling error: ", err) + } + // Now test and newTest contain the same data. + if test.GetLabel() != newTest.GetLabel() { + log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) + } + // etc. + } diff -Nru charm-2.1.1/src/github.com/gosuri/uitable/README.md charm-2.2.0/src/github.com/gosuri/uitable/README.md --- charm-2.1.1/src/github.com/gosuri/uitable/README.md 2016-04-01 15:24:09.000000000 +0000 +++ charm-2.2.0/src/github.com/gosuri/uitable/README.md 2016-04-28 06:03:36.000000000 +0000 @@ -1,6 +1,6 @@ # uitable [![GoDoc](https://godoc.org/github.com/gosuri/uitable?status.svg)](https://godoc.org/github.com/gosuri/uitable) [![Build Status](https://travis-ci.org/gosuri/uitable.svg?branch=master)](https://travis-ci.org/gosuri/uitable) -uitable is a go library for representing data as tables for terminal applications. It provides primitives for sizing and wrapping columns to improve redability. +uitable is a go library for representing data as tables for terminal applications. It provides primitives for sizing and wrapping columns to improve readability. ## Example Usage diff -Nru charm-2.1.1/src/github.com/gosuri/uitable/table.go charm-2.2.0/src/github.com/gosuri/uitable/table.go --- charm-2.1.1/src/github.com/gosuri/uitable/table.go 2016-04-01 15:24:09.000000000 +0000 +++ charm-2.2.0/src/github.com/gosuri/uitable/table.go 2016-04-28 06:03:36.000000000 +0000 @@ -28,14 +28,16 @@ // Separator is the seperator for columns in the table. Default is "\t" Separator string - mtx *sync.RWMutex + mtx *sync.RWMutex + rightAlign map[int]bool } // New returns a new Table with default values func New() *Table { return &Table{ - Separator: Separator, - mtx: new(sync.RWMutex), + Separator: Separator, + mtx: new(sync.RWMutex), + rightAlign: map[int]bool{}, } } @@ -53,6 +55,12 @@ return []byte(t.String()) } +func (t *Table) RightAlign(col int) { + t.mtx.Lock() + t.rightAlign[col] = true + t.mtx.Unlock() +} + // String returns the string value of table func (t *Table) String() string { t.mtx.RLock() @@ -87,6 +95,7 @@ for i, cell := range row.Cells { cell.Width = colwidths[i] cell.Wrap = t.Wrap + cell.RightAlign = t.rightAlign[i] } lines = append(lines, row.String()) } @@ -158,6 +167,9 @@ // Wrap when true wraps the contents of the cell when the lenght exceeds the width Wrap bool + // RightAlign when true aligns contents to the right + RightAlign bool + // Data is the cell data Data interface{} } @@ -180,11 +192,12 @@ return strutil.PadLeft(" ", int(c.Width), ' ') } s := fmt.Sprintf("%v", c.Data) - switch { - case c.Width > 0 && c.Wrap: - return wordwrap.WrapString(s, c.Width) - case c.Width > 0: - return strutil.Resize(s, c.Width) + if c.Width > 0 { + if c.Wrap && uint(len(s)) > c.Width { + return wordwrap.WrapString(s, c.Width) + } else { + return strutil.Resize(s, c.Width, c.RightAlign) + } } return s } diff -Nru charm-2.1.1/src/github.com/gosuri/uitable/table_test.go charm-2.2.0/src/github.com/gosuri/uitable/table_test.go --- charm-2.1.1/src/github.com/gosuri/uitable/table_test.go 2016-04-01 15:24:09.000000000 +0000 +++ charm-2.2.0/src/github.com/gosuri/uitable/table_test.go 2016-04-28 06:03:36.000000000 +0000 @@ -45,6 +45,25 @@ } } +func TestAlign(t *testing.T) { + table := New() + table.AddRow("foo", "bar baz") + table.Rows = []*Row{{ + Separator: "\t", + Cells: []*Cell{ + {Data: "foo", Width: 5, Wrap: true}, + {Data: "bar baz", Width: 10, Wrap: true}, + }, + }} + table.RightAlign(1) + got := table.String() + need := "foo \t bar baz" + + if got != need { + t.Fatalf("need: %q | got: %q ", need, got) + } +} + func TestAddRow(t *testing.T) { var wg sync.WaitGroup table := New() diff -Nru charm-2.1.1/src/github.com/gosuri/uitable/util/strutil/strutil.go charm-2.2.0/src/github.com/gosuri/uitable/util/strutil/strutil.go --- charm-2.1.1/src/github.com/gosuri/uitable/util/strutil/strutil.go 2016-04-01 15:24:09.000000000 +0000 +++ charm-2.2.0/src/github.com/gosuri/uitable/util/strutil/strutil.go 2016-04-28 06:03:36.000000000 +0000 @@ -35,14 +35,18 @@ // Resize resizes the string with the given length. It ellipses with '...' when the string's length exceeds // the desired length or pads spaces to the right of the string when length is smaller than desired -func Resize(s string, length uint) string { +func Resize(s string, length uint, rightAlign bool) string { slen := runewidth.StringWidth(s) n := int(length) if slen == n { return s } // Pads only when length of the string smaller than len needed - s = PadRight(s, n, ' ') + if rightAlign { + s = PadLeft(s, n, ' ') + } else { + s = PadRight(s, n, ' ') + } if slen > n { rs := []rune(s) var buf bytes.Buffer diff -Nru charm-2.1.1/src/github.com/gosuri/uitable/util/strutil/strutil_test.go charm-2.2.0/src/github.com/gosuri/uitable/util/strutil/strutil_test.go --- charm-2.1.1/src/github.com/gosuri/uitable/util/strutil/strutil_test.go 2016-04-01 15:24:09.000000000 +0000 +++ charm-2.2.0/src/github.com/gosuri/uitable/util/strutil/strutil_test.go 2016-04-28 06:03:36.000000000 +0000 @@ -6,18 +6,30 @@ func TestResize(t *testing.T) { s := "foo" - got := Resize(s, 5) + got := Resize(s, 5, false) if len(got) != 5 { t.Fatal("want", 5, "got", len(got)) } s = "foobar" - got = Resize(s, 5) + got = Resize(s, 5, false) if got != "fo..." { t.Fatal("want", "fo...", "got", got) } } +func TestAlign(t *testing.T) { + s := "foo" + got := Resize(s, 5, false) + if got != "foo " { + t.Fatal("want", "foo ", "got", got) + } + got = Resize(s, 5, true) + if got != " foo" { + t.Fatal("want", " foo", "got", got) + } +} + func TestJoin(t *testing.T) { got := Join([]string{"foo", "bar"}, ",") if got != "foo,bar" { diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach.go 2016-03-23 02:08:47.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach.go 2016-09-03 01:24:14.000000000 +0000 @@ -5,31 +5,23 @@ import ( "fmt" - "io" "os" "strings" "github.com/juju/cmd" - "github.com/juju/errors" "gopkg.in/errgo.v1" "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient" "launchpad.net/gnuflag" ) -var uploadResource = func(client *csclient.Client, id *charm.URL, name, path string, file io.ReadSeeker) (revision int, err error) { - return client.UploadResource(id, name, path, file) -} - type attachCommand struct { cmd.CommandBase - id *charm.URL - name string - file string - auth string - username string - password string + channel chanValue + id *charm.URL + name string + file string + auth authInfo } var attachDoc = ` @@ -50,6 +42,7 @@ func (c *attachCommand) SetFlags(f *gnuflag.FlagSet) { addAuthFlag(f, &c.auth) + addChannelFlag(f, &c.channel, nil) } func (c *attachCommand) Init(args []string) error { @@ -79,16 +72,11 @@ c.name = name c.file = filename - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return err - } - return nil } func (c *attachCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, c.channel.C) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } @@ -99,12 +87,12 @@ return errgo.Mask(err) } defer f.Close() - rev, err := uploadResource(client.Client, c.id, c.name, c.file, f) + rev, err := client.Client.UploadResource(c.id, c.name, c.file, f) if err != nil { return errgo.Notef(err, "can't upload resource") } - fmt.Fprintf(ctxt.Stdout, "uploaded revision %d of %s", rev, c.name) + fmt.Fprintf(ctxt.Stdout, "uploaded revision %d of %s\n", rev, c.name) return nil } @@ -122,7 +110,7 @@ return "", "", errgo.New("missing resource name") } if filename == "" { - return "", "", errors.New("missing filename") + return "", "", errgo.New("missing filename") } return name, filename, nil } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach_test.go 2016-03-23 02:08:47.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/attach_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -4,25 +4,34 @@ package charmcmd_test import ( - "io" + "crypto/sha512" "io/ioutil" "path/filepath" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient" - - "github.com/juju/charmstore-client/cmd/charm/charmcmd" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + charmtesting "gopkg.in/juju/charmrepo.v2-unstable/testing" + "gopkg.in/macaroon-bakery.v1/bakery/checkers" ) -type pushResourceSuite struct { +type attachSuite struct { commonSuite } -var _ = gc.Suite(&pushResourceSuite{}) +var _ = gc.Suite(&attachSuite{}) + +func (s *attachSuite) SetUpTest(c *gc.C) { + s.commonSuite.SetUpTest(c) + s.discharge = func(cavId, cav string) ([]checkers.Caveat, error) { + return []checkers.Caveat{ + checkers.DeclaredCaveat("username", "bob"), + }, nil + } +} -var pushResourceInitErrorTests = []struct { +var attachInitErrorTests = []struct { args []string err string }{{ @@ -44,9 +53,9 @@ err: "too many arguments", }} -func (s *pushResourceSuite) TestInitError(c *gc.C) { +func (s *attachSuite) TestInitError(c *gc.C) { dir := c.MkDir() - for i, test := range pushResourceInitErrorTests { + for i, test := range attachInitErrorTests { c.Logf("test %d: %q", i, test.args) args := []string{"attach"} stdout, stderr, code := run(dir, append(args, test.args...)...) @@ -56,31 +65,41 @@ } } -func (s *pushResourceSuite) TestRun(c *gc.C) { - called := 0 +func (s *attachSuite) TestRun(c *gc.C) { + ch := charmtesting.NewCharmMeta(charmtesting.MetaWithResources(nil, "someResource")) + id := charm.MustParseURL("~bob/precise/wordpress") + id, err := s.client.UploadCharm(id, ch) + c.Assert(err, gc.IsNil) + dir := c.MkDir() - err := ioutil.WriteFile(filepath.Join(dir, "bar.zip"), []byte("content"), 0666) + err = ioutil.WriteFile(filepath.Join(dir, "bar.zip"), []byte("content"), 0666) c.Assert(err, gc.IsNil) - s.PatchValue(charmcmd.UploadResource, func(client *csclient.Client, id *charm.URL, name, path string, r io.ReadSeeker) (revision int, err error) { - called++ - c.Assert(client, gc.NotNil) - c.Assert(id, gc.DeepEquals, charm.MustParseURL("wordpress")) - c.Assert(name, gc.Equals, "foo") - c.Assert(path, gc.Equals, "bar.zip") - data, err := ioutil.ReadAll(r) - c.Assert(err, jc.ErrorIsNil) - c.Assert(string(data), gc.Equals, "content") - return 1, nil - }) - - stdout, stderr, exitCode := run(dir, "attach", "wordpress", "foo=bar.zip") - c.Check(called, gc.Equals, 1) - c.Check(exitCode, gc.Equals, 0) - c.Check(stdout, gc.Equals, "uploaded revision 1 of foo") + + stdout, stderr, exitCode := run(dir, "attach", "--channel=unpublished", "~bob/precise/wordpress", "someResource=bar.zip") + c.Check(stdout, gc.Equals, "uploaded revision 0 of someResource\n") c.Check(stderr, gc.Equals, "") + c.Assert(exitCode, gc.Equals, 0) + + // Check that the resource has actually been attached. + resources, err := s.client.WithChannel("unpublished").ListResources(id) + c.Assert(err, gc.IsNil) + c.Assert(resources, jc.DeepEquals, []params.Resource{{ + Name: "someResource", + Type: "file", + Path: "someResource-file", + Revision: 0, + Fingerprint: hashOfString("content"), + Size: int64(len("content")), + Description: "someResource description", + }}) +} + +func hashOfString(s string) []byte { + x := sha512.Sum384([]byte(s)) + return x[:] } -func (s *pushResourceSuite) TestCannotOpenFile(c *gc.C) { +func (s *attachSuite) TestCannotOpenFile(c *gc.C) { path := filepath.Join(c.MkDir(), "/not-there") stdout, stderr, exitCode := run(c.MkDir(), "attach", "wordpress", "foo="+path) c.Assert(exitCode, gc.Equals, 1) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd.go 2016-09-03 01:24:14.000000000 +0000 @@ -1,11 +1,10 @@ -// Copyright 2014 Canonical Ltd. +// Copyright 2014-2016 Canonical Ltd. // Licensed under the GPLv3, see LICENCE file for details. package charmcmd import ( "fmt" - "io/ioutil" "os" "strings" @@ -38,48 +37,16 @@ ` ) -// Main is like cmd.Main but without dying on unknown args, allowing for -// plugins to accept any arguments. -func Main(c commandWithPlugins, ctx *cmd.Context, args []string) int { - f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ContinueOnError) - f.SetOutput(ioutil.Discard) - c.SetFlags(f) - err := f.Parse(false, args) - if err != nil { - fmt.Fprintf(ctx.Stderr, "error: %v\n", err) - return 2 - } - // Since SuperCommands can also return gnuflag.ErrHelp errors, we need to - // handle both those types of errors as well as "real" errors. - err = c.Init(f.Args()) - // Plugins are special. Ignore their Init errors. - if err != nil && !c.isPlugin(args) { - if cmd.IsRcPassthroughError(err) { - return err.(*cmd.RcPassthroughError).Code +// New returns a command that can execute charm commands. +func New() *cmd.SuperCommand { + var c *cmd.SuperCommand + notifyHelp := func(arg []string) { + if len(arg) == 0 { + registerPlugins(c) } - fmt.Fprintf(ctx.Stderr, "error: %v\n", err) - return 2 } - // SuperCommand eats args. Call init directly on the plugin to set correct args. - if c.isPlugin(args) { - c.initPlugin(args[0], args[1:]) - } - if err = c.Run(ctx); err != nil { - if cmd.IsRcPassthroughError(err) { - return err.(*cmd.RcPassthroughError).Code - } - if err != cmd.ErrSilent { - fmt.Fprintf(ctx.Stderr, "error: %v\n", err) - } - return 1 - } - return 0 -} -// New returns a command that can execute juju-charm -// commands. -func New() commandWithPlugins { - supercmd := cmd.NewSuperCommand(cmd.SuperCommandParams{ + c = cmd.NewSuperCommand(cmd.SuperCommandParams{ Name: cmdName, Doc: cmdDoc, Purpose: "tools for accessing the charm store", @@ -87,24 +54,30 @@ Log: &cmd.Log{ DefaultConfig: os.Getenv(osenv.JujuLoggingConfigEnvKey), }, + NotifyHelp: notifyHelp, }) - chcmd := newSuperCommand(supercmd) - chcmd.register(&attachCommand{}) - chcmd.register(&grantCommand{}) - chcmd.register(&listCommand{}) - chcmd.register(&loginCommand{}) - chcmd.register(&logoutCommand{}) - chcmd.register(&publishCommand{}) - chcmd.register(&pullCommand{}) - chcmd.register(&pushCommand{}) - chcmd.register(&revokeCommand{}) - chcmd.register(&setCommand{}) - chcmd.register(&showCommand{}) - chcmd.register(&termsCommand{}) - chcmd.register(&whoamiCommand{}) - chcmd.register(&listResourcesCommand{}) - chcmd.registerPlugins() - return chcmd + c.Register(&attachCommand{}) + c.Register(&grantCommand{}) + c.Register(&listCommand{}) + c.Register(&listResourcesCommand{}) + c.Register(&loginCommand{}) + c.Register(&logoutCommand{}) + c.Register(&pullCommand{}) + c.Register(&pushCommand{}) + c.Register(&releaseCommand{}) + c.Register(&revokeCommand{}) + c.Register(&setCommand{}) + c.Register(&showCommand{}) + c.Register(&termsCommand{}) + c.Register(&whoamiCommand{}) + c.AddHelpTopicCallback( + "plugins", + "Show "+c.Name+" plugins", + func() string { + return pluginHelpTopic() + }, + ) + return c } // Expose the charm store server URL so that @@ -125,9 +98,8 @@ // and cookie jar. type csClient struct { *csclient.Client - jar *cookiejar.Jar - ctxt *cmd.Context - filler esform.Filler + jar *cookiejar.Jar + ctxt *cmd.Context } // SaveJAR calls save on the jar member variable. This follows the Law @@ -139,8 +111,9 @@ // newCharmStoreClient creates and return a charm store client with access to // the associated HTTP client and cookie jar used to save authorization // macaroons. If authUsername and authPassword are provided, the resulting -// client will use HTTP basic auth with the given credentials. -func newCharmStoreClient(ctxt *cmd.Context, authUsername, authPassword string) (*csClient, error) { +// client will use HTTP basic auth with the given credentials. The charm store +// client will use the given channel for its operations. +func newCharmStoreClient(ctxt *cmd.Context, auth authInfo, channel params.Channel) (*csClient, error) { jar, err := cookiejar.New(&cookiejar.Options{ PublicSuffixList: publicsuffix.List, Filename: cookiejar.DefaultCookieFile(), @@ -164,36 +137,84 @@ Client: csclient.New(csclient.Params{ URL: serverURL(), BakeryClient: bakeryClient, - User: authUsername, - Password: authPassword, - }), + User: auth.username, + Password: auth.password, + }).WithChannel(channel), jar: jar, ctxt: ctxt, } return &csClient, nil } +// addChannelFlag adds the -c (--channel) flags to the given flag set. +// The channels argument is the set of allowed channels. +func addChannelFlag(f *gnuflag.FlagSet, cv *chanValue, channels []params.Channel) { + if len(channels) == 0 { + channels = params.OrderedChannels + } + chans := make([]string, len(channels)) + for i, ch := range channels { + chans[i] = string(ch) + } + f.Var(cv, "c", fmt.Sprintf("the channel the charm or bundle is assigned to (%s)", strings.Join(chans, "|"))) + f.Var(cv, "channel", "") +} + +// chanValue is a gnuflag.Value that stores a channel name +// ("stable", "candidate", "beta", "edge" or "unpublished"). +type chanValue struct { + C params.Channel +} + +// Set implements gnuflag.Value.Set by setting the channel value. +func (cv *chanValue) Set(s string) error { + cv.C = params.Channel(s) + if cv.C == params.DevelopmentChannel { + logger.Warningf("the development channel is deprecated: automatically switching to the edge channel") + cv.C = params.EdgeChannel + } + return nil +} + +// String implements gnuflag.Value.String. +func (cv *chanValue) String() string { + return string(cv.C) +} + // addAuthFlag adds the authentication flag to the given flag set. -func addAuthFlag(f *gnuflag.FlagSet, s *string) { - f.StringVar(s, "auth", "", "user:passwd to use for basic HTTP authentication") +func addAuthFlag(f *gnuflag.FlagSet, info *authInfo) { + f.Var(info, "auth", "user:passwd to use for basic HTTP authentication") } -// addChannelFlag adds the -c (--channel) flags to the given flag set. -func addChannelFlag(f *gnuflag.FlagSet, s *string) { - f.StringVar(s, "c", "", fmt.Sprintf("the channel the charm or bundle is assigned to (%s|%s|%s)", params.StableChannel, params.DevelopmentChannel, params.UnpublishedChannel)) - f.StringVar(s, "channel", "", "") +type authInfo struct { + username string + password string } -func validateAuthFlag(flagval string) (string, string, error) { - // Validate the authentication flag. - if flagval == "" { - return "", "", nil +// Set implements gnuflag.Value.Set by validating +// the authentication flag. +func (a *authInfo) Set(s string) error { + if s == "" { + *a = authInfo{} + return nil } - parts := strings.SplitN(flagval, ":", 2) + parts := strings.SplitN(s, ":", 2) if len(parts) != 2 { - return "", "", errgo.Newf(`invalid auth credentials: expected "user:passwd", got %q`, flagval) + return errgo.New(`invalid auth credentials: expected "user:passwd"`) + } + if parts[0] == "" { + return errgo.Newf("empty username") + } + a.username, a.password = parts[0], parts[1] + return nil +} + +// String implements gnuflag.Value.String. +func (a *authInfo) String() string { + if a.username == "" && a.password == "" { + return "" } - return parts[0], parts[1], nil + return a.username + ":" + a.password } func ussoTokenPath() string { diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_internal_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_internal_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_internal_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_internal_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,108 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the GPLv3, see LICENCE file for details. + +package charmcmd + +import ( + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + "launchpad.net/gnuflag" +) + +type cmdAuthInfoSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&cmdAuthInfoSuite{}) + +var authFlagTests = []struct { + about string + arg string + expect authInfo + expectError string +}{{ + about: "empty value", + expect: authInfo{}, +}, { + about: "normal user:password", + arg: "user:pass", + expect: authInfo{ + username: "user", + password: "pass", + }, +}, { + about: "no colon", + arg: "pass", + expectError: `invalid value "pass" for flag --auth: invalid auth credentials: expected "user:passwd"`, +}, { + about: "empty username", + arg: ":pass", + expectError: `invalid value ":pass" for flag --auth: empty username`, +}, { + about: "password containing colon", + arg: "user:pass:word", + expect: authInfo{ + username: "user", + password: "pass:word", + }, +}} + +func (*cmdAuthInfoSuite) TestAuthFlag(c *gc.C) { + for i, test := range authFlagTests { + c.Logf("test %d: %s", i, test.about) + fs := gnuflag.NewFlagSet("x", gnuflag.ContinueOnError) + var info authInfo + addAuthFlag(fs, &info) + err := fs.Parse(true, []string{"--auth", test.arg}) + if test.expectError != "" { + c.Assert(err, gc.ErrorMatches, test.expectError) + } else { + c.Assert(err, jc.ErrorIsNil) + c.Assert(info, jc.DeepEquals, test.expect) + c.Assert(info.String(), gc.Equals, test.arg) + } + } +} + +type cmdChanValueSuite struct { + testing.IsolationSuite + fs *gnuflag.FlagSet + channel chanValue +} + +var _ = gc.Suite(&cmdChanValueSuite{}) + +func (s *cmdChanValueSuite) SetUpTest(c *gc.C) { + s.IsolationSuite.SetUpTest(c) + s.fs = gnuflag.NewFlagSet("", gnuflag.ContinueOnError) + addChannelFlag(s.fs, &s.channel, nil) +} + +func (s *cmdChanValueSuite) run(c *gc.C, channel string) { + err := s.fs.Parse(true, []string{"--channel", channel}) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *cmdChanValueSuite) TestChannel(c *gc.C) { + s.run(c, "beta") + c.Assert(s.channel.C, gc.Equals, params.BetaChannel) + + s.run(c, "stable") + c.Assert(s.channel.C, gc.Equals, params.StableChannel) +} + +func (s *cmdChanValueSuite) TestString(c *gc.C) { + for i, channel := range []string{"stable", "candidate", "beta", "edge", "unpublished"} { + c.Logf("\ntest %d: %s", i, channel) + s.run(c, channel) + c.Assert(s.channel.String(), gc.Equals, channel) + } +} + +func (s *cmdChanValueSuite) TestDevelopmentDeprecated(c *gc.C) { + s.run(c, "development") + c.Assert(s.channel.C, gc.Equals, params.EdgeChannel) + c.Assert(c.GetTestLog(), jc.Contains, "the development channel is deprecated: automatically switching to the edge channel") +} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_test.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/cmd_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -56,7 +56,7 @@ Stdout: &stdoutBuf, Stderr: &stderrBuf, } - exitCode = charmcmd.Main(charmcmd.New(), ctxt, args) + exitCode = cmd.Main(charmcmd.New(), ctxt, args) return stdoutBuf.String(), stderrBuf.String(), exitCode } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/export_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/export_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/export_test.go 2016-04-13 15:17:35.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/export_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -1,21 +1,17 @@ -// Copyright 2014 Canonical Ltd. +// Copyright 2014-2016 Canonical Ltd. // Licensed under the GPLv3, see LICENCE file for details. package charmcmd var ( - ClientGetArchive = &clientGetArchive - CSClientServerURL = &csclientServerURL - GetExtraInfo = getExtraInfo - MapLogEntriesToVcsRevisions = mapLogEntriesToVcsRevisions - ParseGitLog = parseGitLog - PluginTopicText = pluginTopicText - ServerURL = serverURL - UploadResource = &uploadResource - PublishCharm = &publishCharm - ListResources = &listResources - TranslateError = translateError - USSOTokenPath = ussoTokenPath + ClientGetArchive = &clientGetArchive + CSClientServerURL = &csclientServerURL + PluginTopicText = pluginTopicText + ServerURL = serverURL + TranslateError = translateError + USSOTokenPath = ussoTokenPath + PluginDescriptionLastCallReturnedCache = &pluginDescriptionLastCallReturnedCache + WhiteListedCommands = whiteListedCommands ) func ResetPluginDescriptionsResults() { diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant.go 2016-04-01 15:19:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant.go 2016-09-03 01:24:14.000000000 +0000 @@ -17,14 +17,11 @@ type grantCommand struct { cmd.CommandBase - id *charm.URL - auth string - - username string - password string - acl string - set bool - channel string + id *charm.URL + auth authInfo + acl string + set bool + channel chanValue // Validated options used in Run(...). addReads []string @@ -50,7 +47,7 @@ To select a channel, use the --channel option, for instance: - charm grant ~johndoe/wordpress --channel development --acl write --set fred,bob + charm grant ~johndoe/wordpress --channel edge --acl write --set fred,bob ` func (c *grantCommand) Info() *cmd.Info { @@ -64,7 +61,7 @@ func (c *grantCommand) SetFlags(f *gnuflag.FlagSet) { addAuthFlag(f, &c.auth) - addChannelFlag(f, &c.channel) + addChannelFlag(f, &c.channel, nil) f.StringVar(&c.acl, "acl", "read", "read|write") f.BoolVar(&c.set, "set", false, "overwrite the current acl") } @@ -114,24 +111,16 @@ } } - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return errgo.Mask(err) - } - return nil } func (c *grantCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, c.channel.C) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } defer client.jar.Save() - if c.channel != "" { - client.Client = client.Client.WithChannel(params.Channel(c.channel)) - } // Perform the request to change the permissions on the charm store. if err := c.changePerms(client); err != nil { return errgo.Mask(err) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant_test.go 2016-03-17 19:42:57.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/grant_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -227,7 +227,7 @@ s.uploadCharmDir(c, url.WithRevision(43), -1, ch) s.publish(c, url.WithRevision(41), params.StableChannel) - s.publish(c, url.WithRevision(42), params.DevelopmentChannel) + s.publish(c, url.WithRevision(42), params.EdgeChannel) s.setReadPerms(c, url.WithRevision(41), []string{"foo"}) s.setWritePerms(c, url.WithRevision(41), []string{"foo"}) @@ -239,8 +239,8 @@ // Prepare the credentials arguments. auth := s.serverParams.AuthUsername + ":" + s.serverParams.AuthPassword - // Test with the development channel - _, stderr, code := run(dir, "grant", url.String(), "-c", "development", "bar", "--auth", auth) + // Test with the edge channel. + _, stderr, code := run(dir, "grant", url.String(), "-c", "edge", "bar", "--auth", auth) c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) @@ -250,7 +250,7 @@ c.Assert(s.getReadPerms(c, url.WithRevision(41)), jc.DeepEquals, []string{"foo"}) c.Assert(s.getWritePerms(c, url.WithRevision(41)), jc.DeepEquals, []string{"foo"}) - // Test with the stable channel + // Test with the stable channel. _, stderr, code = run(dir, "grant", url.String(), "bar", "--auth", auth) c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/list.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/list.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/list.go 2016-04-01 15:19:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/list.go 2016-09-03 01:24:14.000000000 +0000 @@ -14,12 +14,9 @@ type listCommand struct { cmd.CommandBase - auth string - - username string - password string - out cmd.Output - user string + auth authInfo + out cmd.Output + user string } var listDoc = ` @@ -60,16 +57,11 @@ } func (c *listCommand) Init(args []string) error { - var err error - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return errgo.Mask(err) - } - return nil + return cmd.CheckEmpty(args) } func (c *listCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, params.NoChannel) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources.go 2016-04-13 15:17:35.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources.go 2016-09-03 01:24:14.000000000 +0000 @@ -12,7 +12,6 @@ "github.com/juju/cmd" "gopkg.in/errgo.v1" "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient" "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "launchpad.net/gnuflag" ) @@ -44,15 +43,8 @@ cmd.Output id *charm.URL - channel string - - auth string - username string - password string -} - -var listResources = func(csClient *csclient.Client, id *charm.URL) ([]params.Resource, error) { - return csClient.ListResources(id) + channel chanValue + auth authInfo } // Info implements cmd.Command. @@ -62,7 +54,7 @@ // SetFlags implements cmd.Command. func (c *listResourcesCommand) SetFlags(f *gnuflag.FlagSet) { - addChannelFlag(f, &c.channel) + addChannelFlag(f, &c.channel, nil) addAuthFlag(f, &c.auth) c.Output.AddFlags(f, "tabular", map[string]cmd.Formatter{ "json": cmd.FormatJson, @@ -72,25 +64,31 @@ } // Init implements cmd.Command. -func (c *listResourcesCommand) Init(args []string) (err error) { - c.username, c.password, c.id, err = parseArgs(c.auth, args) - return err +func (c *listResourcesCommand) Init(args []string) error { + if len(args) == 0 { + return errgo.New("no charm id specified") + } + if len(args) > 1 { + return errgo.New("too many arguments") + } + + id, err := charm.ParseURL(args[0]) + if err != nil { + return errgo.Notef(err, "invalid charm id") + } + c.id = id + return nil } // Run implements cmd.Command. func (c *listResourcesCommand) Run(ctx *cmd.Context) error { - client, err := newCharmStoreClient(ctx, c.username, c.password) + client, err := newCharmStoreClient(ctx, c.auth, c.channel.C) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } defer client.SaveJAR() - if c.channel != "" { - client.Client = client.Client.WithChannel(params.Channel(c.channel)) - } - - resources, err := listResources(client.Client, c.id) - + resources, err := client.Client.ListResources(c.id) if err != nil { return errgo.Notef(err, "could not retrieve resource information") } @@ -98,26 +96,6 @@ return c.Write(ctx, resources) } -func parseArgs(auth string, args []string) (string, string, *charm.URL, error) { - if len(args) == 0 { - return "", "", nil, errgo.New("no charm id specified") - } - if len(args) > 1 { - return "", "", nil, errgo.New("too many arguments") - } - username, password, err := validateAuthFlag(auth) - if err != nil { - return "", "", nil, err - } - - id, err := charm.ParseURL(args[0]) - if err != nil { - return "", "", nil, errgo.Notef(err, "invalid charm id") - } - - return username, password, id, err -} - func tabularFormatter(resources interface{}) ([]byte, error) { typedResources, ok := resources.([]params.Resource) if ok == false { diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources_test.go 2016-04-13 15:17:35.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/listresources_test.go 2016-04-28 06:02:24.000000000 +0000 @@ -4,11 +4,13 @@ package charmcmd_test import ( - "github.com/juju/charmstore-client/cmd/charm/charmcmd" + "strings" + gc "gopkg.in/check.v1" "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient" "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + charmtesting "gopkg.in/juju/charmrepo.v2-unstable/testing" + "gopkg.in/macaroon-bakery.v1/bakery/checkers" ) type listResourcesSuite struct { @@ -17,7 +19,14 @@ var _ = gc.Suite(&listResourcesSuite{}) -// TODO frankban: add end-to-end tests. +func (s *listResourcesSuite) SetUpTest(c *gc.C) { + s.commonSuite.SetUpTest(c) + s.discharge = func(cavId, cav string) ([]checkers.Caveat, error) { + return []checkers.Caveat{ + checkers.DeclaredCaveat("username", "bob"), + }, nil + } +} func (s *listResourcesSuite) TestListResourcesErrorCharmNotFound(c *gc.C) { stdout, stderr, retCode := run(c.MkDir(), "list-resources", "no-such") @@ -52,48 +61,42 @@ } } -func (s *listResourcesSuite) TestIdParsedCorrectly(c *gc.C) { - called := 0 - dir := c.MkDir() - s.PatchValue(charmcmd.ListResources, func(csClient *csclient.Client, id *charm.URL) ([]params.Resource, error) { - called++ - c.Assert(csClient, gc.NotNil) - c.Assert(id, gc.DeepEquals, charm.MustParseURL("wordpress")) - return nil, nil - }) - _, _, _ = run(dir, "list-resources", "wordpress") - c.Check(called, gc.Equals, 1) -} - func (s *listResourcesSuite) TestNoResouces(c *gc.C) { - called := 0 - dir := c.MkDir() - s.PatchValue(charmcmd.ListResources, func(csClient *csclient.Client, id *charm.URL) ([]params.Resource, error) { - called++ - return nil, nil - }) - stdout, stderr, code := run(dir, "list-resources", "wordpress") - c.Check(called, gc.Equals, 1) + id, err := s.client.UploadCharm( + charm.MustParseURL("~bob/precise/wordpress"), + charmtesting.NewCharmMeta(nil), + ) + c.Assert(err, gc.IsNil) + err = s.client.Publish(id, []params.Channel{params.StableChannel}, nil) + c.Assert(err, gc.IsNil) + + stdout, stderr, code := run(".", "list-resources", "~bob/wordpress") + c.Check(stdout, gc.Equals, "No resources found.\n") + c.Check(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) - c.Assert(stdout, gc.Equals, "No resources found.\n") - c.Assert(stderr, gc.Equals, "") } func (s *listResourcesSuite) TestListResource(c *gc.C) { - called := 0 - dir := c.MkDir() - s.PatchValue(charmcmd.ListResources, func(csClient *csclient.Client, id *charm.URL) ([]params.Resource, error) { - called++ - c.Assert(csClient, gc.NotNil) - c.Assert(id, gc.DeepEquals, charm.MustParseURL("wordpress")) - return []params.Resource{{ - Name: "my-resource", - Revision: 1, - }}, nil + id, err := s.client.UploadCharm( + charm.MustParseURL("~bob/precise/wordpress"), + charmtesting.NewCharmMeta(charmtesting.MetaWithResources(nil, "someResource")), + ) + c.Assert(err, gc.IsNil) + _, err = s.client.UploadResource(id, "someResource", "", strings.NewReader("content")) + c.Assert(err, gc.IsNil) + + err = s.client.Publish(id, []params.Channel{params.StableChannel}, map[string]int{ + "someResource": 0, }) - stdout, stderr, code := run(dir, "list-resources", "wordpress") - c.Check(called, gc.Equals, 1) + c.Assert(err, gc.IsNil) + + stdout, stderr, code := run(".", "list-resources", "~bob/wordpress") + c.Check(stdout, gc.Equals, ` +[Service] +RESOURCE REVISION +someResource 0 + +`[1:]) + c.Check(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) - c.Assert(stdout, gc.Equals, "[Service]\nRESOURCE REVISION\nmy-resource 1\n\n") - c.Assert(stderr, gc.Equals, "") } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/login.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/login.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/login.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/login.go 2016-09-03 01:24:14.000000000 +0000 @@ -6,6 +6,7 @@ import ( "github.com/juju/cmd" "gopkg.in/errgo.v1" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" ) type loginCommand struct { @@ -27,7 +28,7 @@ } func (c *loginCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, "", "") + client, err := newCharmStoreClient(ctxt, authInfo{}, params.NoChannel) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/logout.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/logout.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/logout.go 2016-04-01 15:19:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/logout.go 2016-09-03 01:24:14.000000000 +0000 @@ -14,6 +14,7 @@ "github.com/juju/cmd" "gopkg.in/errgo.v1" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "gopkg.in/macaroon.v1" ) @@ -40,7 +41,7 @@ if err := os.Remove(ussoTokenPath()); err != nil && !os.IsNotExist(err) { return errgo.New("cannot remove Ubuntu SSO token") } - client, err := newCharmStoreClient(ctxt, "", "") + client, err := newCharmStoreClient(ctxt, authInfo{}, params.NoChannel) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin.go 2016-03-23 02:08:47.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin.go 2016-09-03 01:24:14.000000000 +0000 @@ -1,20 +1,24 @@ -// Copyright 2015 Canonical Ltd. +// Copyright 2015-2016 Canonical Ltd. // Licensed under the GPLv3, see LICENCE file for details. package charmcmd import ( "bytes" + "encoding/json" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" "sort" "strings" + "sync" "syscall" + "text/tabwriter" + "time" "github.com/juju/cmd" + "gopkg.in/yaml.v2" ) const pluginPrefix = cmdName + "-" @@ -57,7 +61,6 @@ return &cmd.Info{ Name: pc.name, Purpose: purpose, - Doc: doc, } } @@ -91,122 +94,288 @@ func pluginHelpTopic() string { output := &bytes.Buffer{} fmt.Fprintf(output, pluginTopicText) - existingPlugins := getPluginDescriptions() + d := getPluginDescriptions() + existingPlugins := make(pluginDescriptions, 0, len(d)) + for _, plugin := range d { + existingPlugins = append(existingPlugins, plugin) + } + sort.Sort(existingPlugins) if len(existingPlugins) == 0 { fmt.Fprintf(output, "No plugins found.\n") } else { - longest := 0 - for _, plugin := range existingPlugins { - if len(plugin.name) > longest { - longest = len(plugin.name) - } - } + w := tabwriter.NewWriter(output, 0, 0, 2, ' ', 0) for _, plugin := range existingPlugins { - fmt.Fprintf(output, "%-*s %s\n", longest, plugin.name, plugin.description) + fmt.Fprintf(w, "%s\t%s\n", plugin.Name, plugin.Description) } + w.Flush() } return output.String() } +func registerPlugins(c *cmd.SuperCommand) { + plugins := getPluginDescriptions() + for _, plugin := range plugins { + c.Register(&pluginCommand{ + name: plugin.Name, + purpose: plugin.Description, + }) + } +} + +// pluginDescriptionLastCallReturnedCache is true if all plugins values were cached. +var pluginDescriptionLastCallReturnedCache bool + // pluginDescriptionsResults holds memoized results for getPluginDescriptions. -var pluginDescriptionsResults []pluginDescription +var pluginDescriptionsResults map[string]pluginDescription // getPluginDescriptions runs each plugin with "--description". The calls to // the plugins are run in parallel, so the function should only take as long // as the longest call. -func getPluginDescriptions() []pluginDescription { +// We cache results in $XDG_CACHE_HOME/charm-command-cache +// or $HOME/.cache/charm-command-cache if $XDG_CACHE_HOME +// isn't set, invalidating the cache if executable modification times change. +func getPluginDescriptions() map[string]pluginDescription { if len(pluginDescriptionsResults) > 0 { return pluginDescriptionsResults } - plugins := findPlugins() - results := []pluginDescription{} + pluginCacheDir := filepath.Join(os.Getenv("HOME"), ".cache") + if d := os.Getenv("XDG_CACHE_HOME"); d != "" { + pluginCacheDir = d + } + pluginCacheFile := filepath.Join(pluginCacheDir, "charm-command-cache") + plugins, seenPlugins := findPlugins() if len(plugins) == 0 { - return results + return map[string]pluginDescription{} } - // Create a channel with enough backing for each plugin. - description := make(chan pluginDescription, len(plugins)) - help := make(chan pluginDescription, len(plugins)) - - // Exec the --description and --help commands. + if err := os.MkdirAll(pluginCacheDir, os.ModeDir|os.ModePerm); err != nil { + logger.Errorf("creating plugin cache dir: %s, %s", pluginCacheDir, err) + } + pluginCache := openCache(pluginCacheFile) + allcached := true + allcachedLock := sync.RWMutex{} + wg := sync.WaitGroup{} for _, plugin := range plugins { - go func(plugin string) { - result := pluginDescription{ - name: plugin, - } - defer func() { - description <- result - }() - desccmd := exec.Command(plugin, "--description") - output, err := desccmd.CombinedOutput() - - if err == nil { - // Trim to only get the first line. - result.description = strings.SplitN(string(output), "\n", 2)[0] - } else { - result.description = fmt.Sprintf("error occurred running '%s --description'", plugin) - logger.Debugf("'%s --description': %s", plugin, err) - } - }(plugin) - go func(plugin string) { - result := pluginDescription{ - name: plugin, - } - defer func() { - help <- result - }() - helpcmd := exec.Command(plugin, "--help") - output, err := helpcmd.CombinedOutput() - if err == nil { - result.doc = string(output) - } else { - result.doc = fmt.Sprintf("error occured running '%s --help'", plugin) - logger.Debugf("'%s --help': %s", plugin, err) - } - }(plugin) + wg.Add(1) + plugin := plugin + go func() { + defer wg.Done() + _, cached := pluginCache.fetch(plugin) + allcachedLock.Lock() + allcached = allcached && cached + allcachedLock.Unlock() + }() + } + wg.Wait() + pluginDescriptionLastCallReturnedCache = allcached + // A plugin may cached but removed. Remove cached plugins from the cache. + pluginCache.removeMissingPlugins(seenPlugins) + pluginCache.save(pluginCacheFile) + pluginDescriptionsResults = pluginCache.Plugins + return pluginCache.Plugins +} + +type pluginCache struct { + Plugins map[string]pluginDescription + pluginsLock sync.RWMutex +} + +func openCache(file string) *pluginCache { + f, err := os.Open(file) + var c pluginCache + c.pluginsLock = sync.RWMutex{} + if err != nil { + c = pluginCache{} + c.Plugins = make(map[string]pluginDescription) + } + if err := json.NewDecoder(f).Decode(&c); err != nil { + c = pluginCache{} + c.Plugins = make(map[string]pluginDescription) } - resultDescriptionMap := map[string]pluginDescription{} - resultHelpMap := map[string]pluginDescription{} - // Gather the results at the end. - for _ = range plugins { - result := <-description - resultDescriptionMap[result.name] = result - helpResult := <-help - resultHelpMap[helpResult.name] = helpResult + return &c +} + +// returns pluginDescription and boolean indicating if cache was used. +func (c *pluginCache) fetch(fi fileInfo) (*pluginDescription, bool) { + filename := filepath.Join(fi.dir, fi.name) + stat, err := os.Stat(filename) + if err != nil { + logger.Errorf("could not stat %s", filename, err) + // If a file is not readable or otherwise not statable, ignore it. + return nil, false + } + mtime := stat.ModTime() + c.pluginsLock.RLock() + p, ok := c.Plugins[filename] + c.pluginsLock.RUnlock() + // If the plugin is cached check its mtime. + if ok { + // If mtime is same as cached, return the cached data. + if mtime.Unix() == p.ModTime.Unix() { + return &p, true + } } - // plugins array is already sorted, use this to get the results in order. - for _, plugin := range plugins { - // Strip the 'charm-' off the start of the plugin name in the results. - result := resultDescriptionMap[plugin] - result.name = result.name[len(pluginPrefix):] - result.doc = resultHelpMap[plugin].doc - results = append(results, result) + // The cached data is invalid. Run the plugin. + result := pluginDescription{ + Name: fi.name[len(pluginPrefix):], + ModTime: mtime, + } + wg := sync.WaitGroup{} + desc := "" + wg.Add(1) + go func() { + defer wg.Done() + desccmd := exec.Command(fi.name, "--description") + output, err := desccmd.CombinedOutput() + + if err == nil { + // Trim to only get the first line. + desc = strings.SplitN(string(output), "\n", 2)[0] + } else { + desc = fmt.Sprintf("error occurred running '%s --description'", fi.name) + logger.Debugf("'%s --description': %s", fi.name, err) + } + }() + wg.Wait() + result.Description = desc + c.pluginsLock.Lock() + c.Plugins[filename] = result + c.pluginsLock.Unlock() + return &result, false +} + +func (c *pluginCache) removeMissingPlugins(fis map[string]int) { + for key := range c.Plugins { + if _, ok := fis[key]; !ok { + delete(c.Plugins, key) + } + } +} + +func (c *pluginCache) save(filename string) error { + if f, err := os.Create(filename); err == nil { + encoder := json.NewEncoder(f) + if err = encoder.Encode(c); err != nil { + logger.Errorf("encoding cached plugin descriptions: %s", err) + return err + } + } else { + logger.Errorf("opening plugin cache file: %s", err) + return err } - pluginDescriptionsResults = results - return results + return nil } type pluginDescription struct { - name string - description string - doc string + Name string + Description string + ModTime time.Time } +type pluginDescriptions []pluginDescription + +func (a pluginDescriptions) Len() int { return len(a) } +func (a pluginDescriptions) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a pluginDescriptions) Less(i, j int) bool { return a[i].Name < a[j].Name } + // findPlugins searches the current PATH for executable files that start with // pluginPrefix. -func findPlugins() []string { +func findPlugins() ([]fileInfo, map[string]int) { + updateWhiteListedCommands() path := os.Getenv("PATH") - plugins := []string{} - for _, name := range filepath.SplitList(path) { - entries, err := ioutil.ReadDir(name) + plugins := []fileInfo{} + seen := map[string]int{} + fullpathSeen := map[string]int{} + for _, dir := range filepath.SplitList(path) { + // ioutil.ReadDir uses lstat on every file and returns a different + // modtime than os.Stat. Do not use ioutil.ReadDir. + dirh, err := os.Open(dir) if err != nil { continue } - for _, entry := range entries { - if strings.HasPrefix(entry.Name(), pluginPrefix) && (entry.Mode()&0111) != 0 { - plugins = append(plugins, entry.Name()) + names, err := dirh.Readdirnames(0) + if err != nil { + continue + } + for _, name := range names { + if seen[name] > 0 { + continue + } + if strings.HasPrefix(name, pluginPrefix) { + fullpath := filepath.Join(dir, name) + stat, err := os.Stat(fullpath) + if err != nil { + continue + } + shortName := name[len(pluginPrefix):] + // Append only if file is executable and name is in the white list. + if stat.Mode()&0111 != 0 && whiteListedCommands[shortName] { + plugins = append(plugins, fileInfo{ + name: name, + mtime: stat.ModTime(), + dir: dir, + }) + seen[name]++ + fullpathSeen[fullpath]++ + } } } } - sort.Strings(plugins) - return plugins + return plugins, fullpathSeen +} + +type fileInfo struct { + name string + mtime time.Time + dir string +} + +// whiteListedCommands is a map of known external charm core commands. A false +// value explicitly blacklists a command. +var whiteListedCommands = map[string]bool{ + "add": true, + "build": true, + "compose": true, + "create": true, + "generate": true, + "get": true, + "getall": true, + "help": false, // help is an internal command. + "info": true, + "inspect": true, + "layers": true, + "list": false, // list is an internal command. + "promulgate": false, // promulgate is an internal command. + "proof": true, + "push-term": true, + "refresh": true, + "release-term": true, + "review": true, + "review-queue": true, + "search": true, + "show-term": true, + "subscibers": true, + "test": true, + "tools-commands": false, // charm-tools-commands is reserved for whitelist extension. + "unpromulgate": true, + "update": true, + "version": true, +} + +// updateWhiteListedCommands calls charm-tools-commands to extend the list of +// commands which are found as plugins and used as charm commands. +// If there is an error executing charm-tools-commands, then it fails silently +// and results in a noop. +func updateWhiteListedCommands() { + text, err := exec.Command("charm-tools-commands").Output() + if err != nil { + return + } + var result []string + err = yaml.Unmarshal([]byte(text), &result) + if err != nil { + return + } + for _, cmd := range result { + whiteListedCommands[cmd] = true + } } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin_test.go 2016-03-23 02:08:47.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/plugin_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -1,4 +1,4 @@ -// Copyright 2015 Canonical Ltd. +// Copyright 2015-2016 Canonical Ltd. // Licensed under the GPLv3, see LICENCE file for details. package charmcmd_test @@ -9,6 +9,7 @@ "io/ioutil" "os" "path/filepath" + "regexp" "runtime" "strings" "text/template" @@ -29,6 +30,14 @@ var _ = gc.Suite(&pluginSuite{}) +func (s *pluginSuite) SetUpSuite(c *gc.C) { + s.IsolationSuite.SetUpSuite(c) + charmcmd.WhiteListedCommands["foo"] = true + charmcmd.WhiteListedCommands["bar"] = true + charmcmd.WhiteListedCommands["baz"] = true + charmcmd.WhiteListedCommands["error"] = true +} + func (s *pluginSuite) SetUpTest(c *gc.C) { if runtime.GOOS == "windows" { c.Skip("tests use bash scripts") @@ -38,6 +47,8 @@ s.dir2 = c.MkDir() s.PatchEnvironment("PATH", s.dir+":"+s.dir2) charmcmd.ResetPluginDescriptionsResults() + os.Remove("/tmp/.cache/charm-command-cache") + os.Remove(filepath.Join(os.Getenv("HOME"), ".cache/charm-command-cache")) } func (*pluginSuite) TestPluginHelpNoPlugins(c *gc.C) { @@ -71,16 +82,11 @@ stdout, stderr, code := run(c.MkDir(), "help", "foo") c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) - c.Assert(stdout, gc.Equals, `Usage: charm foo - -Summary: -foo --description - -Details: + c.Assert(stdout, gc.Equals, ` foo longer help something useful -`) +`[1:]) } func (s *pluginSuite) TestPluginHelpCommandNotFound(c *gc.C) { @@ -108,7 +114,7 @@ c.Assert(stderr, gc.Equals, "") outputChan <- stdout }() - // 10 seconds is arbitrary but should always be generously long. Test + // This time is arbitrary but should always be generously long. Test // actually only takes about 15ms in practice, but 10s allows for system // hiccups, etc. wait := 5 * time.Second @@ -146,33 +152,28 @@ stdout, stderr, code := run(c.MkDir(), "foo", "--help") c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) - c.Assert(stdout, gc.Equals, `Usage: charm foo - -Summary: -foo --description - -Details: + c.Assert(stdout, gc.Equals, ` foo longer help something useful -`) +`[1:]) } func (s *pluginSuite) TestPluginRunWithDebugFlag(c *gc.C) { s.makeFullPlugin(pluginParams{Name: "foo"}) stdout, stderr, code := run(c.MkDir(), "foo", "--debug") - c.Assert(stderr, gc.Matches, ".*?INFO cmd supercommand.go:\\d+ command finished\n") - c.Assert(code, gc.Equals, 0) - c.Assert(stdout, gc.Equals, "some debug\n") + c.Check(stderr, gc.Equals, "") + c.Check(code, gc.Equals, 0) + c.Check(stdout, gc.Equals, "some debug\n") } func (s *pluginSuite) TestPluginRunWithEnvVars(c *gc.C) { s.makeFullPlugin(pluginParams{Name: "foo"}) s.PatchEnvironment("ANSWER", "42") stdout, stderr, code := run(c.MkDir(), "foo") - c.Assert(stderr, gc.Equals, "") - c.Assert(code, gc.Equals, 0) - c.Assert(stdout, gc.Equals, "foo\nanswer is 42\n") + c.Check(stderr, gc.Equals, "") + c.Check(code, gc.Equals, 0) + c.Check(stdout, gc.Equals, "foo\nanswer is 42\n") } func (s *pluginSuite) TestPluginRunWithMultipleNamesInPath(c *gc.C) { @@ -193,6 +194,110 @@ c.Assert(stdout, gc.Equals, "the flag was still there.\n") } +func (s *pluginSuite) TestHelp(c *gc.C) { + s.makeFullPlugin(pluginParams{Name: "foo"}) + s.makeFullPlugin(pluginParams{Name: "bar"}) + s.makeFullPlugin(pluginParams{Name: "help"}) // Duplicates "help" command. + s.makeFullPlugin(pluginParams{Name: "list"}) // Duplicates existing "list" command. + s.makeFullPluginInSecondDir(pluginParams{Name: "foo"}) + + stdout, stderr, code := run(c.MkDir(), "help") + c.Assert(stderr, gc.Matches, "") + c.Assert(code, gc.Equals, 0) + c.Assert(stdout, gc.Matches, ` +(.|\n)* bar - bar --description +(.|\n)* foo - foo --description +(.|\n)* help - show help on a command or other topic +(.|\n)* list - list charms for a given user name +(.|\n)* +`[1:]) +} + +func (s *pluginSuite) TestWhiteListWorks(c *gc.C) { + s.makeFullPlugin(pluginParams{Name: "foo"}) + s.makeFullPlugin(pluginParams{Name: "danger"}) + stdout, stderr, code := run(c.MkDir(), "help") + c.Assert(stderr, gc.Matches, "") + c.Assert(code, gc.Equals, 0) + c.Assert(stdout, gc.Matches, ` +(.|\n)* foo - foo --description +(.|\n)* +`[1:]) +} + +func (s *pluginSuite) TestWhiteListIsExtensible(c *gc.C) { + s.makeFullPlugin(pluginParams{Name: "foo"}) + s.makeFullPlugin(pluginParams{Name: "danger"}) + writePlugin(s.dir, "tools-commands", "#!/bin/bash --norc\necho [\"danger\",]", 0755) + stdout, stderr, code := run(c.MkDir(), "help") + c.Assert(stderr, gc.Matches, "") + c.Assert(code, gc.Equals, 0) + c.Assert(stdout, gc.Matches, ` +(.|\n)* danger - danger --description +(.|\n)* foo - foo --description +(.|\n)* +`[1:]) +} + +func (s *pluginSuite) TestPluginCacheCaches(c *gc.C) { + s.PatchEnvironment("HOME", "/tmp") + s.makeFullPlugin(pluginParams{Name: "foo"}) + run(c.MkDir(), "help") + c.Assert(*charmcmd.PluginDescriptionLastCallReturnedCache, gc.Equals, false) + charmcmd.ResetPluginDescriptionsResults() + run(c.MkDir(), "help") + c.Assert(*charmcmd.PluginDescriptionLastCallReturnedCache, gc.Equals, true) +} + +func (s *pluginSuite) TestPluginCacheInvalidatesOnUpdate(c *gc.C) { + s.PatchEnvironment("HOME", "/tmp") + s.makeFullPlugin(pluginParams{Name: "foo"}) + run(c.MkDir(), "help") + c.Assert(*charmcmd.PluginDescriptionLastCallReturnedCache, gc.Equals, false) + charmcmd.ResetPluginDescriptionsResults() + time.Sleep(time.Second) // Sleep so that the written file has a different mtime + s.makeFullPlugin(pluginParams{Name: "foo"}) + run(c.MkDir(), "help") + c.Assert(*charmcmd.PluginDescriptionLastCallReturnedCache, gc.Equals, false) +} + +func (s *pluginSuite) TestPluginCacheInvalidatesOnNewPlugin(c *gc.C) { + s.PatchEnvironment("HOME", "/tmp") + s.makeFullPlugin(pluginParams{Name: "foo"}) + run(c.MkDir(), "help") + c.Assert(*charmcmd.PluginDescriptionLastCallReturnedCache, gc.Equals, false) + charmcmd.ResetPluginDescriptionsResults() + s.makeFullPlugin(pluginParams{Name: "bar"}) + run(c.MkDir(), "help") + c.Assert(*charmcmd.PluginDescriptionLastCallReturnedCache, gc.Equals, false) +} + +func (s *pluginSuite) TestPluginCacheInvalidatesRemovedPlugin(c *gc.C) { + s.PatchEnvironment("HOME", "/tmp") + s.makeFullPlugin(pluginParams{Name: "foo"}) + // Add bar so that there is more than one plugin. If no plugins are found + // there is a short circuit which makes this test do the wrong thing. + s.makeFullPlugin(pluginParams{Name: "bar"}) + run(c.MkDir(), "help") + charmcmd.ResetPluginDescriptionsResults() + os.Remove(filepath.Join(s.dir, "charm-foo")) + stdout, _, _ := run(c.MkDir(), "help") + // The gc.Matches checker anchors the regex by surrounding it with ^ and $ + // Checking for a not match this way instead. + matches, err := regexp.MatchString(` +foo - foo --description +`[1:], stdout) + if err != nil { + c.Log("regex error" + err.Error()) + c.Fail() + } + expected := false + if matches != expected { + c.Log("output did not match expected output:" + stdout) + } + c.Assert(matches, gc.Equals, expected) +} + func (s *pluginSuite) makePlugin(name string, perm os.FileMode) { content := fmt.Sprintf("#!/bin/bash --norc\necho %s $*", name) writePlugin(s.dir, name, content, perm) @@ -233,7 +338,6 @@ if err := ioutil.WriteFile(path, []byte(content), perm); err != nil { panic(err) } - fmt.Println("wrote ", path) } type pluginParams struct { diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,187 +0,0 @@ -// Copyright 2015 Canonical Ltd. -// Licensed under the GPLv3, see LICENCE file for details. - -package charmcmd - -import ( - "fmt" - "strconv" - "strings" - - "github.com/juju/cmd" - "github.com/juju/errors" - "gopkg.in/errgo.v1" - "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient" - "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" - "launchpad.net/gnuflag" -) - -type publishCommand struct { - cmd.CommandBase - - id *charm.URL - channel string - - auth string - username string - password string - - resources resourceMap -} - -var publishDoc = ` -The publish command publishes a charm or bundle in the charm store. -Publishing is the action of assigning one channel to a specific charm -or bundle revision (revision need to be specified), so that it can be shared -with other users and also referenced without specifying the revision. -Two channels are supported: "stable" and "development"; the "stable" channel is -used by default. - - charm publish ~bob/trusty/wordpress - -To select another channel, use the --channel option, for instance: - - charm publish ~bob/trusty/wordpress --channel stable - charm publish wily/django-42 -c development --resource website-3 --resource data-2 - -If your charm uses resources, you must specify what revision of each resource -will be published along with the charm, using the --resource flag (one per -resource). Note that resource info is embedded in bundles, so you cannot use -this flag with bundles. - - charm publish wily/django-42 --resource website-3 --resource data-2 -` - -func (c *publishCommand) Info() *cmd.Info { - return &cmd.Info{ - Name: "publish", - Args: " [--channel ]", - Purpose: "publish a charm or bundle", - Doc: publishDoc, - } -} - -func (c *publishCommand) SetFlags(f *gnuflag.FlagSet) { - addChannelFlag(f, &c.channel) - addAuthFlag(f, &c.auth) - f.Var(&c.resources, "resource", "resource to be published with the charm") -} - -func (c *publishCommand) Init(args []string) error { - if len(args) == 0 { - return errgo.New("no charm or bundle id specified") - } - if len(args) > 1 { - return errgo.New("too many arguments") - } - - if c.channel == "" { - c.channel = string(params.StableChannel) - } - - id, err := charm.ParseURL(args[0]) - if err != nil { - return errgo.Notef(err, "invalid charm or bundle id") - } - if id.Revision == -1 { - return errgo.Newf("revision needs to be specified") - } - - c.id = id - - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return errgo.Mask(err) - } - - return nil -} - -var publishCharm = func(client *csclient.Client, id *charm.URL, channels []params.Channel, resources map[string]int) error { - return client.Publish(id, channels, resources) -} - -func (c *publishCommand) Run(ctxt *cmd.Context) error { - // Instantiate the charm store client. - client, err := newCharmStoreClient(ctxt, c.username, c.password) - if err != nil { - return errgo.Notef(err, "cannot create the charm store client") - } - defer client.jar.Save() - - err = publishCharm(client.Client, c.id, []params.Channel{params.Channel(c.channel)}, c.resources) - if err != nil { - return errgo.Notef(err, "cannot publish charm or bundle") - } - fmt.Fprintln(ctxt.Stdout, "url:", c.id) - fmt.Fprintln(ctxt.Stdout, "channel:", c.channel) - if "stable" == c.channel { - var result params.MetaAnyResponse - var unset string - verb := "is" - client.Get("/"+c.id.Path()+"/meta/any?include=common-info", &result) - commonInfo, ok := result.Meta["common-info"].(map[string]interface{}) - if !ok { - unset = "bugs-url and homepage" - verb = "are" - } else { - if v, ok := commonInfo["bugs-url"].(string); !ok || v == "" { - unset = "bugs-url" - } - if v, ok := commonInfo["homepage"].(string); !ok || v == "" { - if unset != "" { - unset += " and " - verb = "are" - } - unset += "homepage" - } - } - if unset != "" { - fmt.Fprintf(ctxt.Stdout, "warning: %s %s not set. See set command.\n", unset, verb) - } - } - return nil -} - -// resourceMap is a type that deserializes a CLI string using gnuflag's Value -// semantics. It expects a name-number pair, and supports multiple copies of the -// flag adding more pairs, though the names must be unique. -type resourceMap map[string]int - -// Set implements gnuflag.Value's Set method by adding a value to the resource -// map. -func (m *resourceMap) Set(s string) error { - if *m == nil { - *m = map[string]int{} - } - // make a copy so the following code is less ugly with dereferencing. - mapping := *m - - idx := strings.LastIndex(s, "-") - if idx == -1 { - return errors.NewNotValid(nil, "expected name-revision format") - } - name, value := s[0:idx], s[idx+1:] - if len(name) == 0 || len(value) == 0 { - return errors.NewNotValid(nil, "expected name-revision format") - } - if _, ok := mapping[name]; ok { - return errors.Errorf("duplicate name specified: %q", name) - } - revision, err := strconv.Atoi(value) - if err != nil { - return errors.NewNotValid(err, fmt.Sprintf("badly formatted revision %q", value)) - } - mapping[name] = revision - return nil -} - -// String implements gnuflag.Value's String method. -func (m resourceMap) String() string { - pairs := make([]string, 0, len(m)) - for name, value := range m { - pairs = append(pairs, fmt.Sprintf("%s-%d", name, value)) - } - return strings.Join(pairs, ";") -} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish_test.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/publish_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,283 +0,0 @@ -// Copyright 2015 Canonical Ltd. -// Licensed under the GPLv3, see LICENCE file for details. - -package charmcmd_test - -import ( - "encoding/json" - - gc "gopkg.in/check.v1" - "gopkg.in/errgo.v1" - "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient" - "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" - "gopkg.in/macaroon-bakery.v1/bakery/checkers" - - "github.com/juju/charmstore-client/cmd/charm/charmcmd" - "github.com/juju/charmstore-client/internal/entitytesting" -) - -type publishSuite struct { - commonSuite -} - -var _ = gc.Suite(&publishSuite{}) - -func (s *publishSuite) SetUpTest(c *gc.C) { - s.commonSuite.SetUpTest(c) - s.discharge = func(cavId, cav string) ([]checkers.Caveat, error) { - return []checkers.Caveat{ - checkers.DeclaredCaveat("username", "bob"), - }, nil - } -} - -var publishInitErrorTests = []struct { - about string - args []string - err string -}{{ - about: "Empty Args", - args: []string{}, - err: "no charm or bundle id specified", -}, { - about: "Invalid Charm ID", - args: []string{"invalid:entity"}, - err: `invalid charm or bundle id: charm or bundle URL has invalid schema: "invalid:entity"`, -}, { - about: "Too Many Args", - args: []string{"wordpress", "foo"}, - err: "too many arguments", -}, { - about: "No Resource", - args: []string{"wily/wordpress", "--resource"}, - err: "flag needs an argument: --resource", -}, { - about: "No Revision", - args: []string{"wily/wordpress", "--resource", "foo"}, - err: ".*expected name-revision format", -}, { - about: "No Resource Name", - args: []string{"wily/wordpress", "--resource", "-3"}, - err: ".*expected name-revision format", -}} - -func (s *publishSuite) TestInitError(c *gc.C) { - dir := c.MkDir() - for i, test := range publishInitErrorTests { - c.Logf("test %d (%s): %q", i, test.about, test.args) - subcmd := []string{"publish"} - stdout, stderr, code := run(dir, append(subcmd, test.args...)...) - c.Assert(stdout, gc.Equals, "") - c.Assert(stderr, gc.Matches, "error: "+test.err+"\n") - c.Assert(code, gc.Equals, 2) - } -} - -func (s *publishSuite) TestRunNoSuchCharm(c *gc.C) { - stdout, stderr, code := run(c.MkDir(), "publish", "no-such-entity-55", "--channel", "stable") - c.Assert(stdout, gc.Equals, "") - c.Assert(stderr, gc.Matches, "ERROR cannot publish charm or bundle: no matching charm or bundle for cs:no-such-entity-55\n") - c.Assert(code, gc.Equals, 1) -} - -func (s *publishSuite) TestAuthenticationError(c *gc.C) { - id := charm.MustParseURL("~charmers/utopic/wordpress-42") - s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) - stdout, stderr, code := run(c.MkDir(), "publish", id.String(), "--channel", "stable") - c.Assert(stdout, gc.Equals, "") - c.Assert(stderr, gc.Matches, `ERROR cannot publish charm or bundle: unauthorized: access denied for user "bob"\n`) - c.Assert(code, gc.Equals, 1) -} - -func (s *publishSuite) TestPublishInvalidChannel(c *gc.C) { - id := charm.MustParseURL("~bob/wily/django-42") - s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) - stdout, stderr, code := run(c.MkDir(), "publish", id.String(), "-c", "bad-wolf") - c.Assert(stderr, gc.Matches, `ERROR cannot publish charm or bundle: cannot publish to "bad-wolf"\n`) - c.Assert(stdout, gc.Equals, "") - c.Assert(code, gc.Equals, 1) -} - -func (s *publishSuite) TestPublishSuccess(c *gc.C) { - id := charm.MustParseURL("~bob/wily/django-42") - - // Upload a charm. - s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) - // The stable entity is not published yet. - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) - - // Publish the newly uploaded charm to the development channel. - stdout, stderr, code := run(c.MkDir(), "publish", id.String(), "-c", "development") - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: development\n") - c.Assert(code, gc.Equals, 0) - // The stable channel is not yet published, the development channel is. - c.Assert(s.entityRevision(id.WithRevision(-1), params.DevelopmentChannel), gc.Equals, 42) - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) - - // Publish the newly uploaded charm to the stable channel. - stdout, stderr, code = run(c.MkDir(), "publish", id.String(), "-c", "stable") - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: stable\nwarning: bugs-url and homepage are not set. See set command.\n") - c.Assert(code, gc.Equals, 0) - // Both development and stable channels are published. - c.Assert(s.entityRevision(id.WithRevision(-1), params.DevelopmentChannel), gc.Equals, 42) - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) - - // Publishing is idempotent. - stdout, stderr, code = run(c.MkDir(), "publish", id.String(), "-c", "stable") - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: stable\nwarning: bugs-url and homepage are not set. See set command.\n") - c.Assert(code, gc.Equals, 0) - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) -} - -func (s *publishSuite) TestPublishWithDefaultChannelSuccess(c *gc.C) { - id := charm.MustParseURL("~bob/wily/django-42") - - // Upload a charm. - s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) - // The stable entity is not published yet. - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) - stdout, stderr, code := run(c.MkDir(), "publish", id.String()) - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: stable\nwarning: bugs-url and homepage are not set. See set command.\n") - c.Assert(code, gc.Equals, 0) - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) -} - -var publishDefaultChannelWarnings = []struct { - about string - commonFields map[string]interface{} - name string - warning string -}{{ - about: "missing bugs-url and homepage", - name: "foo", - warning: "warning: bugs-url and homepage are not set. See set command.\n", -}, { - about: "missing homepage", - commonFields: map[string]interface{}{"bugs-url": "http://bugs.example.com"}, - name: "bar", - warning: "warning: homepage is not set. See set command.\n", -}, { - about: "missing bugs-url", - commonFields: map[string]interface{}{"homepage": "http://www.example.com"}, - name: "baz", - warning: "warning: bugs-url is not set. See set command.\n", -}, { - about: "not missing things, no warning is displayed", - commonFields: map[string]interface{}{"homepage": "http://www.example.com", - "bugs-url": " http://bugs.example.com"}, - name: "zaz", - warning: "", -}} - -func (s *publishSuite) TestPublishWithDefaultChannelSuccessWithWarningIfBugsUrlAndHomePageAreNotSet(c *gc.C) { - for i, test := range publishDefaultChannelWarnings { - c.Logf("test %d (%s): [%q]", i, test.about, test.commonFields) - id := charm.MustParseURL("~bob/wily/" + test.name + "-42") - - // Upload a charm. - s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) - // Set bugs-url & homepage - err := s.client.PutCommonInfo(id, test.commonFields) - c.Assert(err, gc.IsNil) - // The stable entity is not published yet. - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) - stdout, stderr, code := run(c.MkDir(), "publish", id.String()) - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/"+test.name+"-42\nchannel: stable\n"+test.warning) - c.Assert(code, gc.Equals, 0) - c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) - } -} - -func (s *publishSuite) TestPublishWithNoRevision(c *gc.C) { - id := charm.MustParseURL("~bob/wily/django") - - // Upload a charm. - stdout, stderr, code := run(c.MkDir(), "publish", id.String()) - c.Assert(stderr, gc.Matches, "error: revision needs to be specified\n") - c.Assert(stdout, gc.Equals, "") - c.Assert(code, gc.Equals, 2) -} - -func (s *publishSuite) TestPublishPartialURL(c *gc.C) { - id := charm.MustParseURL("~bob/wily/django-42") - ch := entitytesting.Repo.CharmDir("wordpress") - - // Upload a couple of charms and and publish a stable charm. - s.uploadCharmDir(c, id, -1, ch) - s.uploadCharmDir(c, id.WithRevision(43), -1, ch) - s.publish(c, id, params.StableChannel) - - // Publish the stable charm as development. - stdout, stderr, code := run(c.MkDir(), "publish", "~bob/wily/django-42", "-c", "development") - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: development\n") - c.Assert(code, gc.Equals, 0) - c.Assert(s.entityRevision(id.WithRevision(-1), params.DevelopmentChannel), gc.Equals, 42) -} - -func (s *publishSuite) TestPublishAndShow(c *gc.C) { - id := charm.MustParseURL("~bob/wily/django-42") - ch := entitytesting.Repo.CharmDir("wordpress") - - // Upload a couple of charms and and publish a stable charm. - s.uploadCharmDir(c, id, -1, ch) - s.uploadCharmDir(c, id.WithRevision(43), -1, ch) - - stdout, stderr, code := run(c.MkDir(), "publish", "~bob/wily/django-42", "-c", "development") - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: development\n") - c.Assert(code, gc.Equals, 0) - c.Assert(s.entityRevision(id.WithRevision(-1), params.DevelopmentChannel), gc.Equals, 42) - - stdout, stderr, code = run(c.MkDir(), "show", "--format=json", "~bob/wily/django-42", "published") - c.Assert(stderr, gc.Matches, "") - c.Assert(code, gc.Equals, 0) - var result map[string]interface{} - err := json.Unmarshal([]byte(stdout), &result) - c.Assert(err, gc.IsNil) - c.Assert(len(result), gc.Equals, 1) - c.Assert(result["published"].(map[string]interface{})["Info"].([]interface{})[0], gc.DeepEquals, - map[string]interface{}{"Channel": "development", "Current": true}) -} - -// entityRevision returns the entity revision for the given id and channel. -// The function returns -1 if the entity is not found. -func (s *publishSuite) entityRevision(id *charm.URL, channel params.Channel) int { - client := s.client.WithChannel(channel) - var resp params.IdRevisionResponse - err := client.Get("/"+id.Path()+"/meta/id-revision", &resp) - if err == nil { - return resp.Revision - } - if errgo.Cause(err) == params.ErrNotFound { - return -1 - } - panic(err) -} - -func (s publishSuite) TestRunResource(c *gc.C) { - var ( - actualID *charm.URL - actualResources map[string]int - ) - fakePub := func(client *csclient.Client, id *charm.URL, channels []params.Channel, resources map[string]int) error { - actualID = id - actualResources = resources - return nil - } - s.PatchValue(charmcmd.PublishCharm, fakePub) - - stdout, stderr, code := run(c.MkDir(), "publish", "wordpress-43", "--resource", "foo-3", "--resource", "bar-4") - c.Assert(stderr, gc.Matches, "") - c.Assert(stdout, gc.Equals, "url: cs:wordpress-43\nchannel: stable\nwarning: bugs-url and homepage are not set. See set command.\n") - c.Assert(code, gc.Equals, 0) - - c.Check(actualID, gc.DeepEquals, charm.MustParseURL("wordpress-43")) - c.Check(actualResources, gc.DeepEquals, map[string]int{"foo": 3, "bar": 4}) -} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull.go 2016-04-01 15:19:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull.go 2016-09-03 01:24:14.000000000 +0000 @@ -23,11 +23,9 @@ id *charm.URL destDir string - channel string + channel chanValue - auth string - username string - password string + auth authInfo } // These values are exposed as variables so that @@ -47,7 +45,7 @@ To select a channel, use the --channel option, for instance: - charm pull wordpress --channel development + charm pull wordpress --channel edge ` func (c *pullCommand) Info() *cmd.Info { @@ -60,7 +58,7 @@ } func (c *pullCommand) SetFlags(f *gnuflag.FlagSet) { - addChannelFlag(f, &c.channel) + addChannelFlag(f, &c.channel, nil) addAuthFlag(f, &c.auth) } @@ -82,11 +80,6 @@ } else { c.destDir = id.Name } - - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return errgo.Mask(err) - } return nil } @@ -95,17 +88,17 @@ if _, err := os.Stat(destDir); err == nil || !os.IsNotExist(err) { return errgo.Newf("directory %q already exists", destDir) } - client, err := newCharmStoreClient(ctxt, c.username, c.password) + channel := params.NoChannel + if c.id.Revision == -1 { + channel = c.channel.C + } + client, err := newCharmStoreClient(ctxt, c.auth, channel) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } defer client.jar.Save() - csClient := client.Client - if c.id.Revision == -1 { - csClient = csClient.WithChannel(params.Channel(c.channel)) - } - r, id, expectHash, _, err := clientGetArchive(csClient, c.id) + r, id, expectHash, _, err := clientGetArchive(client.Client, c.id) if err != nil { return err } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull_test.go 2016-03-17 19:42:57.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/pull_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -128,7 +128,7 @@ s.uploadCharmDir(c, url.WithRevision(42), -1, ch) s.uploadCharmDir(c, url.WithRevision(43), -1, ch) s.publish(c, url.WithRevision(41), params.StableChannel) - s.publish(c, url.WithRevision(42), params.DevelopmentChannel) + s.publish(c, url.WithRevision(42), params.EdgeChannel) // Download the stable charm. stdout, stderr, code := run(c.MkDir(), "pull", url.String()) @@ -136,14 +136,14 @@ c.Assert(stdout, gc.Equals, url.WithRevision(41).String()+"\n") c.Assert(code, gc.Equals, 0) - // Download the development charm. - stdout, stderr, code = run(c.MkDir(), "pull", url.String(), "-c", "development") + // Download the edge charm. + stdout, stderr, code = run(c.MkDir(), "pull", url.String(), "-c", "edge") c.Assert(stderr, gc.Equals, "") c.Assert(stdout, gc.Equals, url.WithRevision(42).String()+"\n") c.Assert(code, gc.Equals, 0) // The channel is ignored when specifying a revision. - stdout, stderr, code = run(c.MkDir(), "pull", url.WithRevision(43).String(), "-c", "development") + stdout, stderr, code = run(c.MkDir(), "pull", url.WithRevision(43).String(), "-c", "edge") c.Assert(stderr, gc.Equals, "") c.Assert(stdout, gc.Equals, url.WithRevision(43).String()+"\n") c.Assert(code, gc.Equals, 0) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push.go 2016-04-13 15:17:35.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push.go 2016-09-03 01:24:14.000000000 +0000 @@ -4,34 +4,31 @@ package charmcmd import ( - "bytes" "fmt" "io" "net/mail" "os" "os/exec" "path/filepath" + "sort" + "strconv" "strings" "time" "github.com/juju/cmd" - "github.com/juju/errors" "gopkg.in/errgo.v1" "gopkg.in/juju/charm.v6-unstable" "gopkg.in/juju/charmrepo.v2-unstable/csclient" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "launchpad.net/gnuflag" ) type pushCommand struct { cmd.CommandBase - id *charm.URL - srcDir string - publish bool - - auth string - username string - password string + id *charm.URL + srcDir string + auth authInfo // resources is a map of resource name to filename to be uploaded on push. resources map[string]string @@ -59,6 +56,9 @@ repeated more than once to upload more than one resource. charm push . --resource website=~/some/file.tgz --resource config=./docs/cfg.xml + +See also the attach subcommand, which can be used to push resources +independently of a charm. ` func (c *pushCommand) Info() *cmd.Info { @@ -72,7 +72,8 @@ func (c *pushCommand) SetFlags(f *gnuflag.FlagSet) { addAuthFlag(f, &c.auth) - f.Var(cmd.StringMap{Mapping: &c.resources}, "resource", "resource to be uploaded to the charmstore") + f.Var(cmd.StringMap{Mapping: &c.resources}, "resource", "") + f.Var(cmd.StringMap{Mapping: &c.resources}, "r", "resource to be uploaded to the charmstore") } func (c *pushCommand) Init(args []string) error { @@ -84,36 +85,29 @@ } c.srcDir = args[0] args = args[1:] - if len(args) == 1 { - id, err := charm.ParseURL(args[0]) - if err != nil { - return errgo.Notef(err, "invalid charm or bundle id %q", args[0]) - } - if id.Revision != -1 { - return errgo.Newf("charm or bundle id %q is not allowed a revision", args[0]) - } - c.id = id + if len(args) == 0 { + return nil } - var err error - c.username, c.password, err = validateAuthFlag(c.auth) + id, err := charm.ParseURL(args[0]) if err != nil { - return errgo.Mask(err) + return errgo.Notef(err, "invalid charm or bundle id %q", args[0]) + } + if id.Revision != -1 { + return errgo.Newf("charm or bundle id %q is not allowed a revision", args[0]) } + c.id = id return nil } func (c *pushCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, params.NoChannel) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } defer client.jar.Save() // Retrieve the source directory where the charm or bundle lives. - srcDir, err := filepath.Abs(c.srcDir) - if err != nil { - return errgo.Notef(err, "cannot access charm or bundle") - } + srcDir := ctxt.AbsPath(c.srcDir) if _, err := os.Stat(srcDir); err != nil { return errgo.Notef(err, "cannot access charm or bundle") } @@ -130,7 +124,7 @@ if c.id.User == "" { resp, err := client.WhoAmI() if err != nil { - return errgo.Notef(err, "cannot retrieve identity") + return errgo.Notef(err, "cannot retrieve current username") } c.id.User = resp.User } @@ -157,6 +151,12 @@ c.id.Series = "bundle" } } + if ch != nil { + // Validate resources before pushing the charm. + if err := validateResources(c.resources, ch.Meta()); err != nil { + return errgo.Mask(err) + } + } // Upload the entity if we've found one. switch { case err != nil: @@ -178,7 +178,7 @@ fmt.Fprintln(ctxt.Stdout, "channel: unpublished") // Update the new charm or bundle with VCS extra information. - if err = updateExtraInfo(c.id, srcDir, client); err != nil { + if err := updateExtraInfo(c.id, srcDir, client); err != nil { return errgo.Notef(err, "cannot add extra information") } @@ -192,12 +192,15 @@ } func (c *pushCommand) pushResources(ctxt *cmd.Context, client *csclient.Client, meta *charm.Meta, stdout io.Writer) error { - if err := validateResources(c.resources, meta); err != nil { - return errgo.Mask(err) - } - - for name, filename := range c.resources { - filename = ctxt.AbsPath(filename) + // Upload resources in alphabetical order so we do things + // deterministically. + resourceNames := make([]string, 0, len(c.resources)) + for name := range c.resources { + resourceNames = append(resourceNames, name) + } + sort.Strings(resourceNames) + for _, name := range resourceNames { + filename := ctxt.AbsPath(c.resources[name]) if err := c.uploadResource(client, name, filename, stdout); err != nil { return errgo.Mask(err) } @@ -211,7 +214,7 @@ return errgo.Mask(err) } defer f.Close() - rev, err := uploadResource(client, c.id, name, file, f) + rev, err := client.UploadResource(c.id, name, file, f) if err != nil { return errgo.Mask(err) } @@ -231,187 +234,142 @@ } switch { case len(unknown) > 1: - return errors.Errorf("unrecognized resources: %s", strings.Join(unknown, ", ")) + sort.Strings(unknown) // Ensure deterministic output. + return errgo.Newf("unrecognized resources: %s", strings.Join(unknown, ", ")) case len(unknown) == 1: - return errors.Errorf("unrecognized resource %q", unknown[0]) + return errgo.Newf("unrecognized resource %q", unknown[0]) default: return nil } } -type LogEntry struct { - Commit string - Name string - Email string - Date time.Time - Message string +func updateExtraInfo(id *charm.URL, srcDir string, client *csClient) error { + info := getExtraInfo(srcDir) + if info == nil { + return nil + } + return client.PutExtraInfo(id, info) } -func getExtraInfo(srcDir string) map[string]interface{} { - for _, vcs := range []struct { - dir string - f func(string) (map[string]interface{}, error) - }{ - {".git", gitParseLog}, - {".bzr", bzrParseLog}, - {".hg", hgParseLog}, - } { - var vcsRevisions map[string]interface{} - if _, err := os.Stat(filepath.Join(srcDir, vcs.dir)); !os.IsNotExist(err) { - vcsRevisions, err = vcs.f(srcDir) - if err == nil { - return vcsRevisions - } else { - fmt.Fprintf(os.Stderr, "%v\n", err) - } - - } - } - return nil +type vcsRevision struct { + Authors []vcsAuthor `json:"authors"` + Date time.Time `json:"date"` + Message string `json:"message,omitempty"` + Commit string `json:"commit,omitempty"` + Revno int `json:"revno,omitempty"` } -func updateExtraInfo(id *charm.URL, srcDir string, client *csClient) (err error) { - vcsRevisions := getExtraInfo(srcDir) - if vcsRevisions != nil { - err = client.PutExtraInfo(id, vcsRevisions) - } - return +type vcsAuthor struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` } -func gitParseLog(srcDir string) (map[string]interface{}, error) { - // get the last 10 log in a format with unit and record separator (ASCII 30, 31) - cmd := exec.Command("git", "log", "-n10", `--pretty=format:%H%x1F%an%x1F%ae%x1F%ai%x1F%B%x1E`) - cmd.Dir = srcDir - var buf bytes.Buffer - cmd.Stdout = &buf - cmd.Stderr = &buf +const ( + // entrySep holds the string used to separate individual + // commits in the formatted log output. + entrySep = "\x1E" + // fieldSep holds the string used to separate fields + // in each entry of the formatted log output. + fieldSep = "\x1F" +) - if err := cmd.Run(); err != nil { - return nil, errgo.Notef(err, "git command failed") - } - logs, err := parseGitLog(&buf) - if err != nil { - return nil, err - } - return mapLogEntriesToVcsRevisions(logs), nil -} +var gitLogFormat = strings.Join([]string{ + "%H", + "%an", + "%ae", + "%ai", + "%B", +}, fieldSep) + entrySep + +var hgLogFormat = strings.Join([]string{ + "{node|short}", + "{author|person}", + "{author|email}", + "{date|isodatesec}", + "{desc}", +}, fieldSep) + entrySep + "\n" + +type vcsLogParser struct { + name string + parse func(output string) ([]vcsRevision, error) + args []string +} + +var vcsLogParsers = []vcsLogParser{{ + name: "git", + parse: parseLogOutput, + args: []string{"log", "-n10", "--pretty=format:" + gitLogFormat}, +}, { + name: "bzr", + parse: parseBzrLogOutput, + args: []string{"log", "-l10", "--show-ids"}, +}, { + name: "hg", + parse: parseLogOutput, + args: []string{"log", "-l10", "--template", hgLogFormat}, +}} -func bzrParseLog(srcDir string) (map[string]interface{}, error) { - output, err := bzrLog(srcDir) - if err != nil { - return nil, err - } - logs, err := parseBzrLog(output) - if err != nil { - return nil, err - } - var revisions []map[string]interface{} - for _, log := range logs { - as, err := parseEmailAddresses(getApparentAuthors(log)) - if err != nil { - return nil, err +func getExtraInfo(srcDir string) map[string]interface{} { + for _, vcs := range vcsLogParsers { + if _, err := os.Stat(filepath.Join(srcDir, "."+vcs.name)); err != nil { + continue } - authors := []map[string]interface{}{} - for _, a := range as { - authors = append(authors, map[string]interface{}{ - "name": a.Name, - "email": a.Email, - }) - } - revisions = append(revisions, map[string]interface{}{ - "authors": authors, - "date": log.Timestamp, - "message": log.Message, - "revno": log.Revno, - }) - } - vcsRevisions := map[string]interface{}{ - "vcs-revisions": revisions, + revisions, err := vcs.getRevisions(srcDir) + if err == nil { + return map[string]interface{}{ + "vcs-revisions": revisions, + } + } + logger.Errorf("cannot parse %s log: %v", vcs.name, err) } - return vcsRevisions, nil + return nil } -func hgParseLog(srcDir string) (map[string]interface{}, error) { - cmd := exec.Command("hg", "log", "-l10", "--template", "{node|short}\x1F{author|person}\x1F{author|email}\x1F{date|isodatesec}\x1F{desc}\x1E\x0a") +func (p vcsLogParser) getRevisions(srcDir string) ([]vcsRevision, error) { + cmd := exec.Command(p.name, p.args...) cmd.Dir = srcDir - var buf bytes.Buffer - cmd.Stdout = &buf - cmd.Stderr = &buf - - if err := cmd.Run(); err != nil { - return nil, errgo.Notef(err, "hg command failed") - } - logs, err := parseGitLog(&buf) + output, err := cmd.CombinedOutput() if err != nil { - return nil, err + return nil, errgo.Notef(err, "%s command failed", p.name) } - return mapLogEntriesToVcsRevisions(logs), nil -} - -func mapLogEntriesToVcsRevisions(logs []LogEntry) map[string]interface{} { - var revisions []map[string]interface{} - for _, log := range logs { - authors := []map[string]interface{}{{ - "name": log.Name, - "email": log.Email, - }} - revisions = append(revisions, map[string]interface{}{ - "authors": authors, - "date": log.Date, - "message": log.Message, - "revno": log.Commit, - }) - } - vcsRevisions := map[string]interface{}{ - "vcs-revisions": revisions, + revisions, err := p.parse(string(output)) + if err != nil { + return nil, outputErr(output, err) } - return vcsRevisions + return revisions, nil } -func parseGitLog(buf *bytes.Buffer) ([]LogEntry, error) { - var logs []LogEntry - // Split on unit separator - commits := bytes.Split(buf.Bytes(), []byte("\x1E")) +// parseLogOutput parses the log output from bzr or git. +// Each commit is terminated with entrySep, and within +// a commit, each field is separated with fieldSep. +func parseLogOutput(output string) ([]vcsRevision, error) { + var entries []vcsRevision + commits := strings.Split(output, entrySep) for _, commit := range commits { - commit = bytes.TrimSpace(commit) + commit = strings.TrimSpace(commit) if string(commit) == "" { continue } // Split on record separator - fields := bytes.Split(commit, []byte("\x1F")) - date, err := time.Parse("2006-01-02 15:04:05 -0700", string(fields[3])) + fields := strings.Split(commit, fieldSep) + if len(fields) < 5 { + return nil, errgo.Newf("unexpected field count in commit log") + } + date, err := time.Parse("2006-01-02 15:04:05 -0700", fields[3]) if err != nil { - return nil, err + return nil, errgo.Notef(err, "cannot parse commit timestamp") } - logs = append(logs, LogEntry{ - Commit: string(fields[0]), - Name: string(fields[1]), - Email: string(fields[2]), + entries = append(entries, vcsRevision{ + Authors: []vcsAuthor{{ + Name: fields[1], + Email: fields[2], + }}, Date: date, - Message: string(fields[4]), + Message: fields[4], + Commit: fields[0], }) } - return logs, nil -} - -type bzrLogEntry struct { - Revno string - Author string - Committer string - BranchNick string - Timestamp time.Time - Message string -} - -// bzrLog returns 10 most recent entries ofthe Bazaar log for the branch in branchDir. -func bzrLog(branchDir string) ([]byte, error) { - cmd := exec.Command("bzr", "log", "-l10") - cmd.Dir = branchDir - output, err := cmd.CombinedOutput() - if err != nil { - return nil, outputErr(output, err) - } - return output, nil + return entries, nil } // outputErr returns an error that assembles some command's output and its @@ -423,76 +381,114 @@ return err } -// parseBzrLog reads the raw bytes output from bzr log and returns -// a slice of bzrLogEntry. -func parseBzrLog(bzrlog []byte) ([]bzrLogEntry, error) { - logs := bytes.Split(bzrlog, []byte("------------------------------------------------------------")) +// bzrLogDivider holds the text dividing individual bzr log entries. +// TODO this isn't sufficient, because log output can have +// this divider inside messages. Instead, it would probably +// be better to parse on a line-by-line basis. +const bzrLogDivider = "------------------------------------------------------------" + +// parseBzrLogOutput reads the raw bytes output from bzr log and returns +// the entries to be added to extra-info. +func parseBzrLogOutput(bzrlog string) ([]vcsRevision, error) { + logs := strings.Split(bzrlog, bzrLogDivider) lastindex := len(logs) - 1 - if string(logs[lastindex]) == "\nUse --include-merged or -n0 to see merged revisions.\n" { + if logs[lastindex] == "\nUse --include-merged or -n0 to see merged revisions.\n" { logs = logs[:lastindex] } - logs = logs[1:] - entries := make([]bzrLogEntry, len(logs)) - for i, log := range logs { - var entry bzrLogEntry - err := parseBzrLogEntry(log, &entry) - entries[i] = entry + // Skip the empty item before the first divider. + if len(logs) > 0 && logs[0] == "" { + logs = logs[1:] + } + if len(logs) == 0 { + return nil, errgo.Newf("no log entries") + } + var revisions []vcsRevision + for _, log := range logs { + entry, err := parseBzrLogEntry(log) if err != nil { - return nil, errgo.Notef(err, "cannot parse bzr log entry %s", log) + return nil, errgo.Notef(err, "cannot parse bzr log entry") } + revisions = append(revisions, vcsRevision{ + Date: entry.Date, + Message: entry.Message, + Revno: entry.Revno, + Commit: entry.Commit, + Authors: parseEmailAddresses(getApparentAuthors(entry)), + }) } - return entries, nil + return revisions, nil } -func parseBzrLogEntry(entryBytes []byte, entry *bzrLogEntry) error { - lines := bytes.Split(entryBytes, []byte("\n")) +type bzrLogEntry struct { + Revno int + Commit string + Author string + Committer string + Date time.Time + Message string +} + +// parseBzrLogEntry parses a single bzr log commit entry. +func parseBzrLogEntry(entryText string) (bzrLogEntry, error) { + var entry bzrLogEntry + lines := strings.Split(entryText, "\n") for i, line := range lines { - if strings.TrimSpace(string(line)) == "" { + if strings.TrimSpace(line) == "" { continue } - kvp := strings.SplitN(string(line), ":", 2) + kvp := strings.SplitN(line, ":", 2) if len(kvp) != 2 { - logger.Errorf("unexpected line: %s", string(line)) + logger.Errorf("unexpected line in bzr output: %q", string(line)) continue } val := strings.TrimLeft(kvp[1], " ") switch kvp[0] { case "revno": - entry.Revno = val + revno, err := strconv.Atoi(val) + if err != nil { + return bzrLogEntry{}, errgo.Newf("invalid revision number %q", val) + } + entry.Revno = revno + case "revision-id": + entry.Commit = val case "author": entry.Author = val case "committer": entry.Committer = val - case "branch nick": - entry.BranchNick = val case "timestamp": t, err := time.Parse("Mon 2006-01-02 15:04:05 Z0700", val) if err != nil { - logger.Errorf("cannot parse timestamp %q: %v", val, err) - } else { - entry.Timestamp = t + return bzrLogEntry{}, errgo.Mask(err) } + entry.Date = t case "message": - entry.Message = string(bytes.Join(lines[i+1:], []byte("\n"))) - return nil + // TODO this doesn't preserve the message intact. We + // should strip off the final \n and the leading two + // space characters from each line. + entry.Message = strings.Join(lines[i+1:], "\n") + return entry, nil } } - return nil + return bzrLogEntry{}, errgo.Newf("no commit message found in bzr log entry") } // parseEmailAddresses is not as flexible as // https://hg.python.org/cpython/file/430aaeaa8087/Lib/email/utils.py#l222 -func parseEmailAddresses(emails string) ([]author, error) { +func parseEmailAddresses(emails string) []vcsAuthor { addresses, err := mail.ParseAddressList(emails) if err != nil { - return nil, err + // The address list is invalid. Don't abort because of + // this - just add the email as a name. + return []vcsAuthor{{ + Name: emails, + }} } - authors := make([]author, len(addresses)) + authors := make([]vcsAuthor, len(addresses)) for i, address := range addresses { authors[i].Name = address.Name authors[i].Email = address.Address } - return authors, nil + return authors } // getApparentAuthors returns author if it is not empty otherwise, returns committer @@ -505,8 +501,3 @@ } return authors } - -type author struct { - Email string `json:"email"` - Name string `json:"name"` -} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_internal_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_internal_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_internal_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_internal_test.go 2016-04-28 06:02:24.000000000 +0000 @@ -0,0 +1,499 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the GPLv3, see LICENCE file for details. + +package charmcmd + +import ( + "encoding/json" + "io/ioutil" + "os/exec" + "strings" + "time" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" +) + +type pushInternalSuite struct { +} + +var _ = gc.Suite(&pushInternalSuite{}) + +var parseLogOutputTests = []struct { + about string + vcsName string + output string + expectRevisions []vcsRevision + expectError string +}{{ + about: "hg output", + vcsName: "hg", + output: `0e68f6fcfa75Jay R. Wrenjrwren@xmtp.net2015-09-01 10:39:01 -0500now I have a user name62755f248a17jrwrenjrwren@xmtp.net2015-09-01 10:31:01 -0500' " and a quote and 🍺 and a smile + +right ?5b6c84261061jrwrenjrwren@xmtp.net2015-09-01 10:29:01 -0500ladidadi`, + expectRevisions: []vcsRevision{{ + Authors: []vcsAuthor{{ + Name: "Jay R. Wren", + Email: "jrwren@xmtp.net", + }}, + Commit: "0e68f6fcfa75", + Message: "now I have a user name", + Date: mustParseTime("2015-09-01T10:39:01-05:00"), + }, { + Authors: []vcsAuthor{{ + Name: "jrwren", + Email: "jrwren@xmtp.net", + }}, + Commit: "62755f248a17", + Message: `' " and a quote and 🍺 and a smile + +right ?`, + Date: mustParseTime("2015-09-01T10:31:01-05:00"), + }, { + Authors: []vcsAuthor{{ + Name: "jrwren", + Email: "jrwren@xmtp.net", + }}, + Commit: "5b6c84261061", + Message: "ladidadi", + Date: mustParseTime("2015-09-01T10:29:01-05:00"), + }}, +}, { + about: "git output", + vcsName: "git", + output: ` 6827b561164edbadf9e063e86aa5bddf9ff5d82eJay R. Wrenjrwren@xmtp.net2015-08-31 14:24:26 -0500this is a commit + +hello! + +050371d9213fee776b85e4ce40bf13e1a9fec4f8Jay R. Wrenjrwren@xmtp.net2015-08-31 13:54:59 -0500silly" +complex +log +message +' +😁 + +02f607004604568640ea0a126f0022789070cfc3Jay R. Wrenjrwren@xmtp.net2015-08-31 12:05:32 -0500try 2 + +11cc03952eb993b6b7879f6e62049167678ff14dJay R. Wrenjrwren@xmtp.net2015-08-31 12:03:39 -0500hello fabrice + +`, + expectRevisions: []vcsRevision{{ + Authors: []vcsAuthor{{ + Name: "Jay R. Wren", + Email: "jrwren@xmtp.net", + }}, + Date: mustParseTime("2015-08-31T14:24:26-05:00"), + Commit: "6827b561164edbadf9e063e86aa5bddf9ff5d82e", + Message: `this is a commit + +hello!`, + }, { + Authors: []vcsAuthor{{ + Name: "Jay R. Wren", + Email: "jrwren@xmtp.net", + }}, + Date: mustParseTime("2015-08-31T13:54:59-05:00"), + Commit: "050371d9213fee776b85e4ce40bf13e1a9fec4f8", + Message: `silly" +complex +log +message +' +😁`, + }, { + Authors: []vcsAuthor{{ + Name: "Jay R. Wren", + Email: "jrwren@xmtp.net", + }}, + Date: mustParseTime("2015-08-31T12:05:32-05:00"), + Commit: "02f607004604568640ea0a126f0022789070cfc3", + Message: `try 2`, + }, { + Authors: []vcsAuthor{{ + Name: "Jay R. Wren", + Email: "jrwren@xmtp.net", + }}, + Date: mustParseTime("2015-08-31T12:03:39-05:00"), + Commit: "11cc03952eb993b6b7879f6e62049167678ff14d", + Message: "hello fabrice", + }}, +}, { + about: "bzr output", + vcsName: "bzr", + output: `------------------------------------------------------------ +revno: 57 +revision-id: roger.peppe@canonical.com-20160414165404-imszq8k3gr0ntfql +parent: roger.peppe@canonical.com-20160414164658-u8pg4k6r8ezd7b8d +author: Mary Smith , jdoe@example.org, Who? +committer: Roger Peppe +branch nick: wordpress +timestamp: Thu 2016-04-14 17:54:04 +0100 +message: + multiple authors +------------------------------------------------------------ +revno: 56 +revision-id: roger.peppe@canonical.com-20160414164658-u8pg4k6r8ezd7b8d +parent: roger.peppe@canonical.com-20160414164354-xq2xleb0e9hvyzem +author: A. N. Other +committer: Roger Peppe +branch nick: wordpress +timestamp: Thu 2016-04-14 17:46:58 +0100 +message: + hello +------------------------------------------------------------ +revno: 55 +revision-id: roger.peppe@canonical.com-20160414164354-xq2xleb0e9hvyzem +parent: clint@ubuntu.com-20120618202816-c0iuyzr7nrwowwpv +committer: Roger Peppe +branch nick: wordpress +timestamp: Thu 2016-04-14 17:43:54 +0100 +message: + A commit message + with some extra lines + And some indentation. + And quotes '$. +------------------------------------------------------------ +revno: 54 +revision-id: clint@ubuntu.com-20120618202816-c0iuyzr7nrwowwpv +parent: clint@fewbar.com-20120618145422-ebta2xe3djn55ovf +committer: Clint Byrum +branch nick: wordpress +timestamp: Mon 2012-06-18 13:28:16 -0700 +message: + Fixing so wordpress is configured as the only thing on port 80 +------------------------------------------------------------ +revno: 53 +revision-id: clint@fewbar.com-20120618145422-ebta2xe3djn55ovf +parent: clint@ubuntu.com-20120522223500-lcjebki0k1oynz02 +committer: Clint Byrum +branch nick: wordpress +timestamp: Mon 2012-06-18 07:54:22 -0700 +message: + fixing website relation for providers who set invalid hostnames (local) +`, + expectRevisions: []vcsRevision{{ + Authors: []vcsAuthor{{ + Name: "Mary Smith", + Email: "mary@x.test", + }, { + Email: "jdoe@example.org", + }, { + Name: "Who?", + Email: "one@y.test", + }}, + Date: mustParseTime("2016-04-14T17:54:04+01:00"), + Commit: "roger.peppe@canonical.com-20160414165404-imszq8k3gr0ntfql", + Revno: 57, + Message: " multiple authors\n", + }, { + Authors: []vcsAuthor{{ + Name: "A. N. Other", + Email: "another@nowhere.com", + }}, + Date: mustParseTime("2016-04-14T17:46:58+01:00"), + Commit: "roger.peppe@canonical.com-20160414164658-u8pg4k6r8ezd7b8d", + Revno: 56, + Message: " hello\n", + }, { + Authors: []vcsAuthor{{ + Name: "Roger Peppe", + Email: "roger.peppe@canonical.com", + }}, + Date: mustParseTime("2016-04-14T17:43:54+01:00"), + Commit: "roger.peppe@canonical.com-20160414164354-xq2xleb0e9hvyzem", + Revno: 55, + Message: ` A commit message + with some extra lines + And some indentation. + And quotes '$. +`, + }, { + Authors: []vcsAuthor{{ + Name: "Clint Byrum", + Email: "clint@ubuntu.com", + }}, + Date: mustParseTime("2012-06-18T13:28:16-07:00"), + Commit: "clint@ubuntu.com-20120618202816-c0iuyzr7nrwowwpv", + Revno: 54, + Message: ` Fixing so wordpress is configured as the only thing on port 80 +`, + }, { + Authors: []vcsAuthor{{ + Name: "Clint Byrum", + Email: "clint@fewbar.com", + }}, + Date: mustParseTime("2012-06-18T07:54:22-07:00"), + Commit: "clint@fewbar.com-20120618145422-ebta2xe3djn55ovf", + Revno: 53, + Message: ` fixing website relation for providers who set invalid hostnames (local) +`, + }}, +}, { + about: "bad email address", + vcsName: "bzr", + output: `------------------------------------------------------------ +revno: 58 +author: Foo +branch nick: wordpress +timestamp: Fri 2016-04-15 08:18:19 +0100 +message: + something +`, + expectRevisions: []vcsRevision{{ + Authors: []vcsAuthor{{ + Name: "Foo +bad line +timestamp: Mon 2012-06-18 13:28:16 -0700 +message: + x +`, + expectRevisions: []vcsRevision{{ + Authors: []vcsAuthor{{ + Name: "Clint Byrum", + Email: "clint@ubuntu.com", + }}, + Date: mustParseTime("2012-06-18T13:28:16-07:00"), + Revno: 54, + Message: " x\n", + }}, +}, { + about: "bad timestamp in bzr output", + vcsName: "bzr", + output: `------------------------------------------------------------ +revno: 54 +committer: Clint Byrum +bad line +timestamp: Mon 2012-06-18 13:28:16 +message: + Fixing so wordpress is configured as the only thing on port 80 +`, + expectError: `cannot parse bzr log entry: parsing time "Mon 2012-06-18 13:28:16" as "Mon 2006-01-02 15:04:05 Z0700": cannot parse "" as "Z0700"`, +}, { + about: "no message in bzr output", + vcsName: "bzr", + output: `------------------------------------------------------------ +revno: 54 +committer: Clint Byrum +`, + expectError: "cannot parse bzr log entry: no commit message found in bzr log entry", +}, { + about: "bzr output with trailer", + vcsName: "bzr", + output: `------------------------------------------------------------ +revno: 1 +committer: kapil.thangavelu@canonical.com +branch nick: trunk +timestamp: Tue 2011-02-01 12:40:51 -0500 +message: + wordpress and mysql formulas with tongue in cheek descriptions. +------------------------------------------------------------ +Use --include-merged or -n0 to see merged revisions. +`, + expectRevisions: []vcsRevision{{ + Authors: []vcsAuthor{{ + Email: "kapil.thangavelu@canonical.com", + }}, + Date: mustParseTime("2011-02-01T12:40:51-05:00"), + Revno: 1, + Message: ` wordpress and mysql formulas with tongue in cheek descriptions. +`, + }}, +}} + +func (s *pushInternalSuite) TestParseLogOutput(c *gc.C) { + for i, test := range parseLogOutputTests { + c.Logf("test %d: %s", i, test.about) + var parse func(output string) ([]vcsRevision, error) + for _, p := range vcsLogParsers { + if p.name == test.vcsName { + parse = p.parse + break + } + } + c.Assert(parse, gc.NotNil) + revs, err := parse(test.output) + if test.expectError != "" { + c.Assert(err, gc.ErrorMatches, test.expectError) + continue + } + assertEqualRevisions(c, revs, test.expectRevisions) + } +} + +func (s *pushInternalSuite) TestVCSRevisionJSONMarshal(c *gc.C) { + rev := vcsRevision{ + Authors: []vcsAuthor{{ + Email: "marco@ceppi.net", + Name: "Marco Ceppi", + }}, + Date: mustParseTime("2015-09-03T18:17:50Z"), + Message: "made tags", + Commit: "some-commit", + Revno: 84, + } + data, err := json.Marshal(rev) + c.Assert(err, gc.IsNil) + var got interface{} + err = json.Unmarshal(data, &got) + c.Assert(err, gc.IsNil) + + wantJSON := ` + { + "authors": [ + { + "email": "marco@ceppi.net", + "name": "Marco Ceppi" + } + ], + "date": "2015-09-03T18:17:50Z", + "message": "made tags", + "commit": "some-commit", + "revno": 84 + } + ` + var want interface{} + err = json.Unmarshal([]byte(wantJSON), &want) + c.Assert(err, gc.IsNil) + + c.Assert(got, jc.DeepEquals, want) +} + +func assertEqualRevisions(c *gc.C, got, want []vcsRevision) { + // Deal with times separately because we can't use DeepEquals. + wantTimes := make([]time.Time, len(want)) + for i := range want { + wantTimes[i] = want[i].Date + want[i].Date = time.Time{} + } + gotTimes := make([]time.Time, len(got)) + for i := range got { + gotTimes[i] = got[i].Date + got[i].Date = time.Time{} + } + c.Assert(got, jc.DeepEquals, want) + for i := range got { + if !gotTimes[i].Equal(wantTimes[i]) { + c.Errorf("time mismatch in entry %d; got %v want %v", i, gotTimes[i], wantTimes[i]) + } + } +} + +func (s *pushInternalSuite) TestUpdateExtraInfoGit(c *gc.C) { + tempDir := c.MkDir() + git(c, tempDir, "init") + + err := ioutil.WriteFile(tempDir+"/foo", []byte("bar"), 0600) + c.Assert(err, gc.IsNil) + + git(c, tempDir, "config", "user.name", "test") + git(c, tempDir, "config", "user.email", "test") + git(c, tempDir, "add", "foo") + git(c, tempDir, "commit", "-n", "-madd foo") + + extraInfo := getExtraInfo(tempDir) + c.Assert(extraInfo, gc.NotNil) + commits := extraInfo["vcs-revisions"].([]vcsRevision) + c.Assert(len(commits), gc.Equals, 1) +} + +func (s *pushInternalSuite) TestUpdateExtraInfoHg(c *gc.C) { + tempDir := c.MkDir() + hg(c, tempDir, "init") + + err := ioutil.WriteFile(tempDir+"/foo", []byte("bar"), 0600) + c.Assert(err, gc.IsNil) + + hg(c, tempDir, "add", "foo") + hg(c, tempDir, "commit", "-madd foo") + + extraInfo := getExtraInfo(tempDir) + c.Assert(extraInfo, gc.NotNil) + commits := extraInfo["vcs-revisions"].([]vcsRevision) + c.Assert(len(commits), gc.Equals, 1) +} + +func (s *pushInternalSuite) TestUpdateExtraInfoBzr(c *gc.C) { + tempDir := c.MkDir() + bzr(c, tempDir, "init") + + err := ioutil.WriteFile(tempDir+"/foo", []byte("bar"), 0600) + c.Assert(err, gc.IsNil) + + bzr(c, tempDir, "whoami", "--branch", "Someone ") + bzr(c, tempDir, "add", "foo") + bzr(c, tempDir, "commit", "-madd foo") + + extraInfo := getExtraInfo(tempDir) + c.Assert(extraInfo, gc.NotNil) + commits := extraInfo["vcs-revisions"].([]vcsRevision) + c.Assert(commits, gc.HasLen, 1) + c.Assert(commits[0].Authors, jc.DeepEquals, []vcsAuthor{{ + Name: "Someone", + Email: "who@nowhere.com", + }}) + c.Assert(commits[0].Message, gc.Equals, " add foo\n") +} + +func git(c *gc.C, tempDir string, arg ...string) { + runVCS(c, tempDir, "git", arg...) +} + +func hg(c *gc.C, tempDir string, arg ...string) { + runVCS(c, tempDir, "hg", arg...) +} + +func bzr(c *gc.C, tempDir string, arg ...string) { + runVCS(c, tempDir, "bzr", arg...) +} + +func runVCS(c *gc.C, tempDir, name string, arg ...string) { + if !vcsAvailable(name) { + c.Skip(name + " command not available") + } + cmd := exec.Command(name, arg...) + cmd.Dir = tempDir + out, err := cmd.CombinedOutput() + c.Assert(err, gc.IsNil, gc.Commentf("output: %q", out)) +} + +var vcsVersionOutput = map[string]string{ + "bzr": "Bazaar (bzr)", + "git": "git version", + "hg": "Mercurial Distributed SCM", +} + +var vcsChecked = make(map[string]bool) + +func vcsAvailable(name string) bool { + if avail, ok := vcsChecked[name]; ok { + return avail + } + expect := vcsVersionOutput[name] + if expect == "" { + panic("unknown VCS name") + } + out, _ := exec.Command(name, "version").CombinedOutput() + avail := strings.HasPrefix(string(out), expect) + vcsChecked[name] = avail + return avail +} + +func mustParseTime(s string) time.Time { + t, err := time.Parse(time.RFC3339, s) + if err != nil { + panic(err) + } + return t +} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_test.go 2016-04-13 15:17:35.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/push_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -4,24 +4,17 @@ package charmcmd_test import ( - "bytes" "fmt" - "io" "io/ioutil" "os" - "os/exec" "path/filepath" - "sort" - "time" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient" "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "gopkg.in/macaroon-bakery.v1/bakery/checkers" - "github.com/juju/charmstore-client/cmd/charm/charmcmd" "github.com/juju/charmstore-client/internal/entitytesting" ) @@ -225,20 +218,13 @@ func (s *pushSuite) TestUploadCharmNoIdFromRelativeDir(c *gc.C) { repo := entitytesting.Repo charmDir := filepath.Join(repo.Path(), "quantal/multi-series") - curDir, err := os.Getwd() - c.Assert(err, gc.IsNil) - err = os.Chdir(charmDir) - c.Assert(err, gc.IsNil) - defer os.Chdir(curDir) - stdout, stderr, code := run(".", "push", ".") + stdout, stderr, code := run(charmDir, "push", ".") c.Assert(stderr, gc.Matches, "") c.Assert(stdout, gc.Equals, "url: cs:~bob/multi-series-0\nchannel: unpublished\n") c.Assert(code, gc.Equals, 0) - err = os.Chdir(filepath.Join(charmDir, "hooks")) - c.Assert(err, gc.IsNil) - stdout, stderr, code = run(".", "push", "../") + stdout, stderr, code = run(filepath.Join(charmDir, "hooks"), "push", "../") c.Assert(stderr, gc.Matches, "") c.Assert(stdout, gc.Equals, "url: cs:~bob/multi-series-0\nchannel: unpublished\n") c.Assert(code, gc.Equals, 0) @@ -289,217 +275,53 @@ c.Assert(code, gc.Equals, 0) } -var gitlog = ` 6827b561164edbadf9e063e86aa5bddf9ff5d82eJay R. Wrenjrwren@xmtp.net2015-08-31 14:24:26 -0500this is a commit - -hello! - -050371d9213fee776b85e4ce40bf13e1a9fec4f8Jay R. Wrenjrwren@xmtp.net2015-08-31 13:54:59 -0500silly" -complex -log -message -' -😁 - -02f607004604568640ea0a126f0022789070cfc3Jay R. Wrenjrwren@xmtp.net2015-08-31 12:05:32 -0500try 2 - -11cc03952eb993b6b7879f6e62049167678ff14dJay R. Wrenjrwren@xmtp.net2015-08-31 12:03:39 -0500hello fabrice - -` - -func (s *pushSuite) TestParseGitLog(c *gc.C) { - commits, err := charmcmd.ParseGitLog(bytes.NewBufferString(gitlog)) - c.Assert(err, gc.IsNil) - c.Assert(len(commits), gc.Equals, 4) - first := commits[3] - c.Assert(first.Name, gc.Equals, "Jay R. Wren") - c.Assert(first.Email, gc.Equals, "jrwren@xmtp.net") - c.Assert(first.Commit, gc.Equals, "11cc03952eb993b6b7879f6e62049167678ff14d") - c.Assert(first.Message, gc.Equals, "hello fabrice") -} - -func (s *pushSuite) TestMapLogEntriesToVcsRevisions(c *gc.C) { - now := time.Now() - revisions := charmcmd.MapLogEntriesToVcsRevisions([]charmcmd.LogEntry{ - { - Name: "bob", - Email: "bob@example.com", - Date: now, - Message: "what you been thinking about", - Commit: "this would be some hash", - }, - { - Name: "alice", - Email: "alice@example.com", - Date: now, - Message: "foo", - Commit: "bar", - }, - }) - revs := revisions["vcs-revisions"].([]map[string]interface{}) - authors0 := revs[0]["authors"].([]map[string]interface{}) - c.Assert(authors0[0]["name"], gc.Equals, "bob") - c.Assert(authors0[0]["email"], gc.Equals, "bob@example.com") - c.Assert(revs[0]["date"], gc.Equals, now) - c.Assert(revs[0]["revno"], gc.Equals, "this would be some hash") - c.Assert(revs[0]["message"], gc.Equals, "what you been thinking about") - //todo fill out - authors1 := revs[1]["authors"].([]map[string]interface{}) - c.Assert(authors1[0]["name"], gc.Equals, "alice") - c.Assert(authors1[0]["email"], gc.Equals, "alice@example.com") -} - -func git(c *gc.C, tempDir string, arg ...string) { - cmd := exec.Command("git", arg...) - cmd.Dir = tempDir - _, err := cmd.Output() - c.Assert(err, gc.IsNil) -} - -// TODO frankban: really test the process of pushing VCS extra information, -// including error cases. There is no need to export internal implementation -// functions. Moreover, Bazaar tests seem to be missing. - -func (s *pushSuite) TestUpdateExtraInfoGit(c *gc.C) { - // Under OSX git is placed in /usr/local/bin by HomeBrew - s.PatchEnvironment("PATH", "/usr/bin:/usr/local/bin") - tempDir := c.MkDir() - git(c, tempDir, "init") - - err := ioutil.WriteFile(tempDir+"/foo", []byte("bar"), 0600) - c.Assert(err, gc.IsNil) - - git(c, tempDir, "config", "user.name", "test") - git(c, tempDir, "config", "user.email", "test") - git(c, tempDir, "add", "foo") - git(c, tempDir, "commit", "-n", "-madd foo") - - extraInfo := charmcmd.GetExtraInfo(tempDir) - c.Assert(extraInfo, gc.NotNil) - commits := extraInfo["vcs-revisions"].([]map[string]interface{}) - c.Assert(len(commits), gc.Equals, 1) -} - -func hg(c *gc.C, tempDir string, arg ...string) { - cmd := exec.Command("hg", arg...) - cmd.Dir = tempDir - err := cmd.Run() - c.Assert(err, gc.IsNil) -} - -func (s *pushSuite) TestUpdateExtraInfoHg(c *gc.C) { - // Under OSX hg is placed in /usr/local/bin by HomeBrew. - s.PatchEnvironment("PATH", "/usr/bin:/usr/local/bin") - tempDir := c.MkDir() - hg(c, tempDir, "init") - - err := ioutil.WriteFile(tempDir+"/foo", []byte("bar"), 0600) - c.Assert(err, gc.IsNil) - - hg(c, tempDir, "add", "foo") - hg(c, tempDir, "commit", "-madd foo") - - extraInfo := charmcmd.GetExtraInfo(tempDir) - c.Assert(extraInfo, gc.NotNil) - commits := extraInfo["vcs-revisions"].([]map[string]interface{}) - c.Assert(len(commits), gc.Equals, 1) -} - -var hglog = `0e68f6fcfa75Jay R. Wrenjrwren@xmtp.net2015-09-01 10:39:01 -0500now I have a user name62755f248a17jrwrenjrwren@xmtp.net2015-09-01 10:31:01 -0500' " and a quote and 🍺 and a smile - -right ?5b6c84261061jrwrenjrwren@xmtp.net2015-09-01 10:29:01 -0500ladidadi` - -func (s *pushSuite) TestParseHgLog(c *gc.C) { - commits, err := charmcmd.ParseGitLog(bytes.NewBufferString(hglog)) - c.Assert(err, gc.IsNil) - c.Assert(len(commits), gc.Equals, 3) - first := commits[2] - c.Assert(first.Name, gc.Equals, "jrwren") - c.Assert(first.Email, gc.Equals, "jrwren@xmtp.net") - c.Assert(first.Commit, gc.Equals, "5b6c84261061") - c.Assert(first.Message, gc.Equals, "ladidadi") - last := commits[0] - c.Assert(last.Name, gc.Equals, "Jay R. Wren") -} - func (s *pushSuite) TestUploadCharmWithResources(c *gc.C) { - // note the revs here correspond to the revs in the stdout check. - f := &fakeUploader{revs: []int{1, 2}} - s.PatchValue(charmcmd.UploadResource, f.UploadResource) - dir := c.MkDir() - datapath := filepath.Join(dir, "data.zip") - websitepath := filepath.Join(dir, "web.html") - err := ioutil.WriteFile(datapath, []byte("hi!"), 0600) + dataPath := filepath.Join(dir, "data.zip") + err := ioutil.WriteFile(dataPath, []byte("data content"), 0666) c.Assert(err, jc.ErrorIsNil) - err = ioutil.WriteFile(websitepath, []byte("hi!"), 0600) + + websitePath := filepath.Join(dir, "web.html") + err = ioutil.WriteFile(websitePath, []byte("web content"), 0666) c.Assert(err, jc.ErrorIsNil) - repo := entitytesting.Repo stdout, stderr, code := run( dir, "push", - filepath.Join(repo.Path(), "quantal/use-resources"), + entitytesting.Repo.CharmDir("use-resources").Path, "~bob/trusty/something", - "--resource", "data="+datapath, - "--resource", "website="+websitepath) + "--resource", "data=data.zip", + "--resource", "website=web.html") c.Assert(stderr, gc.Matches, "") c.Assert(code, gc.Equals, 0) - // Since we store the resources in a map, the order in which they're - // uploaded is nondeterministic, so we need to do some contortions to allow - // for different orders. - if stdout != fmt.Sprintf(` -url: cs:~bob/trusty/something-0 -channel: unpublished -Uploaded %q as data-1 -Uploaded %q as website-2 -`[1:], datapath, websitepath) && stdout != fmt.Sprintf(` + expectOutput := fmt.Sprintf(` url: cs:~bob/trusty/something-0 channel: unpublished -Uploaded %q as website-1 -Uploaded %q as data-2 -`[1:], websitepath, datapath) { - c.Fail() - } - - c.Assert(f.args, gc.HasLen, 2) - sort.Sort(byname(f.args)) - expectedID := charm.MustParseURL("cs:~bob/trusty/something-0") - - c.Check(f.args[0].id, gc.DeepEquals, expectedID) - c.Check(f.args[0].name, gc.Equals, "data") - c.Check(f.args[0].path, gc.Equals, datapath) - c.Check(f.args[0].file, gc.Equals, datapath) +Uploaded %q as data-0 +Uploaded %q as website-0 +`[1:], dataPath, websitePath, + ) + c.Assert(stdout, gc.Equals, expectOutput) - c.Check(f.args[1].id, gc.DeepEquals, expectedID) - c.Check(f.args[1].name, gc.Equals, "website") - c.Check(f.args[1].path, gc.Equals, websitepath) - c.Check(f.args[1].file, gc.Equals, websitepath) -} - -type fakeUploader struct { - // args holds the arguments passed to UploadResource. - args []uploadArgs - // revs holds the revisions returned by UploadResource. - revs []int -} + client := s.client.WithChannel(params.UnpublishedChannel) + resources, err := client.ListResources(charm.MustParseURL("cs:~bob/trusty/something-0")) -func (f *fakeUploader) UploadResource(client *csclient.Client, id *charm.URL, name, path string, file io.ReadSeeker) (revision int, err error) { - fl := file.(*os.File) - f.args = append(f.args, uploadArgs{id, name, path, fl.Name()}) - rev := f.revs[0] - f.revs = f.revs[1:] - return rev, nil -} - -type uploadArgs struct { - id *charm.URL - name string - path string - file string + c.Assert(err, jc.ErrorIsNil) + c.Assert(resources, jc.DeepEquals, []params.Resource{{ + Name: "data", + Type: "file", + Path: "data.zip", + Revision: 0, + Fingerprint: hashOfString("data content"), + Size: int64(len("data content")), + Description: "Some data for your service", + }, { + Name: "website", + Type: "file", + Path: "web.html", + Revision: 0, + Fingerprint: hashOfString("web content"), + Size: int64(len("web content")), + Description: "A website for your service", + }}) } - -type byname []uploadArgs - -func (b byname) Less(i, j int) bool { return b[i].name < b[j].name } -func (b byname) Swap(i, j int) { b[i], b[j] = b[j], b[i] } -func (b byname) Len() int { return len(b) } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release.go 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,182 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the GPLv3, see LICENCE file for details. + +package charmcmd + +import ( + "fmt" + "strconv" + "strings" + + "github.com/juju/cmd" + "gopkg.in/errgo.v1" + "gopkg.in/juju/charm.v6-unstable" + "gopkg.in/juju/charmrepo.v2-unstable/csclient" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + "launchpad.net/gnuflag" +) + +type releaseCommand struct { + cmd.CommandBase + + id *charm.URL + channel chanValue + auth authInfo + + resources resourceMap +} + +var releaseDoc = ` +The release command publishes a charm or bundle to the charm store. +Releasing is the action of assigning one channel to a specific charm +or bundle revision (revision need to be specified), so that it can be shared +with other users and also referenced without specifying the revision. +Four channels are supported: "stable", "candidate", "beta" and "edge"; +the "stable" channel is used by default. + + charm release ~bob/trusty/wordpress + +To select another channel, use the --channel option, for instance: + + charm release ~bob/trusty/wordpress --channel beta + charm release wily/django-42 -c edge --resource website-3 --resource data-2 + +If your charm uses resources, you must specify what revision of each resource +will be published along with the charm, using the --resource flag (one per +resource). Note that resource info is embedded in bundles, so you cannot use +this flag with bundles. + + charm release wily/django-42 --resource website-3 --resource data-2 +` + +func (c *releaseCommand) Info() *cmd.Info { + return &cmd.Info{ + Name: "release", + Args: " [--channel ]", + Purpose: "release a charm or bundle", + Doc: releaseDoc, + } +} + +func (c *releaseCommand) SetFlags(f *gnuflag.FlagSet) { + channels := make([]params.Channel, 0, len(params.OrderedChannels)-1) + for _, ch := range params.OrderedChannels { + if ch != params.UnpublishedChannel { + channels = append(channels, ch) + } + } + c.channel = chanValue{ + C: params.StableChannel, + } + addChannelFlag(f, &c.channel, channels) + addAuthFlag(f, &c.auth) + f.Var(&c.resources, "resource", "") + f.Var(&c.resources, "r", "resource to be published with the charm") +} + +func (c *releaseCommand) Init(args []string) error { + if len(args) == 0 { + return errgo.New("no charm or bundle id specified") + } + if len(args) > 1 { + return errgo.New("too many arguments") + } + + id, err := charm.ParseURL(args[0]) + if err != nil { + return errgo.Notef(err, "invalid charm or bundle id") + } + if id.Revision == -1 { + return errgo.Newf("charm revision needs to be specified") + } + c.id = id + + return nil +} + +var releaseCharm = func(client *csclient.Client, id *charm.URL, channels []params.Channel, resources map[string]int) error { + return client.Publish(id, channels, resources) +} + +func (c *releaseCommand) Run(ctxt *cmd.Context) error { + // Instantiate the charm store client. + client, err := newCharmStoreClient(ctxt, c.auth, params.NoChannel) + if err != nil { + return errgo.Notef(err, "cannot create the charm store client") + } + defer client.jar.Save() + + err = releaseCharm(client.Client, c.id, []params.Channel{c.channel.C}, c.resources) + if err != nil { + return errgo.Notef(err, "cannot release charm or bundle") + } + fmt.Fprintln(ctxt.Stdout, "url:", c.id) + fmt.Fprintln(ctxt.Stdout, "channel:", c.channel.C) + if c.channel.C == params.StableChannel { + var result params.MetaAnyResponse + var unset string + verb := "is" + client.Get("/"+c.id.Path()+"/meta/any?include=common-info", &result) + commonInfo, ok := result.Meta["common-info"].(map[string]interface{}) + if !ok { + unset = "bugs-url and homepage" + verb = "are" + } else { + if v, ok := commonInfo["bugs-url"].(string); !ok || v == "" { + unset = "bugs-url" + } + if v, ok := commonInfo["homepage"].(string); !ok || v == "" { + if unset != "" { + unset += " and " + verb = "are" + } + unset += "homepage" + } + } + if unset != "" { + fmt.Fprintf(ctxt.Stdout, "warning: %s %s not set. See set command.\n", unset, verb) + } + } + return nil +} + +// resourceMap is a type that deserializes a CLI string using gnuflag's Value +// semantics. It expects a name-number pair, and supports multiple copies of the +// flag adding more pairs, though the names must be unique. +type resourceMap map[string]int + +// Set implements gnuflag.Value's Set method by adding a value to the resource +// map. +func (m0 *resourceMap) Set(s string) error { + if *m0 == nil { + *m0 = make(map[string]int) + } + m := *m0 + + idx := strings.LastIndex(s, "-") + if idx == -1 { + return errgo.New("expected name-revision format") + } + name, value := s[0:idx], s[idx+1:] + if len(name) == 0 || len(value) == 0 { + return errgo.New("expected name-revision format") + } + if _, ok := m[name]; ok { + return errgo.New("duplicate resource name") + } + revision, err := strconv.Atoi(value) + if err != nil { + return errgo.New("invalid revision number") + } + m[name] = revision + return nil +} + +// String implements gnuflag.Value's String method. +func (m resourceMap) String() string { + pairs := make([]string, 0, len(m)) + for name, value := range m { + pairs = append(pairs, fmt.Sprintf("%s-%d", name, value)) + } + return strings.Join(pairs, ";") +} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/release_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,314 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the GPLv3, see LICENCE file for details. + +package charmcmd_test + +import ( + "encoding/json" + "strings" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/errgo.v1" + "gopkg.in/juju/charm.v6-unstable" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + charmtesting "gopkg.in/juju/charmrepo.v2-unstable/testing" + "gopkg.in/macaroon-bakery.v1/bakery/checkers" + + "github.com/juju/charmstore-client/internal/entitytesting" +) + +type releaseSuite struct { + commonSuite +} + +var _ = gc.Suite(&releaseSuite{}) + +func (s *releaseSuite) SetUpTest(c *gc.C) { + s.commonSuite.SetUpTest(c) + s.discharge = func(cavId, cav string) ([]checkers.Caveat, error) { + return []checkers.Caveat{ + checkers.DeclaredCaveat("username", "bob"), + }, nil + } +} + +var releaseInitErrorTests = []struct { + about string + args []string + err string +}{{ + about: "empty args", + args: []string{}, + err: "no charm or bundle id specified", +}, { + about: "invalid charm id", + args: []string{"invalid:entity"}, + err: `invalid charm or bundle id: charm or bundle URL has invalid schema: "invalid:entity"`, +}, { + about: "too many args", + args: []string{"wordpress", "foo"}, + err: "too many arguments", +}, { + about: "no resource", + args: []string{"wily/wordpress", "--resource"}, + err: "flag needs an argument: --resource", +}, { + about: "no revision", + args: []string{"wily/wordpress", "--resource", "foo"}, + err: `invalid value "foo" for flag --resource: expected name-revision format`, +}, { + about: "no resource name", + args: []string{"wily/wordpress", "--resource", "-3"}, + err: `invalid value "-3" for flag --resource: expected name-revision format`, +}, { + about: "bad revision number", + args: []string{"wily/wordpress", "--resource", "someresource-bad"}, + err: `invalid value "someresource-bad" for flag --resource: invalid revision number`, +}} + +func (s *releaseSuite) TestInitError(c *gc.C) { + dir := c.MkDir() + for i, test := range releaseInitErrorTests { + c.Logf("test %d: %s; %q", i, test.about, test.args) + args := []string{"release"} + stdout, stderr, code := run(dir, append(args, test.args...)...) + c.Assert(stdout, gc.Equals, "") + c.Assert(stderr, gc.Matches, "error: "+test.err+"\n") + c.Assert(code, gc.Equals, 2) + } +} + +func (s *releaseSuite) TestRunNoSuchCharm(c *gc.C) { + stdout, stderr, code := run(c.MkDir(), "release", "no-such-entity-55", "--channel", "stable") + c.Assert(stdout, gc.Equals, "") + c.Assert(stderr, gc.Matches, "ERROR cannot release charm or bundle: no matching charm or bundle for cs:no-such-entity-55\n") + c.Assert(code, gc.Equals, 1) +} + +func (s *releaseSuite) TestAuthenticationError(c *gc.C) { + id := charm.MustParseURL("~charmers/utopic/wordpress-42") + s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) + stdout, stderr, code := run(c.MkDir(), "release", id.String(), "--channel", "stable") + c.Assert(stdout, gc.Equals, "") + c.Assert(stderr, gc.Matches, `ERROR cannot release charm or bundle: unauthorized: access denied for user "bob"\n`) + c.Assert(code, gc.Equals, 1) +} + +func (s *releaseSuite) TestReleaseInvalidChannel(c *gc.C) { + id := charm.MustParseURL("~bob/wily/django-42") + s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) + stdout, stderr, code := run(c.MkDir(), "release", id.String(), "-c", "bad-wolf") + c.Assert(stderr, gc.Matches, `ERROR cannot release charm or bundle: unrecognized channel "bad-wolf"\n`) + c.Assert(stdout, gc.Equals, "") + c.Assert(code, gc.Equals, 1) +} + +func (s *releaseSuite) TestReleaseSuccess(c *gc.C) { + id := charm.MustParseURL("~bob/wily/django-42") + + // Upload a charm. + s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) + // The stable entity is not released yet. + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) + + // Release the newly uploaded charm to the edge channel. + stdout, stderr, code := run(c.MkDir(), "release", id.String(), "-c", "edge") + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: edge\n") + c.Assert(code, gc.Equals, 0) + // The stable channel is not yet released, the edge channel is. + c.Assert(s.entityRevision(id.WithRevision(-1), params.EdgeChannel), gc.Equals, 42) + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) + + // Release the newly uploaded charm to the stable channel. + stdout, stderr, code = run(c.MkDir(), "release", id.String(), "-c", "stable") + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: stable\nwarning: bugs-url and homepage are not set. See set command.\n") + c.Assert(code, gc.Equals, 0) + // Both edge and stable channels are released. + c.Assert(s.entityRevision(id.WithRevision(-1), params.EdgeChannel), gc.Equals, 42) + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) + + // Releasing is idempotent. + stdout, stderr, code = run(c.MkDir(), "release", id.String(), "-c", "stable") + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: stable\nwarning: bugs-url and homepage are not set. See set command.\n") + c.Assert(code, gc.Equals, 0) + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) +} + +func (s *releaseSuite) TestReleaseWithDefaultChannelSuccess(c *gc.C) { + id := charm.MustParseURL("~bob/wily/django-42") + + // Upload a charm. + s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) + // The stable entity is not released yet. + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) + stdout, stderr, code := run(c.MkDir(), "release", id.String()) + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: stable\nwarning: bugs-url and homepage are not set. See set command.\n") + c.Assert(code, gc.Equals, 0) + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) +} + +var releaseDefaultChannelWarnings = []struct { + about string + commonFields map[string]interface{} + name string + warning string +}{{ + about: "missing bugs-url and homepage", + name: "foo", + warning: "warning: bugs-url and homepage are not set. See set command.\n", +}, { + about: "missing homepage", + commonFields: map[string]interface{}{"bugs-url": "http://bugs.example.com"}, + name: "bar", + warning: "warning: homepage is not set. See set command.\n", +}, { + about: "missing bugs-url", + commonFields: map[string]interface{}{"homepage": "http://www.example.com"}, + name: "baz", + warning: "warning: bugs-url is not set. See set command.\n", +}, { + about: "not missing things, no warning is displayed", + commonFields: map[string]interface{}{"homepage": "http://www.example.com", + "bugs-url": " http://bugs.example.com"}, + name: "zaz", + warning: "", +}} + +func (s *releaseSuite) TestReleaseWithDefaultChannelSuccessWithWarningIfBugsURLAndHomePageAreNotSet(c *gc.C) { + for i, test := range releaseDefaultChannelWarnings { + c.Logf("test %d (%s): [%q]", i, test.about, test.commonFields) + id := charm.MustParseURL("~bob/wily/" + test.name + "-42") + + // Upload a charm. + s.uploadCharmDir(c, id, -1, entitytesting.Repo.CharmDir("wordpress")) + // Set bugs-url & homepage + err := s.client.PutCommonInfo(id, test.commonFields) + c.Assert(err, gc.IsNil) + // The stable entity is not released yet. + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, -1) + stdout, stderr, code := run(c.MkDir(), "release", id.String()) + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/"+test.name+"-42\nchannel: stable\n"+test.warning) + c.Assert(code, gc.Equals, 0) + c.Assert(s.entityRevision(id.WithRevision(-1), params.StableChannel), gc.Equals, 42) + } +} + +func (s *releaseSuite) TestReleaseWithNoRevision(c *gc.C) { + id := charm.MustParseURL("~bob/wily/django") + + // Upload a charm. + stdout, stderr, code := run(c.MkDir(), "release", id.String()) + c.Assert(stderr, gc.Matches, "error: charm revision needs to be specified\n") + c.Assert(stdout, gc.Equals, "") + c.Assert(code, gc.Equals, 2) +} + +func (s *releaseSuite) TestReleasePartialURL(c *gc.C) { + id := charm.MustParseURL("~bob/wily/django-42") + ch := entitytesting.Repo.CharmDir("wordpress") + + // Upload a couple of charms and release a stable charm. + s.uploadCharmDir(c, id, -1, ch) + s.uploadCharmDir(c, id.WithRevision(43), -1, ch) + s.publish(c, id, params.StableChannel) + + // Release the stable charm as edge. + stdout, stderr, code := run(c.MkDir(), "release", "~bob/wily/django-42", "-c", "edge") + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: edge\n") + c.Assert(code, gc.Equals, 0) + c.Assert(s.entityRevision(id.WithRevision(-1), params.EdgeChannel), gc.Equals, 42) +} + +func (s *releaseSuite) TestReleaseAndShow(c *gc.C) { + id := charm.MustParseURL("~bob/wily/django-42") + ch := entitytesting.Repo.CharmDir("wordpress") + + // Upload a couple of charms and release a stable charm. + s.uploadCharmDir(c, id, -1, ch) + s.uploadCharmDir(c, id.WithRevision(43), -1, ch) + + stdout, stderr, code := run(c.MkDir(), "release", "~bob/wily/django-42", "-c", "edge") + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, "url: cs:~bob/wily/django-42\nchannel: edge\n") + c.Assert(code, gc.Equals, 0) + c.Assert(s.entityRevision(id.WithRevision(-1), params.EdgeChannel), gc.Equals, 42) + + stdout, stderr, code = run(c.MkDir(), "show", "--format=json", "~bob/wily/django-42", "published") + c.Assert(stderr, gc.Matches, "") + c.Assert(code, gc.Equals, 0) + var result map[string]interface{} + err := json.Unmarshal([]byte(stdout), &result) + c.Assert(err, gc.IsNil) + c.Assert(len(result), gc.Equals, 1) + c.Assert(result["published"].(map[string]interface{})["Info"].([]interface{})[0], gc.DeepEquals, + map[string]interface{}{"Channel": "edge", "Current": true}) +} + +func (s *releaseSuite) TestReleaseWithResources(c *gc.C) { + // Note we include one resource with a hyphen in the name, + // just to make sure the resource flag parsing code works OK + // in that case. + id, err := s.client.UploadCharm( + charm.MustParseURL("~bob/precise/wordpress"), + charmtesting.NewCharmMeta(charmtesting.MetaWithResources(nil, "resource1-name", "resource2")), + ) + c.Assert(err, gc.IsNil) + + _, err = s.client.UploadResource(id, "resource1-name", "", strings.NewReader("resource1 content")) + c.Assert(err, gc.IsNil) + _, err = s.client.UploadResource(id, "resource2", "", strings.NewReader("resource2 content")) + c.Assert(err, gc.IsNil) + _, err = s.client.UploadResource(id, "resource2", "", strings.NewReader("resource2 content rev 1")) + c.Assert(err, gc.IsNil) + + stdout, stderr, code := run(c.MkDir(), "release", "~bob/precise/wordpress-0", "--resource=resource1-name-0", "-r", "resource2-1") + c.Assert(stderr, gc.Matches, "") + c.Assert(stdout, gc.Equals, ` +url: cs:~bob/precise/wordpress-0 +channel: stable +warning: bugs-url and homepage are not set. See set command. +`[1:]) + c.Assert(code, gc.Equals, 0) + + resources, err := s.client.WithChannel(params.StableChannel).ListResources(id) + c.Assert(err, gc.IsNil) + c.Assert(resources, jc.DeepEquals, []params.Resource{{ + Name: "resource1-name", + Type: "file", + Path: "resource1-name-file", + Revision: 0, + Fingerprint: hashOfString("resource1 content"), + Size: int64(len("resource1 content")), + Description: "resource1-name description", + }, { + Name: "resource2", + Type: "file", + Path: "resource2-file", + Revision: 1, + Fingerprint: hashOfString("resource2 content rev 1"), + Size: int64(len("resource2 content rev 1")), + Description: "resource2 description", + }}) +} + +// entityRevision returns the entity revision for the given id and channel. +// The function returns -1 if the entity is not found. +func (s *releaseSuite) entityRevision(id *charm.URL, channel params.Channel) int { + client := s.client.WithChannel(channel) + var resp params.IdRevisionResponse + err := client.Get("/"+id.Path()+"/meta/id-revision", &resp) + if err == nil { + return resp.Revision + } + if errgo.Cause(err) == params.ErrNotFound { + return -1 + } + panic(err) +} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke.go 2016-09-03 01:24:14.000000000 +0000 @@ -14,13 +14,10 @@ type revokeCommand struct { cmd.CommandBase - id *charm.URL - auth string - username string - password string - + id *charm.URL acl string - channel string + channel chanValue + auth authInfo // Validated options used in Run(...). removeReads []string @@ -40,7 +37,7 @@ To select a channel, use the --channel option, for instance: - charm revoke ~johndoe/wordpress --channel development --acl write fred,bob + charm revoke ~johndoe/wordpress --channel edge --acl write fred,bob ` func (c *revokeCommand) Info() *cmd.Info { @@ -55,7 +52,7 @@ func (c *revokeCommand) SetFlags(f *gnuflag.FlagSet) { addAuthFlag(f, &c.auth) f.StringVar(&c.acl, "acl", "", "read|write") - addChannelFlag(f, &c.channel) + addChannelFlag(f, &c.channel, nil) } func (c *revokeCommand) Init(args []string) error { @@ -97,24 +94,16 @@ c.removeWrites = users } - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return errgo.Mask(err) - } - return nil } func (c *revokeCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, c.channel.C) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } defer client.jar.Save() - if c.channel != "" { - client.Client = client.Client.WithChannel(params.Channel(c.channel)) - } // Perform the request to change the permissions on the charm store. if err := c.changePerms(client); err != nil { return errgo.Mask(err) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke_test.go 2016-03-17 19:42:57.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/revoke_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -202,7 +202,7 @@ s.uploadCharmDir(c, url.WithRevision(43), -1, ch) s.publish(c, url.WithRevision(41), params.StableChannel) - s.publish(c, url.WithRevision(42), params.DevelopmentChannel) + s.publish(c, url.WithRevision(42), params.EdgeChannel) s.setReadPerms(c, url.WithRevision(41), []string{"foo", "bar"}) s.setWritePerms(c, url.WithRevision(41), []string{"foo", "bar"}) @@ -214,8 +214,8 @@ // Prepare the credentials arguments. auth := s.serverParams.AuthUsername + ":" + s.serverParams.AuthPassword - // Test with the development channel - _, stderr, code := run(dir, "revoke", url.String(), "-c", "development", "foo", "--auth", auth) + // Test with the edge channel. + _, stderr, code := run(dir, "revoke", url.String(), "-c", "edge", "foo", "--auth", auth) c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) @@ -225,7 +225,7 @@ c.Assert(s.getReadPerms(c, url.WithRevision(41)), jc.DeepEquals, []string{"foo", "bar"}) c.Assert(s.getWritePerms(c, url.WithRevision(41)), jc.DeepEquals, []string{"foo", "bar"}) - // Test with the stable channel + // Test with the stable channel. _, stderr, code = run(dir, "revoke", url.String(), "bar", "--auth", auth) c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set.go 2016-09-03 01:24:14.000000000 +0000 @@ -10,7 +10,6 @@ "github.com/juju/cmd" "gopkg.in/errgo.v1" "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "launchpad.net/gnuflag" ) @@ -20,11 +19,8 @@ id *charm.URL commonFields map[string]interface{} extraFields map[string]interface{} - channel string - - auth string - username string - password string + channel chanValue + auth authInfo } // allowedCommonFields lists the limited info available for charm show or set. @@ -47,7 +43,7 @@ To select a channel, use the --channel option, for instance: - charm set wordpress someinfo=somevalue --channel development + charm set wordpress someinfo=somevalue --channel edge ` func (c *setCommand) Info() *cmd.Info { @@ -60,7 +56,7 @@ } func (c *setCommand) SetFlags(f *gnuflag.FlagSet) { - addChannelFlag(f, &c.channel) + addChannelFlag(f, &c.channel, nil) addAuthFlag(f, &c.auth) } @@ -93,25 +89,16 @@ return errgo.Mask(err) } - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return errgo.Mask(err) - } - return nil } func (c *setCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, c.channel.C) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } defer client.jar.Save() - if c.channel != "" { - client.Client = client.Client.WithChannel(params.Channel(c.channel)) - } - // TODO: do this atomically with a single PUT meta/any request. if len(c.commonFields) > 0 { if err := client.PutCommonInfo(c.id, c.commonFields); err != nil { diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set_test.go 2016-03-17 19:42:57.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/set_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -65,7 +65,7 @@ err: `invalid set arguments: key "homepage" specified more than once`, }, { args: []string{"wordpress", "name=value", "--auth", "bad-wolf"}, - err: `invalid auth credentials: expected "user:passwd", got "bad-wolf"`, + err: `invalid value "bad-wolf" for flag --auth: invalid auth credentials: expected "user:passwd"`, }} func (s *setSuite) TestInitError(c *gc.C) { @@ -281,7 +281,7 @@ s.uploadCharmDir(c, url.WithRevision(43), -1, ch) s.publish(c, url.WithRevision(41), params.StableChannel) - s.publish(c, url.WithRevision(42), params.DevelopmentChannel) + s.publish(c, url.WithRevision(42), params.EdgeChannel) dir := c.MkDir() @@ -301,14 +301,14 @@ "common-info": map[string]interface{}{}, }, } - // Test with the development channel - _, stderr, code := run(dir, "set", url.String(), "name=value", "-c", "development") + // Test with the edge channel. + _, stderr, code := run(dir, "set", url.String(), "name=value", "-c", "edge") c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) c.Assert(s.getInfo(c, url.WithRevision(42)), jc.JSONEquals, expectDevelopment) c.Assert(s.getInfo(c, url.WithRevision(41)), jc.JSONEquals, expectStable) - // Test with the stable channel + // Test with the stable channel. _, stderr, code = run(dir, "set", url.String(), "name=value1") c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show.go 2016-04-07 19:52:14.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show.go 2016-09-03 01:24:14.000000000 +0000 @@ -19,14 +19,12 @@ cmd.CommandBase out cmd.Output - channel string + channel chanValue id *charm.URL includes []string list bool - auth string - username string - password string + auth authInfo } var showDoc = ` @@ -37,7 +35,7 @@ To select a channel, use the --channel option, for instance: - charm show wordpress --channel development + charm show wordpress --channel edge To specify one or more specific metadatas: @@ -64,7 +62,7 @@ }) f.BoolVar(&c.list, "list", false, "list available metadata endpoints") addAuthFlag(f, &c.auth) - addChannelFlag(f, &c.channel) + addChannelFlag(f, &c.channel, nil) } func (c *showCommand) Init(args []string) error { @@ -85,16 +83,12 @@ return errgo.Notef(err, "invalid charm or bundle id") } c.id = id - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return err - } return nil } func (c *showCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, c.channel.C) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } @@ -121,11 +115,6 @@ } var result params.MetaAnyResponse - - if c.channel != "" { - client.Client = client.Client.WithChannel(params.Channel(c.channel)) - } - path := "/" + c.id.Path() + "/meta/any?" + query.Encode() if err := client.Get(path, &result); err != nil { return errgo.Notef(err, "cannot get metadata from %s", path) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show_test.go 2016-03-17 19:42:57.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/show_test.go 2016-09-03 01:24:14.000000000 +0000 @@ -1,4 +1,4 @@ -// Copyright 2014 Canonical Ltd. +// Copyright 2014-2016 Canonical Ltd. // Licensed under the GPLv3, see LICENCE file for details. package charmcmd_test @@ -10,7 +10,7 @@ gc "gopkg.in/check.v1" "gopkg.in/juju/charm.v6-unstable" "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" - "gopkg.in/yaml.v1" + "gopkg.in/yaml.v2" "github.com/juju/charmstore-client/internal/entitytesting" ) @@ -34,7 +34,7 @@ expectStderr: `error: cannot specify charm or bundle with --list`, }, { args: []string{"wordpress", "--auth", "bad-wolf"}, - expectStderr: `error: invalid auth credentials: expected "user:passwd", got "bad-wolf"`, + expectStderr: `error: invalid value "bad-wolf" for flag --auth: invalid auth credentials: expected "user:passwd"`, }} func (s *showSuite) TestInitError(c *gc.C) { @@ -259,12 +259,12 @@ s.uploadCharmDir(c, url.WithRevision(43), -1, ch) s.publish(c, url.WithRevision(41), params.StableChannel) - s.publish(c, url.WithRevision(42), params.DevelopmentChannel) + s.publish(c, url.WithRevision(42), params.EdgeChannel) dir := c.MkDir() - // Test with the development channel - stdout, stderr, code := run(dir, "show", url.String(), "--format=json", "id-revision", "-c", "development") + // Test with the edge channel. + stdout, stderr, code := run(dir, "show", url.String(), "--format=json", "id-revision", "-c", "edge") c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) @@ -274,7 +274,7 @@ c.Assert(len(result), gc.Equals, 1) c.Assert(result["id-revision"].(map[string]interface{})["Revision"], gc.Equals, float64(42)) - // Test with the stable channel + // Test with the stable channel. stdout, stderr, code = run(dir, "show", url.String(), "--format=json", "id-revision") c.Assert(stderr, gc.Equals, "") c.Assert(code, gc.Equals, 0) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/supercmd.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/supercmd.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/supercmd.go 2016-03-23 02:08:47.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/supercmd.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,83 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the GPLv3, see LICENCE file for details. - -package charmcmd - -import ( - "github.com/juju/cmd" -) - -// commandWithPlugins is a cmd.Command with the ability to check if some -// provided args refer to a registered plugin, and in that case, to properly -// initialize the plugin. -type commandWithPlugins interface { - cmd.Command - isPlugin(args []string) bool - initPlugin(name string, args []string) -} - -// newSuperCommand creates and returns a new superCommand wrapping the given -// cmd.SuperCommand. -func newSuperCommand(supercmd *cmd.SuperCommand) *superCommand { - return &superCommand{ - SuperCommand: supercmd, - all: map[string]bool{ - // The help command is registered by the super command. - "help": true, - }, - } -} - -// superCommand is a cmd.SuperCommand that can keep track of registered -// subcommands and plugins. superCommand implements commandWithPlugins. -type superCommand struct { - *cmd.SuperCommand - plugins map[string]cmd.Command - all map[string]bool -} - -// register registers the given command so that it is made available to be used -// from the super command. -func (s *superCommand) register(command cmd.Command) { - s.SuperCommand.Register(command) - s.all[command.Info().Name] = true -} - -// registerPlugins registers all found plugins as subcommands. -func (s *superCommand) registerPlugins() { - plugins := getPluginDescriptions() - s.plugins = make(map[string]cmd.Command, len(plugins)) - for _, plugin := range plugins { - if s.isAvailable(plugin.name) { - command := &pluginCommand{ - name: plugin.name, - purpose: plugin.description, - doc: plugin.doc, - } - s.register(command) - s.plugins[plugin.name] = command - } - } - s.SuperCommand.AddHelpTopicCallback( - "plugins", "Show "+s.SuperCommand.Name+" plugins", pluginHelpTopic) -} - -// isAvailable reports whether the given command name is available, meaning -// it has not been already registered. -func (s *superCommand) isAvailable(name string) bool { - return !s.all[name] -} - -// isPlugin reports whether the given super command arguments call a plugin. -func (s *superCommand) isPlugin(args []string) bool { - if len(args) == 0 { - return false - } - _, ok := s.plugins[args[0]] - return ok -} - -// initPlugin initialize a registered plugin using the given args. -func (s *superCommand) initPlugin(name string, args []string) { - s.plugins[name].Init(args) -} diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/terms.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/terms.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/terms.go 2016-04-01 15:19:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/terms.go 2016-09-03 01:24:14.000000000 +0000 @@ -15,12 +15,10 @@ type termsCommand struct { cmd.CommandBase - auth string + auth authInfo - username string - password string - out cmd.Output - user string + out cmd.Output + user string } // TODO (mattyw) As of 16Mar2016 this is implemented @@ -61,11 +59,6 @@ // Init implements cmd.Command.Init. func (c *termsCommand) Init(args []string) error { - var err error - c.username, c.password, err = validateAuthFlag(c.auth) - if err != nil { - return errgo.Mask(err) - } return cmd.CheckEmpty(args) } @@ -75,7 +68,7 @@ // Run implements cmd.Command.Run. func (c *termsCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, c.username, c.password) + client, err := newCharmStoreClient(ctxt, c.auth, params.NoChannel) if err != nil { return errgo.Notef(err, "cannot create the charm store client") } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami.go 2016-04-01 15:19:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami.go 2016-09-03 01:24:14.000000000 +0000 @@ -5,7 +5,6 @@ import ( "fmt" - "launchpad.net/gnuflag" "net/url" "sort" "strings" @@ -13,6 +12,7 @@ "github.com/juju/cmd" "gopkg.in/errgo.v1" "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + "launchpad.net/gnuflag" ) type whoamiCommand struct { @@ -54,7 +54,7 @@ } func (c *whoamiCommand) Run(ctxt *cmd.Context) error { - client, err := newCharmStoreClient(ctxt, "", "") + client, err := newCharmStoreClient(ctxt, authInfo{}, params.NoChannel) if err != nil { return errgo.Notef(err, "could not load the cookie from file") } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami_test.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami_test.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami_test.go 2016-04-01 15:19:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/charmcmd/whoami_test.go 2016-04-28 06:02:24.000000000 +0000 @@ -11,7 +11,6 @@ "os" "time" - "github.com/juju/charmstore-client/cmd/charm/charmcmd" "github.com/juju/idmclient/idmtest" "github.com/juju/persistent-cookiejar" jc "github.com/juju/testing/checkers" @@ -23,6 +22,8 @@ "gopkg.in/macaroon-bakery.v1/httpbakery" "gopkg.in/macaroon.v1" "gopkg.in/yaml.v2" + + "github.com/juju/charmstore-client/cmd/charm/charmcmd" ) type whoamiSuite struct { diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/main.go charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/main.go --- charm-2.1.1/src/github.com/juju/charmstore-client/cmd/charm/main.go 2016-03-23 02:08:47.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/cmd/charm/main.go 2016-09-03 01:24:14.000000000 +0000 @@ -4,6 +4,7 @@ package main import ( + "fmt" "os" "github.com/juju/cmd" @@ -14,11 +15,10 @@ func main() { osenv.SetJujuXDGDataHome(osenv.JujuXDGDataHomeDir()) - ctxt := &cmd.Context{ - Dir: ".", - Stdout: os.Stdout, - Stderr: os.Stderr, - Stdin: os.Stdin, + ctxt, err := cmd.DefaultContext() + if err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(2) } - os.Exit(charmcmd.Main(charmcmd.New(), ctxt, os.Args[1:])) + os.Exit(cmd.Main(charmcmd.New(), ctxt, os.Args[1:])) } diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/config/bash/charm charm-2.2.0/src/github.com/juju/charmstore-client/config/bash/charm --- charm-2.1.1/src/github.com/juju/charmstore-client/config/bash/charm 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/config/bash/charm 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,47 @@ +# bash completion for the charm command. +# +# adapted from https://github.com/jjo/juju/blob/11e1a81c012a45d285545ff84db336b3ed9a4b78/etc/bash_completion.d/juju +# +# Copyright 2016 Canonical Ltd. + +_complete_with_func() { + local action="${1}" func=${2?} + local flags=$(_flags_for "${action}") + local cur="${COMP_WORDS[COMP_CWORD]}" + _get_comp_words_by_ref -n : cur + COMPREPLY=( $( compgen -W "$(${func} ${juju_status_file} ${postfix_str}) $flags" -- ${cur} )) +} + +_list_commands() { + ${COMP_WORDS[0]} help 2>/dev/null | awk '{if (cmd==1){print $1}} /commands:/{cmd=1;}' +} + +_flags_for() { + test -z "${1}" && return 0 + ${COMP_WORDS[0]} help ${1} 2>/dev/null |egrep -o -- '(^|-)-[a-z-]+'|sort -u +} + +_completion_func_for_cmd() { + local action=${1} cword=${2} + # if cword==1 or action==help use _list_commands + if [[ "${cword}" -eq 1 || "${action}" == help ]]; then + echo _list_commands + return 0 + fi + case $(${COMP_WORDS[0]} help ${action} 2>/dev/null | head -1) in + ?*) echo true;; + *) echo false;; + esac +} + +_completer() { + local action parsing_func + action="${COMP_WORDS[1]}" + COMPREPLY=() + parsing_func=$(_completion_func_for_cmd "${action}" ${COMP_CWORD}) + test -n "${parsing_func}" && _complete_with_func "${action}" "${parsing_func}" + return $? +} + +complete -F _completer charm +# vim: ai et sw=2 ts=2 diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/dependencies.tsv charm-2.2.0/src/github.com/juju/charmstore-client/dependencies.tsv --- charm-2.1.1/src/github.com/juju/charmstore-client/dependencies.tsv 2016-04-13 15:17:35.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/dependencies.tsv 2016-09-03 01:24:14.000000000 +0000 @@ -1,45 +1,53 @@ github.com/ajstarks/svgo git 89e3ac64b5b3e403a5e7c35ea4f98d45db7b4518 2014-10-04T21:11:59Z -github.com/gosuri/uitable git cacfc559e8712a81692496c5147c80aced020e51 2015-12-16T01:20:41Z +github.com/beorn7/perks git 3ac7bf7a47d159a033b107610db8a1b6575507a4 2016-02-29T21:34:45Z +github.com/golang/protobuf git 34a5f244f1c01cdfee8e60324258cfbb97a42aec 2015-05-26T01:21:09Z +github.com/gosuri/uitable git 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 2016-04-04T20:39:58Z github.com/juju/blobstore git 06056004b3d7b54bbb7984d830c537bad00fec21 2015-07-29T11:18:58Z -github.com/juju/cmd git 04d94ec1dcc78b5444449c15b3b83d1ad3366d5a 2016-03-17T03:47:00Z +github.com/juju/cmd git 7b97644c35b36184b5e777d7fcb9000306398b09 2016-05-25T17:29:21Z github.com/juju/errors git 1b5e39b83d1835fa480e0c2ddefb040ee82d58b3 2015-09-16T12:56:42Z github.com/juju/go4 git 40d72ab9641a2a8c36a9c46a51e28367115c8e59 2016-02-22T16:32:58Z github.com/juju/gojsonpointer git afe8b77aa08f272b49e01b82de78510c11f61500 2015-02-04T19:46:29Z github.com/juju/gojsonreference git f0d24ac5ee330baa21721cdff56d45e4ee42628e 2015-02-04T19:46:33Z github.com/juju/gojsonschema git e1ad140384f254c82f89450d9a7c8dd38a632838 2015-03-12T17:00:16Z github.com/juju/httpprof git 14bf14c307672fd2456bdbf35d19cf0ccd3cf565 2014-12-17T16:00:36Z -github.com/juju/httprequest git 89d547093c45e293599088cc63e805c6f1205dc0 2016-03-02T10:09:58Z -github.com/juju/idmclient git 98ac3e5ba9acebc7f946ec6478a783836507dd7f 2016-04-13T13:29:21Z -github.com/juju/juju git 45cc6aa81dc491d292b0e5731652017409e71f1c 2016-03-16T08:56:24Z +github.com/juju/httprequest git 3d72385a5c19cf70e983c8cb888769a2d9efe2ea 2016-04-20T11:37:53Z +github.com/juju/idmclient git 24249a7beddb83931417a6a761c29b1234c9cbac 2016-04-25T12:21:04Z +github.com/juju/juju git a52586b8f3e3d4b8f9e96c710b34d125dcad03ed 2016-04-15T00:47:06Z github.com/juju/loggo git 8477fc936adf0e382d680310047ca27e128a309a 2015-05-27T03:58:39Z github.com/juju/mempool git 24974d6c264fe5a29716e7d56ea24c4bd904b7cc 2016-02-05T10:49:27Z -github.com/juju/names git ef19de31613af3735aa69ba3b40accce2faf7316 2016-03-01T22:07:10Z +github.com/juju/names git 8a0aa0963bbacdc790914892e9ff942e94d6f795 2016-03-30T15:05:33Z github.com/juju/persistent-cookiejar git e710b897c13ca52828ca2fc9769465186fd6d15c 2016-03-31T17:12:27Z github.com/juju/schema git 1e25943f8c6fd6815282d6f1ac87091d21e14e19 2016-03-01T11:16:46Z -github.com/juju/testing git 4d5a7d64948aae30698f5d97b4c0d1a85a4504b7 2016-03-07T02:41:09Z +github.com/juju/testing git b9cfe07211e464f16d78ca9304c503d636b8eefa 2016-05-19T00:49:35Z github.com/juju/txn git 99ec629d0066a4d73c54d8e021a7fc1dc07df614 2015-06-09T16:58:27Z github.com/juju/usso git 68a59c96c178fbbad65926e7f93db50a2cd14f33 2016-04-01T10:44:24Z -github.com/juju/utils git 0cac78a34dd1c42d2f2dc718c345fd13e3a264fc 2016-01-29T15:50:19Z -github.com/juju/version git 102b12db83e38cb2ce7003544092ea7b0ca59e92 2015-11-07T04:32:11Z +github.com/juju/utils git ffea6ead0c374583e876c8357c9db6e98bc71476 2016-05-26T02:52:51Z +github.com/juju/version git ef897ad7f130870348ce306f61332f5335355063 2015-11-27T20:34:00Z github.com/juju/webbrowser git 54b8c57083b4afb7dc75da7f13e2967b2606a507 2016-03-09T14:36:29Z github.com/juju/xml git eb759a627588d35166bc505fceb51b88500e291e 2015-04-13T13:11:21Z github.com/juju/zip git f6b1e93fa2e29a1d7d49b566b2b51efb060c982a 2016-02-05T10:52:21Z github.com/julienschmidt/httprouter git 77a895ad01ebc98a4dc95d8355bc825ce80a56f6 2015-10-13T22:55:20Z github.com/mattn/go-runewidth git d96d1bd051f2bd9e7e43d602782b37b93b1b5666 2015-11-18T07:21:59Z +github.com/matttproud/golang_protobuf_extensions git c12348ce28de40eed0136aa2b644d0ee0650e56c 2016-04-24T11:30:07Z +github.com/prometheus/client_golang git b90ee0840e8e7dfb84c08d13b9c4f3a794586a21 2016-05-13T04:20:11Z +github.com/prometheus/client_model git fa8ad6fec33561be4280a8f0514318c79d7f6cb6 2015-02-12T10:17:44Z +github.com/prometheus/common git dd586c1c5abb0be59e60f942c22af711a2008cb4 2016-05-03T22:05:32Z +github.com/prometheus/procfs git abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 2016-04-11T19:08:41Z +github.com/rogpeppe/fastuuid git 6724a57986aff9bff1a1770e9347036def7c89f6 2015-01-06T09:32:20Z golang.org/x/crypto git aedad9a179ec1ea11b7064c57cbc6dc30d7724ec 2015-08-30T18:06:42Z golang.org/x/net git ea47fc708ee3e20177f3ca3716217c4ab75942cb 2015-08-29T23:03:18Z gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z gopkg.in/errgo.v1 git 66cb46252b94c1f3d65646f54ee8043ab38d766c 2015-10-07T15:31:57Z -gopkg.in/juju/charm.v6-unstable git 4ad4f4ba39affe67785e385c1f49e7ec4206896f 2016-03-09T11:06:06Z -gopkg.in/juju/charmrepo.v2-unstable git 18144077f54b067f75f36eaddacd1065b84a5838 2016-04-12T16:24:43Z -gopkg.in/juju/charmstore.v5-unstable git c826fcd97e86bc44c219fd2fbcea4af8d82ba138 2016-03-17T09:18:53Z +gopkg.in/juju/charm.v6-unstable git a3bb92d047b0892452b6a39ece59b4d3a2ac35b9 2016-07-22T08:34:31Z +gopkg.in/juju/charmrepo.v2-unstable git d4ef779a4aa30acc2b281f5b0f23c8fb4004a45d 2016-08-09T13:52:27Z +gopkg.in/juju/charmstore.v5-unstable git 714c25cd43dbb8eb51f57a55d76d521fc2126455 2016-08-09T13:45:06Z gopkg.in/juju/environschema.v1 git 7359fc7857abe2b11b5b3e23811a9c64cb6b01e0 2015-11-04T11:58:10Z -gopkg.in/juju/jujusvg.v1 git a60359df348ef2ca40ec3bcd58a01de54f05658e 2016-02-11T10:02:50Z -gopkg.in/macaroon-bakery.v1 git fddb3dcd74806133259879d033fdfe92f9e67a8a 2016-04-01T12:14:21Z +gopkg.in/juju/jujusvg.v2 git d82160011935ef79fc7aca84aba2c6f74700fe75 2016-06-09T10:52:15Z +gopkg.in/juju/names.v2 git e38bc90539f22af61a9c656d35068bd5f0a5b30a 2016-05-25T23:07:23Z +gopkg.in/macaroon-bakery.v1 git e7074941455a293ffb7905cd89ca20ee547a03ec 2016-05-27T11:55:54Z gopkg.in/macaroon.v1 git ab3940c6c16510a850e1c2dd628b919f0f3f1464 2015-01-21T11:42:31Z gopkg.in/mgo.v2 git 4d04138ffef2791c479c0c8bbffc30b34081b8d9 2015-10-26T16:34:53Z gopkg.in/natefinch/lumberjack.v2 git 514cbda263a734ae8caac038dadf05f8f3f9f738 2016-01-25T11:17:49Z -gopkg.in/yaml.v1 git 9f9df34309c04878acc86042b16630b0f696e1de 2014-09-24T16:16:07Z -gopkg.in/yaml.v2 git 53feefa2559fb8dfa8d81baad31be332c97d6c77 2015-09-24T14:23:14Z -launchpad.net/gnuflag bzr roger.peppe@canonical.com-20140716064605-pk32dnmfust02yab 13 +gopkg.in/yaml.v2 git a83829b6f1293c91addabc89d0571c246397bbf4 2016-03-01T20:40:22Z +launchpad.net/gnuflag bzr roger.peppe@canonical.com-20150126181550-ucyih42xy2uq6qz5 13.1.1 launchpad.net/tomb bzr gustavo@niemeyer.net-20140529072043-hzcrlnl3ygvg914q 18 diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/Makefile charm-2.2.0/src/github.com/juju/charmstore-client/Makefile --- charm-2.1.1/src/github.com/juju/charmstore-client/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/Makefile 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,119 @@ +# Makefile for the charm store client. + +ifndef GOPATH +$(warning You need to set up a GOPATH.) +endif + +PROJECT := github.com/juju/charmstore-client +PROJECT_DIR := $(shell go list -e -f '{{.Dir}}' $(PROJECT)) + +INSTALL_FILE=install -m 644 -p + +ifeq ($(shell uname -p | sed -r 's/.*(x86|armel|armhf).*/golang/'), golang) + GO_C := golang + INSTALL_FLAGS := +else + GO_C := gccgo-4.9 gccgo-go + INSTALL_FLAGS := -gccgoflags=-static-libgo +endif + +default: build + +$(GOPATH)/bin/godeps: + go get -u -v github.com/rogpeppe/godeps + +# Start of GOPATH-dependent targets. Some targets only make sense - +# and will only work - when this tree is found on the GOPATH. + +ifeq ($(CURDIR),$(PROJECT_DIR)) + +build: + go build $(PROJECT)/... + +check: + go test $(PROJECT)/... + +install: + go install $(INSTALL_FLAGS) -v $(PROJECT)/... + +clean: + go clean $(PROJECT)/... + rm -rf man + +else + +build: + $(error Cannot $@; $(CURDIR) is not on GOPATH) + +check: + $(error Cannot $@; $(CURDIR) is not on GOPATH) + +install: + $(error Cannot $@; $(CURDIR) is not on GOPATH) + +clean: + $(error Cannot $@; $(CURDIR) is not on GOPATH) + +endif +# End of GOPATH-dependent targets. + +# Reformat source files. +format: + gofmt -w -l . + +# Reformat and simplify source files. +simplify: + gofmt -w -l -s . + +# Update the project Go dependencies to the required revision. +deps: $(GOPATH)/bin/godeps + $(GOPATH)/bin/godeps -u dependencies.tsv + +# Generate the dependencies file. +create-deps: $(GOPATH)/bin/godeps + godeps -t $(shell go list $(PROJECT)/...) > dependencies.tsv || true + +# Generate man pages. +man/man1: + make install + mkdir -p man/man1 + cd man/man1 && ../../scripts/generate-all-manpages.sh + +# The install-man make target are for use by debian packaging. +# The semantics should match autotools make files as dh_make expects it. +install-man: man/man1 + mkdir -p $(DESTDIR)/usr/share/man/man1 + for file in man/man1/* ; do \ + $(INSTALL_FILE) $$file "$(DESTDIR)/usr/share/man/man1" ; done + +uninstall-man: man/man1 + for file in man/man1/* ; do \ + rm "$(DESTDIR)/usr/share/$$file" ; done + -rmdir -p $(DESTDIR)/usr/share/man/man1 + +install-bash-completion: + mkdir -p $(DESTDIR)/usr/share/bash-completion/completions + $(INSTALL_FILE) config/bash/charm "$(DESTDIR)/usr/share/bash-completion/completions/" + +uninstall-bash-completion: + rm "$(DESTDIR)/usr/share/bash-completion/completions/charm" + -rmdir -p $(DESTDIR)/usr/share/bash-completion/completions + +help: + @echo -e 'Charmstore-client - list of make targets:\n' + @echo 'make - Build the package.' + @echo 'make check - Run tests.' + @echo 'make install - Install the package to $$GOPATH/bin' + @echo 'make clean - Remove object files from package source directories.' + @echo 'make deps - Set up the project Go dependencies.' + @echo 'make create-deps - Generate the Go dependencies file.' + @echo 'make format - Format the source files.' + @echo 'make man - Generate man pages.' + @echo 'make simplify - Format and simplify the source files.' + @echo 'make install-man - Install man pages to $$DESTDIR/usr/share/man' + @echo 'make uninstall-man - Remove man pages from $$DESTDIR/usr/share/man' + @echo 'make install-bash-completion - Install completion to $$DESTDIR/usr/share/bash-completion/completions/' + @echo 'make uninstall-bash-completion - Remove completion from $$DESTDIR/usr/share/bash-completion/completions/' + +.PHONY: build check clean create-deps deps format help install simplify \ + install-man uninstall-man install-bash-completion uninstall-bash-completion diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/README.md charm-2.2.0/src/github.com/juju/charmstore-client/README.md --- charm-2.1.1/src/github.com/juju/charmstore-client/README.md 2016-03-17 19:42:57.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/README.md 2016-04-28 06:02:24.000000000 +0000 @@ -2,3 +2,14 @@ The charmstore-client repository holds client-side code for interacting with the Juju charm store. + +To install: + +``` +go get github.com/juju/charmstore-client +cd $GOPATH/src/github.com/juju/charmstore-client +make deps install +``` + +You'll then be able to run `$GOPATH/bin/charm`. + diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/scripts/generate-all-manpages.sh charm-2.2.0/src/github.com/juju/charmstore-client/scripts/generate-all-manpages.sh --- charm-2.1.1/src/github.com/juju/charmstore-client/scripts/generate-all-manpages.sh 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/scripts/generate-all-manpages.sh 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,18 @@ +#!/bin/sh +# Called from the project root by 'make man' + +set -e + +VERSION=$(git describe --dirty) +TITLE='Charm Manual' +FILES=$(echo "~/.go-cookies\tHolds authentication tokens for communicating with the charmstore. +~/.cache/charm-command-cache\tHolds cache for descriptions of extended core charm commands.") + +dir=$(dirname $0) +$dir/generate-manpage.py -a 'https://jujucharms.com' -f "$FILES" -v "$VERSION" -t "$TITLE" charm + +for cmd in $(charm help | awk '{if (x==1) {print $1} if( $1=="commands:")x=1;} ') +do + $dir/generate-manpage.py -a 'https://jujucharms.com' -s "$cmd" -t "$TITLE" -v "$VERSION" charm + +done diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/scripts/generate-manpage.py charm-2.2.0/src/github.com/juju/charmstore-client/scripts/generate-manpage.py --- charm-2.1.1/src/github.com/juju/charmstore-client/scripts/generate-manpage.py 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/scripts/generate-manpage.py 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2016 Canonical Ltd. +# Adapted from github.com/juju/juju/scripts/generate-docs.py + +from argparse import ArgumentParser +import os +import sys + +# Insert the directory that this module is in into the python path. +sys.path.insert(0, (os.path.dirname(__file__))) + +from jujuman import JujuMan + + +def main(argv): + parser = ArgumentParser() + + parser.add_argument("-a", "--also", dest="also", + help="set the extra see also in generated man page") + parser.add_argument("-f", "--files", dest="files", + help="set the files in generated man page") + parser.add_argument("-o", "--output", dest="filename", metavar="FILE", + help="write output to FILE") + parser.add_argument("-s", "--subcommand", + dest="subcommand", + help="write man page for subcommand") + parser.add_argument("-t", "--title", dest="title", + help="set the title in generated man page") + parser.add_argument("-v", "--version", dest="version", + help="set the version in generated man page") + parser.add_argument("utility", + help="write man page for utility") + + options = parser.parse_args(argv[1:]) + + if not options.utility: + parser.print_help() + sys.exit(1) + + doc_generator = JujuMan(options.utility, + also=options.also, + files=options.files, + subcmd=options.subcommand, + title=options.title, + version=options.version) + + if options.filename: + outfilename = options.filename + else: + outfilename = doc_generator.get_filename() + + if outfilename == "-": + outfile = sys.stdout + else: + outfile = open(outfilename, "w") + + doc_generator.write_documentation(outfile) + + +if __name__ == "__main__": + main(sys.argv) diff -Nru charm-2.1.1/src/github.com/juju/charmstore-client/scripts/jujuman.py charm-2.2.0/src/github.com/juju/charmstore-client/scripts/jujuman.py --- charm-2.1.1/src/github.com/juju/charmstore-client/scripts/jujuman.py 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/charmstore-client/scripts/jujuman.py 2016-09-03 01:24:14.000000000 +0000 @@ -0,0 +1,275 @@ +# Copyright 2013-2016 Canonical Ltd. +# Adapted from github.com/juju/juju/scripts/jujuman.py + +"""Functions for generating the manpage using the juju command.""" + +import subprocess +import time + + +class JujuMan: + + def __init__(self, + cmd, + also=None, + files=None, + subcmd=None, + title=None, + version='0.0.1'): + self.also = also + self.cmd = cmd + self.files = files + self.subcmd = subcmd + self.title = title + self.version = version + + self.filename = self.cmd + ('' if not self.subcmd + else '-' + self.subcmd) + '.1' + + def get_filename(self): + """Provides name of manpage""" + return self.filename + + def run_help(self): + cmd = [self.cmd, 'help'] + if self.subcmd: + cmd = [self.cmd, 'help', self.subcmd] + print('running '+' '.join(cmd)) + return subprocess.check_output(cmd).strip() + + def help(self): + text = self.run_help() + # extcommands = self.run('help', 'plugins') + commands = [] + known_states = ['commands:', 'summary:', 'options:', 'details:', + 'examples:'] + states = {} + state = None + for line in text.decode("utf-8").split('\n'): + line = line.strip() + state = line.lower() + if state in known_states: + states[state] = '' + continue + if state == 'commands:': + s = line.split(' ', 1) + if len(s) > 1: + name, short_help = s + else: + continue + if 'alias for' in short_help: + continue + short_help = short_help.lstrip('- ') + commands.append((name, short_help.strip())) + continue + if state: + states[state] += '\n' + line + + states['options:'] = self.format_options(states.get('options:', None)) + return (states.get('summary:', None), + states.get('options:', None), + states.get('details:', None), + states.get('examples:', None), + commands) + + def write_documentation(self, outfile): + """Assembles a man page""" + t = time.time() + gmt = time.gmtime(t) + summary, options, details, examples, commands = self.help() + params = { + "cmd": self.cmd, + "datestamp": time.strftime("%Y-%m-%d", gmt), + "description": details, + "examples": examples, + "files": self.files, + "hsubcmd": ('-' + self.subcmd) if self.subcmd else '', + "options": options, + "subcmd": self.subcmd or ' ', + "summary": summary, + "timestamp": time.strftime("%Y-%m-%d %H:%M:%S +0000", tt), + "title": self.title, + "version": self.version, + } + outfile.write(man_preamble % params) + outfile.write(man_escape(man_head % params)) + if self.subcmd: + outfile.write(man_escape(man_syn_sub % params)) + else: + outfile.write(man_escape(man_syn % params)) + outfile.write(man_escape(man_desc % params)) + if commands: + outfile.write(man_escape( + self.get_command_overview(self.cmd, commands))) + if examples: + outfile.write(man_escape(man_examples % params)) + sac = '.SH "SEE ALSO"\n' + if commands: + sac += "".join((r'\fB' + self.cmd + '-' + cmd + r'\fR(1), ' + for cmd, _ in commands)) + else: + sac += r'\fB' + self.cmd + r'\fR(1), ' + sac += '\n' + outfile.write("".join(environment_variables())) + if self.files: + outfile.write(man_escape(man_files + self.format_files())) + outfile.write(sac) + if self.also: + outfile.write(man_escape( + ''.join('.UR {0}\n.BR {0}'.format(a) for a in + self.also.split('\t')))) + + def get_command_overview(self, basecmd, commands): + """Builds summary help for command names in manpage format""" + output = '.SH "COMMAND OVERVIEW"\n' + for cmd_name, short_help in commands: + tmp = '.TP\n.B "%s %s"\n%s\n' % (basecmd, + cmd_name, short_help) + output = output + tmp + return output + + def format_options(self, options): + if not options: + return + olist = [] + prev = None + for line in options.split('\n'): + if not line: + continue + if line.startswith('-'): + if prev: + olist.append(prev) + prev = [line, ''] + continue + prev = [prev[0], prev[1] + line] + + return ''.join(('.PP\n' + c + + '\n.RS 4\n' + d + + '\n.RE\n' for c, d in olist)) + + def format_files(self): + if not self.files: + return + files = self.files.split('\n') + return ''.join('''.TP +.I "{}" +{} +'''.format(*line.split('\t', 1)) for line in files) + + +ENVIRONMENT = ( + # The command does not currently read documented environment variable + # values, but if it did, we would document them this way. + # ('JUJU_MODEL', textwrap.dedent(""" + # Provides a way for the shell environment to specify the current Juju + # model to use. If the model is specified explicitly using + # -m MODEL, this takes precedence. + # """)), + # ('JUJU_DATA', textwrap.dedent(""" + # Overrides the default Juju configuration directory of + # $XDG_DATA_HOME/juju or ~/.local/share/juju + # if $XDG_DATA_HOME is not defined. + # """)), + # ('AWS_ACCESS_KEY_ID', textwrap.dedent(""" + # The access-key for your AWS account. + # """)), + # ('AWS_SECRET_ACCESS_KEY', textwrap.dedent(""" + # The secret-key for your AWS account. + # """)), + # ('OS_USERNAME', textwrap.dedent(""" + # Your openstack username. + # """)), + # ('OS_PASSWORD', textwrap.dedent(""" + # Your openstack password. + # """)), + # ('OS_TENANT_NAME', textwrap.dedent(""" + # Your openstack tenant name. + # """)), + # ('OS_REGION_NAME', textwrap.dedent(""" + # Your openstack region name. + # """)), +) + + +def man_escape(string): + """Escapes strings for man page compatibility""" + result = string.replace("\\", "\\\\") + result = result.replace("`", "\\'") + result = result.replace("'", "\\*(Aq") + result = result.replace("-", "\\-") + return result + + +def environment_variables(): + if ENVIRONMENT: + yield ".SH \"ENVIRONMENT\"\n" + + for k, desc in ENVIRONMENT: + yield ".TP\n" + yield ".I \"%s\"\n" % k + yield man_escape(desc) + "\n" + + +man_preamble = """\ +.\\\"Man page for %(cmd)s +.\\\" +.\\\" Large parts of this file are autogenerated from the output of +.\\\" \"%(cmd)s help commands\" +.\\\" \"%(cmd)s help \" +.\\\" +.\\\" Generation time: %(timestamp)s +.\\\" + +.ie \\n(.g .ds Aq \\(aq +.el .ds Aq ' +""" + +man_head = """\ +.TH %(cmd)s%(hsubcmd)s 1 "%(datestamp)s" "%(version)s" "%(title)s" +.SH "NAME" +%(cmd)s%(hsubcmd)s -- %(summary)s +""" + +man_syn = """\ +.SH "SYNOPSIS" +.B "%(cmd)s %(subcmd)s" +.I "command" +[ +.I "command_options" +] +.br +.B "%(cmd)s %(subcmd)s" +.B "help" +.br +.B "%(cmd)s %(subcmd)s" +.B "help" +.I "command" +""" + +man_syn_sub = """\ +.SH "SYNOPSIS" +.B "%(cmd)s %(subcmd)s" +[ +.I "options" +] +.br +.B "%(cmd)s %(subcmd)s" +.B "help" +""" + +man_desc = """\ +.SH "DESCRIPTION" + +%(description)s +.SH "OPTIONS" + +%(options)s +""" + +# TODO: Support example section. +man_examples = """\ +""" + +man_files = """\ +.SH "FILES" +""" diff -Nru charm-2.1.1/src/github.com/juju/cmd/cmd.go charm-2.2.0/src/github.com/juju/cmd/cmd.go --- charm-2.1.1/src/github.com/juju/cmd/cmd.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/cmd.go 2016-04-28 06:03:29.000000000 +0000 @@ -225,7 +225,7 @@ } fmt.Fprintf(buf, "\n") if i.Purpose != "" { - fmt.Fprintf(buf, "\nSummary:\n%s\n", i.Purpose) + fmt.Fprintf(buf, "\nSummary:\n%s\n", strings.TrimSpace(i.Purpose)) } if hasOptions { fmt.Fprintf(buf, "\nOptions:\n") diff -Nru charm-2.1.1/src/github.com/juju/cmd/cmd_test.go charm-2.2.0/src/github.com/juju/cmd/cmd_test.go --- charm-2.1.1/src/github.com/juju/cmd/cmd_test.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/cmd_test.go 2016-04-28 06:03:29.000000000 +0000 @@ -9,6 +9,8 @@ "os" "path/filepath" + "launchpad.net/gnuflag" + gc "gopkg.in/check.v1" "github.com/juju/cmd" @@ -171,3 +173,41 @@ c.Assert(cmd.IsErrSilent(cmd.NewRcPassthroughError(99)), gc.Equals, true) c.Assert(cmd.IsErrSilent(fmt.Errorf("noisy")), gc.Equals, false) } + +func (s *CmdSuite) TestInfoHelp(c *gc.C) { + // Test that white space is trimmed consistently from cmd.Info.Purpose + // (Help Summary) and cmd.Info.Doc (Help Details) + option := "option" + fs := gnuflag.NewFlagSet("", gnuflag.ContinueOnError) + fs.StringVar(&option, "option", "", "option-doc") + + table := []struct { + summary, details string + }{ + {` + verb the juju`, + ` + verb-doc`}, + {`verb the juju`, `verb-doc`}, + {` + + verb the juju`, + ` + + verb-doc`}, + {`verb the juju `, `verb-doc + + `}, + } + want := fullHelp + for _, tv := range table { + i := cmd.Info{ + Name: "verb", + Args: "", + Purpose: tv.summary, + Doc: tv.details, + } + got := string(i.Help(fs)) + c.Check(got, gc.Equals, want) + } +} diff -Nru charm-2.1.1/src/github.com/juju/cmd/help.go charm-2.2.0/src/github.com/juju/cmd/help.go --- charm-2.1.1/src/github.com/juju/cmd/help.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/help.go 2016-09-03 01:25:01.000000000 +0000 @@ -105,6 +105,9 @@ } func (c *helpCommand) Init(args []string) error { + if c.super.notifyHelp != nil { + c.super.notifyHelp(args) + } logger.Tracef("helpCommand.Init: %#v", args) if len(args) == 0 { // If there is no help topic specified, print basic usage if it is diff -Nru charm-2.1.1/src/github.com/juju/cmd/help_test.go charm-2.2.0/src/github.com/juju/cmd/help_test.go --- charm-2.1.1/src/github.com/juju/cmd/help_test.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/help_test.go 2016-09-03 01:25:01.000000000 +0000 @@ -159,3 +159,21 @@ c.Check(cmdtesting.Stdout(ctx), gc.Equals, help) } } + +func (s *HelpCommandSuite) TestNotifyHelp(c *gc.C) { + var called [][]string + super := cmd.NewSuperCommand(cmd.SuperCommandParams{ + Name: "super", + NotifyHelp: func(args []string) { + called = append(called, args) + }, + }) + super.Register(&TestCommand{ + Name: "blah", + }) + ctx := cmdtesting.Context(c) + code := cmd.Main(super, ctx, []string{"help", "blah"}) + c.Assert(code, gc.Equals, 0) + + c.Assert(called, jc.DeepEquals, [][]string{{"blah"}}) +} diff -Nru charm-2.1.1/src/github.com/juju/cmd/logging.go charm-2.2.0/src/github.com/juju/cmd/logging.go --- charm-2.1.1/src/github.com/juju/cmd/logging.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/logging.go 2016-09-03 01:25:01.000000000 +0000 @@ -100,7 +100,8 @@ } } // Set the level on the root logger. - loggo.GetLogger("").SetLogLevel(level) + root := loggo.GetLogger("") + root.SetLogLevel(level) // Override the logging config with specified logging config. loggo.ConfigureLoggers(log.Config) return nil diff -Nru charm-2.1.1/src/github.com/juju/cmd/output.go charm-2.2.0/src/github.com/juju/cmd/output.go --- charm-2.1.1/src/github.com/juju/cmd/output.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/output.go 2016-04-28 06:03:29.000000000 +0000 @@ -129,7 +129,7 @@ i++ } sort.Strings(choices) - return "specify output format (" + strings.Join(choices, "|") + ")" + return "Specify output format (" + strings.Join(choices, "|") + ")" } // format runs the chosen formatter on value. @@ -148,7 +148,7 @@ func (c *Output) AddFlags(f *gnuflag.FlagSet, defaultFormatter string, formatters map[string]Formatter) { c.formatter = newFormatterValue(defaultFormatter, formatters) f.Var(c.formatter, "format", c.formatter.doc()) - f.StringVar(&c.outPath, "o", "", "specify an output file") + f.StringVar(&c.outPath, "o", "", "Specify an output file") f.StringVar(&c.outPath, "output", "", "") } diff -Nru charm-2.1.1/src/github.com/juju/cmd/supercommand.go charm-2.2.0/src/github.com/juju/cmd/supercommand.go --- charm-2.1.1/src/github.com/juju/cmd/supercommand.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/supercommand.go 2016-09-03 01:25:01.000000000 +0000 @@ -9,6 +9,7 @@ "sort" "strings" + "github.com/juju/errors" "github.com/juju/loggo" "launchpad.net/gnuflag" ) @@ -48,6 +49,13 @@ // is about to run a sub-command. NotifyRun func(cmdName string) + // NotifyHelp is called just before help is printed, with the + // arguments received by the help command. This can be + // used, for example, to load command information for external + // "plugin" commands, so that their documentation will show up + // in the help output. + NotifyHelp func([]string) + Name string Purpose string Doc string @@ -76,6 +84,7 @@ Aliases: params.Aliases, version: params.Version, notifyRun: params.NotifyRun, + notifyHelp: params.NotifyHelp, userAliasesFilename: params.UserAliasesFilename, } command.init() @@ -129,6 +138,7 @@ noAlias bool missingCallback MissingCallback notifyRun func(string) + notifyHelp func([]string) } // IsSuperCommand implements Command.IsSuperCommand @@ -446,6 +456,7 @@ err := c.action.command.Run(ctx) if err != nil && !IsErrSilent(err) { logger.Errorf("%v", err) + logger.Debugf("(error details: %v)", errors.Details(err)) // Now that this has been logged, don't log again in cmd.Main. if !IsRcPassthroughError(err) { err = ErrSilent diff -Nru charm-2.1.1/src/github.com/juju/cmd/supercommand_test.go charm-2.2.0/src/github.com/juju/cmd/supercommand_test.go --- charm-2.1.1/src/github.com/juju/cmd/supercommand_test.go 2016-03-17 19:45:29.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/cmd/supercommand_test.go 2016-09-03 01:25:01.000000000 +0000 @@ -258,7 +258,7 @@ ctx := cmdtesting.Context(c) code := cmd.Main(sc, ctx, []string{"blah", "--option", "error", "--debug"}) c.Assert(code, gc.Equals, 1) - c.Assert(bufferString(ctx.Stderr), gc.Matches, `^.* ERROR .* BAM!\n`) + c.Assert(bufferString(ctx.Stderr), gc.Matches, `^.* ERROR .* BAM!\n.* DEBUG .* \(error details.*\).*\n`) } func (s *SuperCommandSuite) TestNotifyRun(c *gc.C) { diff -Nru charm-2.1.1/src/github.com/juju/httprequest/handler.go charm-2.2.0/src/github.com/juju/httprequest/handler.go --- charm-2.1.1/src/github.com/juju/httprequest/handler.go 2016-03-17 19:46:08.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/httprequest/handler.go 2016-09-03 01:25:03.000000000 +0000 @@ -80,9 +80,10 @@ Path: rt.path, Handle: func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { hf(fv, Params{ - Response: w, - Request: req, - PathVar: p, + Response: w, + Request: req, + PathVar: p, + PathPattern: rt.path, }) }, } @@ -140,9 +141,10 @@ handler := func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { terrv := fv.Call([]reflect.Value{ reflect.ValueOf(Params{ - Response: w, - Request: req, - PathVar: p, + Response: w, + Request: req, + PathVar: p, + PathPattern: rt.path, }), }) tv, errv := terrv[0], terrv[1] @@ -154,9 +156,10 @@ defer tv.Interface().(io.Closer).Close() } hf(tv.Method(i), Params{ - Response: w, - Request: req, - PathVar: p, + Response: w, + Request: req, + PathVar: p, + PathPattern: rt.path, }) } @@ -365,6 +368,9 @@ // HandleJSON returns a handler that writes the return value of handle // as a JSON response. If handle returns an error, it is passed through // the error mapper. +// +// Note that the Params argument passed to handle will not +// have its PathPattern set as that information is not available. func (e ErrorMapper) HandleJSON(handle JSONHandler) httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { val, err := handle(Params{ @@ -383,6 +389,9 @@ // HandleErrors returns a handler that passes any non-nil error returned // by handle through the error mapper and writes it as a JSON response. +// +// Note that the Params argument passed to handle will not +// have its PathPattern set as that information is not available. func (e ErrorMapper) HandleErrors(handle ErrorHandler) httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { w1 := responseWriter{ diff -Nru charm-2.1.1/src/github.com/juju/httprequest/handler_test.go charm-2.2.0/src/github.com/juju/httprequest/handler_test.go --- charm-2.1.1/src/github.com/juju/httprequest/handler_test.go 2016-03-17 19:46:08.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/httprequest/handler_test.go 2016-09-03 01:25:03.000000000 +0000 @@ -53,6 +53,7 @@ c.Assert(p.Request.Form, jc.DeepEquals, url.Values{ "c": {"43"}, }) + c.Assert(p.PathPattern, gc.Equals, "") p.Response.Header().Set("Content-Type", "application/json") p.Response.Write([]byte("true")) } @@ -77,6 +78,7 @@ } return func(p httprequest.Params, s *testStruct) error { c.Assert(s, jc.DeepEquals, &testStruct{123}) + c.Assert(p.PathPattern, gc.Equals, "") p.Response.Header().Set("Content-Type", "application/json") p.Response.Write([]byte("true")) return nil @@ -95,6 +97,7 @@ A int `httprequest:"a,path"` } return func(p httprequest.Params, s *testStruct) error { + c.Assert(p.PathPattern, gc.Equals, "") c.Assert(s, jc.DeepEquals, &testStruct{123}) return errUnauth } @@ -116,6 +119,7 @@ A int `httprequest:"a,path"` } return func(p httprequest.Params, s *testStruct) (int, error) { + c.Assert(p.PathPattern, gc.Equals, "") c.Assert(s, jc.DeepEquals, &testStruct{123}) return 1234, nil } @@ -133,6 +137,7 @@ A int `httprequest:"a,path"` } return func(p httprequest.Params, s *testStruct) (int, error) { + c.Assert(p.PathPattern, gc.Equals, "") c.Assert(s, jc.DeepEquals, &testStruct{123}) return 0, errUnauth } @@ -154,6 +159,7 @@ A int `httprequest:"a,path"` } return func(p httprequest.Params, s *testStruct) (int, error) { + c.Assert(p.PathPattern, gc.Equals, "") _, err := p.Response.Write(nil) c.Assert(err, gc.ErrorMatches, "inappropriate call to ResponseWriter.Write in JSON-returning handler") p.Response.WriteHeader(http.StatusTeapot) @@ -352,8 +358,9 @@ httprequest.Route `httprequest:"GET /foo/:bar"` A string `httprequest:"bar,path"` } - return func(s *testStruct) { + return func(p httprequest.Params, s *testStruct) { c.Check(s.A, gc.Equals, "val") + c.Assert(p.PathPattern, gc.Equals, "/foo/:bar") } }, req: &http.Request{}, @@ -462,19 +469,22 @@ } var handlersTests = []struct { - calledMethod string - callParams httptesting.JSONCallParams + calledMethod string + callParams httptesting.JSONCallParams + expectPathPattern string }{{ calledMethod: "M1", callParams: httptesting.JSONCallParams{ URL: "/m1/99", }, + expectPathPattern: "/m1/:p", }, { calledMethod: "M2", callParams: httptesting.JSONCallParams{ URL: "/m2/99", ExpectBody: 999, }, + expectPathPattern: "/m2/:p", }, { calledMethod: "M3", callParams: httptesting.JSONCallParams{ @@ -484,6 +494,7 @@ }, ExpectStatus: http.StatusInternalServerError, }, + expectPathPattern: "/m3/:p", }, { calledMethod: "M3Post", callParams: httptesting.JSONCallParams{ @@ -491,6 +502,7 @@ URL: "/m3/99", JSONBody: make(map[string]interface{}), }, + expectPathPattern: "/m3/:p", }} func (*handlerSuite) TestHandlers(c *gc.C) { @@ -530,10 +542,13 @@ } for i, test := range handlersTests { c.Logf("test %d: %s", i, test.calledMethod) - handleVal.calledMethod = "" + handleVal = testHandlers{ + c: c, + } test.callParams.Handler = router httptesting.AssertJSONCall(c, test.callParams) c.Assert(handleVal.calledMethod, gc.Equals, test.calledMethod) + c.Assert(handleVal.p.PathPattern, gc.Equals, test.expectPathPattern) } } @@ -552,6 +567,7 @@ h.c.Check(p.Response, gc.Equals, h.p.Response) h.c.Check(p.Request, gc.Equals, h.p.Request) h.c.Check(p.PathVar, gc.DeepEquals, h.p.PathVar) + h.c.Check(p.PathPattern, gc.Equals, "/m1/:p") } func (h *testHandlers) M2(arg *struct { @@ -890,6 +906,7 @@ handler := errorMapper.HandleErrors(func(p httprequest.Params) error { c.Assert(p.Request, jc.DeepEquals, req) c.Assert(p.PathVar, jc.DeepEquals, params) + c.Assert(p.PathPattern, gc.Equals, "") return errUnauth }) rec := httptest.NewRecorder() @@ -905,6 +922,7 @@ handler = errorMapper.HandleErrors(func(p httprequest.Params) error { c.Assert(p.Request, jc.DeepEquals, req) c.Assert(p.PathVar, jc.DeepEquals, params) + c.Assert(p.PathPattern, gc.Equals, "") p.Response.WriteHeader(http.StatusCreated) p.Response.Write([]byte("something")) return nil @@ -956,6 +974,7 @@ handler := errorMapper.HandleJSON(func(p httprequest.Params) (interface{}, error) { c.Assert(p.Request, jc.DeepEquals, req) c.Assert(p.PathVar, jc.DeepEquals, params) + c.Assert(p.PathPattern, gc.Equals, "") return nil, errUnauth }) rec := httptest.NewRecorder() @@ -971,6 +990,7 @@ handler = errorMapper.HandleJSON(func(p httprequest.Params) (interface{}, error) { c.Assert(p.Request, jc.DeepEquals, req) c.Assert(p.PathVar, jc.DeepEquals, params) + c.Assert(p.PathPattern, gc.Equals, "") p.Response.Header().Set("Some-Header", "value") return "something", nil }) diff -Nru charm-2.1.1/src/github.com/juju/httprequest/type.go charm-2.2.0/src/github.com/juju/httprequest/type.go --- charm-2.1.1/src/github.com/juju/httprequest/type.go 2016-03-17 19:46:08.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/httprequest/type.go 2016-09-03 01:25:03.000000000 +0000 @@ -38,6 +38,11 @@ Response http.ResponseWriter Request *http.Request PathVar httprouter.Params + // PathPattern holds the path pattern matched by httprouter. + // It is only set where httprequest has the information; + // that is where the call was made by ErrorMapper.Handler + // or ErrorMapper.Handlers. + PathPattern string } // resultMaker is provided to the unmarshal functions. diff -Nru charm-2.1.1/src/github.com/juju/httprequest/unmarshal_test.go charm-2.2.0/src/github.com/juju/httprequest/unmarshal_test.go --- charm-2.1.1/src/github.com/juju/httprequest/unmarshal_test.go 2016-03-17 19:46:08.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/httprequest/unmarshal_test.go 2016-09-03 01:25:03.000000000 +0000 @@ -133,22 +133,6 @@ }, }, }, { - about: "unexported embedded type for body works ok", - val: struct { - sFG `httprequest:",body"` - }{ - sFG: sFG{ - F: 99, - G: 100, - }, - }, - params: httprequest.Params{ - Request: &http.Request{ - Header: http.Header{"Content-Type": {"application/json"}}, - Body: body(`{"F": 99, "G": 100}`), - }, - }, -}, { about: "unexported type for body is ignored", val: struct { foo sFG `httprequest:",body"` diff -Nru charm-2.1.1/src/github.com/juju/idmclient/groupcache.go charm-2.2.0/src/github.com/juju/idmclient/groupcache.go --- charm-2.1.1/src/github.com/juju/idmclient/groupcache.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/idmclient/groupcache.go 2016-09-03 01:25:11.000000000 +0000 @@ -0,0 +1,79 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package idmclient + +import ( + "sort" + "time" + + "github.com/juju/utils/cache" + "gopkg.in/errgo.v1" + + "github.com/juju/idmclient/params" +) + +// GroupCache holds a cache of group membership information. +type GroupCache struct { + cache *cache.Cache + client *Client +} + +// NewGroupCache returns a GroupCache that will cache +// group membership information. +// +// It will cache results for at most cacheTime. +// +// Note that use of this type should be avoided when possible - in +// the future it may not be possible to enumerate group membership +// for a user. +func NewGroupCache(c *Client, cacheTime time.Duration) *GroupCache { + return &GroupCache{ + cache: cache.New(cacheTime), + client: c, + } +} + +// Groups returns the set of groups that the user is a member of. +func (gc *GroupCache) Groups(username string) ([]string, error) { + groupMap, err := gc.groupMap(username) + if err != nil { + return nil, errgo.Mask(err) + } + groups := make([]string, 0, len(groupMap)) + for g := range groupMap { + groups = append(groups, g) + } + sort.Strings(groups) + return groups, nil +} + +func (gc *GroupCache) groupMap(username string) (map[string]bool, error) { + groups0, err := gc.cache.Get(username, func() (interface{}, error) { + groups, err := gc.client.UserGroups(¶ms.UserGroupsRequest{ + Username: params.Username(username), + }) + if err != nil && errgo.Cause(err) != params.ErrNotFound { + return nil, errgo.Mask(err) + } + groupMap := make(map[string]bool) + for _, g := range groups { + groupMap[g] = true + } + return groupMap, nil + }) + if err != nil { + return nil, errgo.Notef(err, "cannot fetch groups") + } + return groups0.(map[string]bool), nil +} + +// CacheEvict evicts username from the cache. +func (c *GroupCache) CacheEvict(username string) { + c.cache.Evict(username) +} + +// CacheEvictAll evicts everything from the cache. +func (c *GroupCache) CacheEvictAll() { + c.cache.EvictAll() +} diff -Nru charm-2.1.1/src/github.com/juju/idmclient/permcheck.go charm-2.2.0/src/github.com/juju/idmclient/permcheck.go --- charm-2.1.1/src/github.com/juju/idmclient/permcheck.go 2016-03-17 19:46:06.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/idmclient/permcheck.go 2016-09-03 01:25:11.000000000 +0000 @@ -6,16 +6,12 @@ import ( "time" - "github.com/juju/utils/cache" "gopkg.in/errgo.v1" - - "github.com/juju/idmclient/params" ) // PermChecker provides a way to query ACLs using the identity client. type PermChecker struct { - cache *cache.Cache - client *Client + cache *GroupCache } // NewPermChecker returns a permission checker @@ -24,8 +20,15 @@ // It will cache results for at most cacheTime. func NewPermChecker(c *Client, cacheTime time.Duration) *PermChecker { return &PermChecker{ - cache: cache.New(cacheTime), - client: c, + cache: NewGroupCache(c, cacheTime), + } +} + +// NewPermCheckerWithCache returns a new PermChecker using +// the given cache for its group queries. +func NewPermCheckerWithCache(cache *GroupCache) *PermChecker { + return &PermChecker{ + cache: cache, } } @@ -41,23 +44,10 @@ return true, nil } } - groups0, err := c.cache.Get(username, func() (interface{}, error) { - groups, err := c.client.UserGroups(¶ms.UserGroupsRequest{ - Username: params.Username(username), - }) - if err != nil && errgo.Cause(err) != params.ErrNotFound { - return nil, errgo.Mask(err) - } - groupMap := make(map[string]bool) - for _, g := range groups { - groupMap[g] = true - } - return groupMap, nil - }) + groups, err := c.cache.groupMap(username) if err != nil { - return false, errgo.Notef(err, "cannot fetch groups") + return false, errgo.Mask(err) } - groups := groups0.(map[string]bool) for _, a := range acl { if groups[a] { return true, nil @@ -68,10 +58,10 @@ // CacheEvict evicts username from the cache. func (c *PermChecker) CacheEvict(username string) { - c.cache.Evict(username) + c.cache.CacheEvict(username) } // CacheEvictAll evicts everything from the cache. func (c *PermChecker) CacheEvictAll() { - c.cache.EvictAll() + c.cache.CacheEvictAll() } diff -Nru charm-2.1.1/src/github.com/juju/idmclient/permcheck_test.go charm-2.2.0/src/github.com/juju/idmclient/permcheck_test.go --- charm-2.1.1/src/github.com/juju/idmclient/permcheck_test.go 2016-04-01 15:24:11.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/idmclient/permcheck_test.go 2016-09-03 01:25:11.000000000 +0000 @@ -6,6 +6,7 @@ import ( "time" + jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/idmclient" @@ -62,3 +63,38 @@ c.Assert(err, gc.IsNil) c.Assert(ok, gc.Equals, true) } + +func (s *permCheckerSuite) TestGroupCache(c *gc.C) { + srv := idmtest.NewServer() + srv.AddUser("alice", "somegroup", "othergroup") + + client := idmclient.New(idmclient.NewParams{ + BaseURL: srv.URL.String(), + Client: srv.Client("alice"), + }) + + cache := idmclient.NewGroupCache(client, time.Hour) + + // If the user isn't found, we retturn no groups. + g, err := cache.Groups("bob") + c.Assert(err, gc.IsNil) + c.Assert(g, gc.HasLen, 0) + + g, err = cache.Groups("alice") + c.Assert(err, gc.IsNil) + c.Assert(g, jc.DeepEquals, []string{"othergroup", "somegroup"}) + + srv.AddUser("bob", "beatles") + + // The group details are currently cached by the client, + // so we'll still see the original group membership. + g, err = cache.Groups("bob") + c.Assert(err, gc.IsNil) + c.Assert(g, gc.HasLen, 0) + + // Clearing the cache allows it to succeed. + cache.CacheEvictAll() + g, err = cache.Groups("bob") + c.Assert(err, gc.IsNil) + c.Assert(g, jc.DeepEquals, []string{"beatles"}) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/bootstrap.go charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/bootstrap.go --- charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/bootstrap.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/bootstrap.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,261 @@ +// Copyright 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package agentbootstrap + +import ( + "github.com/juju/errors" + "github.com/juju/loggo" + "github.com/juju/names" + "github.com/juju/utils" + "github.com/juju/utils/series" + + "github.com/juju/juju/agent" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/constraints" + "github.com/juju/juju/controller/modelmanager" + "github.com/juju/juju/environs/config" + "github.com/juju/juju/instance" + "github.com/juju/juju/mongo" + "github.com/juju/juju/network" + "github.com/juju/juju/state" + "github.com/juju/juju/state/multiwatcher" +) + +var logger = loggo.GetLogger("juju.agent.agentbootstrap") + +// BootstrapMachineConfig holds configuration information +// to attach to the bootstrap machine. +type BootstrapMachineConfig struct { + // Addresses holds the bootstrap machine's addresses. + Addresses []network.Address + + // BootstrapConstraints holds the bootstrap machine's constraints. + BootstrapConstraints constraints.Value + + // ModelConstraints holds the model-level constraints. + ModelConstraints constraints.Value + + // Jobs holds the jobs that the machine agent will run. + Jobs []multiwatcher.MachineJob + + // InstanceId holds the instance id of the bootstrap machine. + InstanceId instance.Id + + // Characteristics holds hardware information on the + // bootstrap machine. + Characteristics instance.HardwareCharacteristics + + // SharedSecret is the Mongo replica set shared secret (keyfile). + SharedSecret string +} + +// InitializeState should be called on the bootstrap machine's agent +// configuration. It uses that information to create the controller, dial the +// controller, and initialize it. It also generates a new password for the +// bootstrap machine and calls Write to save the the configuration. +// +// The cfg values will be stored in the state's ModelConfig; the +// machineCfg values will be used to configure the bootstrap Machine, +// and its constraints will be also be used for the model-level +// constraints. The connection to the controller will respect the +// given timeout parameter. +// +// InitializeState returns the newly initialized state and bootstrap +// machine. If it fails, the state may well be irredeemably compromised. +func InitializeState( + adminUser names.UserTag, + c agent.ConfigSetter, + cfg *config.Config, + hostedModelConfigAttrs map[string]interface{}, + machineCfg BootstrapMachineConfig, + dialOpts mongo.DialOpts, + policy state.Policy, +) (_ *state.State, _ *state.Machine, resultErr error) { + if c.Tag() != names.NewMachineTag(agent.BootstrapMachineId) { + return nil, nil, errors.Errorf("InitializeState not called with bootstrap machine's configuration") + } + servingInfo, ok := c.StateServingInfo() + if !ok { + return nil, nil, errors.Errorf("state serving information not available") + } + // N.B. no users are set up when we're initializing the state, + // so don't use any tag or password when opening it. + info, ok := c.MongoInfo() + if !ok { + return nil, nil, errors.Errorf("stateinfo not available") + } + info.Tag = nil + info.Password = c.OldPassword() + + if err := initMongoAdminUser(info.Info, dialOpts, info.Password); err != nil { + return nil, nil, errors.Annotate(err, "failed to initialize mongo admin user") + } + + logger.Debugf("initializing address %v", info.Addrs) + st, err := state.Initialize(adminUser, info, cfg, dialOpts, policy) + if err != nil { + return nil, nil, errors.Errorf("failed to initialize state: %v", err) + } + logger.Debugf("connected to initial state") + defer func() { + if resultErr != nil { + st.Close() + } + }() + servingInfo.SharedSecret = machineCfg.SharedSecret + c.SetStateServingInfo(servingInfo) + + // Filter out any LXC bridge addresses from the machine addresses. + machineCfg.Addresses = network.FilterLXCAddresses(machineCfg.Addresses) + + if err = initAPIHostPorts(c, st, machineCfg.Addresses, servingInfo.APIPort); err != nil { + return nil, nil, err + } + ssi := paramsStateServingInfoToStateStateServingInfo(servingInfo) + if err := st.SetStateServingInfo(ssi); err != nil { + return nil, nil, errors.Errorf("cannot set state serving info: %v", err) + } + m, err := initConstraintsAndBootstrapMachine(c, st, machineCfg) + if err != nil { + return nil, nil, err + } + + // Create the initial hosted model, with the model config passed to + // bootstrap, which contains the UUID, name for the hosted model, + // and any user supplied config. + attrs := make(map[string]interface{}) + for k, v := range hostedModelConfigAttrs { + attrs[k] = v + } + hostedModelConfig, err := modelmanager.ModelConfigCreator{}.NewModelConfig(modelmanager.IsAdmin, cfg, attrs) + if err != nil { + return nil, nil, errors.Annotate(err, "creating hosted model config") + } + _, hostedModelState, err := st.NewModel(state.ModelArgs{ + Config: hostedModelConfig, + Owner: adminUser, + }) + if err != nil { + return nil, nil, errors.Annotate(err, "creating hosted model") + } + if err := hostedModelState.SetModelConstraints(machineCfg.ModelConstraints); err != nil { + return nil, nil, errors.Annotate(err, "cannot set initial hosted model constraints") + } + hostedModelState.Close() + + return st, m, nil +} + +func paramsStateServingInfoToStateStateServingInfo(i params.StateServingInfo) state.StateServingInfo { + return state.StateServingInfo{ + APIPort: i.APIPort, + StatePort: i.StatePort, + Cert: i.Cert, + PrivateKey: i.PrivateKey, + CAPrivateKey: i.CAPrivateKey, + SharedSecret: i.SharedSecret, + SystemIdentity: i.SystemIdentity, + } +} + +func initConstraintsAndBootstrapMachine(c agent.ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) { + if err := st.SetModelConstraints(cfg.ModelConstraints); err != nil { + return nil, errors.Annotate(err, "cannot set initial model constraints") + } + m, err := initBootstrapMachine(c, st, cfg) + if err != nil { + return nil, errors.Annotate(err, "cannot initialize bootstrap machine") + } + return m, nil +} + +// initMongoAdminUser adds the admin user with the specified +// password to the admin database in Mongo. +func initMongoAdminUser(info mongo.Info, dialOpts mongo.DialOpts, password string) error { + session, err := mongo.DialWithInfo(info, dialOpts) + if err != nil { + return err + } + defer session.Close() + return mongo.SetAdminMongoPassword(session, mongo.AdminUser, password) +} + +// initBootstrapMachine initializes the initial bootstrap machine in state. +func initBootstrapMachine(c agent.ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) { + logger.Infof("initialising bootstrap machine with config: %+v", cfg) + + jobs := make([]state.MachineJob, len(cfg.Jobs)) + for i, job := range cfg.Jobs { + machineJob, err := machineJobFromParams(job) + if err != nil { + return nil, errors.Errorf("invalid bootstrap machine job %q: %v", job, err) + } + jobs[i] = machineJob + } + m, err := st.AddOneMachine(state.MachineTemplate{ + Addresses: cfg.Addresses, + Series: series.HostSeries(), + Nonce: agent.BootstrapNonce, + Constraints: cfg.BootstrapConstraints, + InstanceId: cfg.InstanceId, + HardwareCharacteristics: cfg.Characteristics, + Jobs: jobs, + }) + if err != nil { + return nil, errors.Errorf("cannot create bootstrap machine in state: %v", err) + } + if m.Id() != agent.BootstrapMachineId { + return nil, errors.Errorf("bootstrap machine expected id 0, got %q", m.Id()) + } + // Read the machine agent's password and change it to + // a new password (other agents will change their password + // via the API connection). + logger.Debugf("create new random password for machine %v", m.Id()) + + newPassword, err := utils.RandomPassword() + if err != nil { + return nil, err + } + if err := m.SetPassword(newPassword); err != nil { + return nil, err + } + if err := m.SetMongoPassword(newPassword); err != nil { + return nil, err + } + c.SetPassword(newPassword) + return m, nil +} + +// initAPIHostPorts sets the initial API host/port addresses in state. +func initAPIHostPorts(c agent.ConfigSetter, st *state.State, addrs []network.Address, apiPort int) error { + var hostPorts []network.HostPort + // First try to select the correct address using the default space where all + // API servers should be accessible on. + spaceAddr, ok := network.SelectAddressBySpaces(addrs) + if ok { + logger.Debugf("selected %q as API address", spaceAddr.Value) + hostPorts = network.AddressesWithPort([]network.Address{spaceAddr}, apiPort) + } else { + // Fallback to using all instead. + hostPorts = network.AddressesWithPort(addrs, apiPort) + } + + return st.SetAPIHostPorts([][]network.HostPort{hostPorts}) +} + +// machineJobFromParams returns the job corresponding to params.MachineJob. +// TODO(dfc) this function should live in apiserver/params, move there once +// state does not depend on apiserver/params +func machineJobFromParams(job multiwatcher.MachineJob) (state.MachineJob, error) { + switch job { + case multiwatcher.JobHostUnits: + return state.JobHostUnits, nil + case multiwatcher.JobManageModel: + return state.JobManageModel, nil + case multiwatcher.JobManageNetworking: + return state.JobManageNetworking, nil + default: + return -1, errors.Errorf("invalid machine job %q", job) + } +} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/bootstrap_test.go charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/bootstrap_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/bootstrap_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/bootstrap_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,359 @@ +// Copyright 2012, 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package agentbootstrap_test + +import ( + "io/ioutil" + "net" + "path/filepath" + + "github.com/juju/names" + gitjujutesting "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils" + "github.com/juju/utils/series" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/agent" + "github.com/juju/juju/agent/agentbootstrap" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/constraints" + "github.com/juju/juju/environs" + "github.com/juju/juju/environs/config" + "github.com/juju/juju/instance" + "github.com/juju/juju/mongo" + "github.com/juju/juju/network" + "github.com/juju/juju/provider/dummy" + "github.com/juju/juju/state" + "github.com/juju/juju/state/multiwatcher" + "github.com/juju/juju/testing" + jujuversion "github.com/juju/juju/version" +) + +type bootstrapSuite struct { + testing.BaseSuite + mgoInst gitjujutesting.MgoInstance +} + +var _ = gc.Suite(&bootstrapSuite{}) + +func (s *bootstrapSuite) SetUpTest(c *gc.C) { + s.BaseSuite.SetUpTest(c) + // Don't use MgoSuite, because we need to ensure + // we have a fresh mongo for each test case. + s.mgoInst.EnableAuth = true + err := s.mgoInst.Start(testing.Certs) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *bootstrapSuite) TearDownTest(c *gc.C) { + s.mgoInst.Destroy() + s.BaseSuite.TearDownTest(c) +} + +func (s *bootstrapSuite) TestInitializeState(c *gc.C) { + dataDir := c.MkDir() + + lxcFakeNetConfig := filepath.Join(c.MkDir(), "lxc-net") + netConf := []byte(` + # comments ignored +LXC_BR= ignored +LXC_ADDR = "fooo" +LXC_BRIDGE="foobar" # detected +anything else ignored +LXC_BRIDGE="ignored"`[1:]) + err := ioutil.WriteFile(lxcFakeNetConfig, netConf, 0644) + c.Assert(err, jc.ErrorIsNil) + s.PatchValue(&network.InterfaceByNameAddrs, func(name string) ([]net.Addr, error) { + c.Assert(name, gc.Equals, "foobar") + return []net.Addr{ + &net.IPAddr{IP: net.IPv4(10, 0, 3, 1)}, + &net.IPAddr{IP: net.IPv4(10, 0, 3, 4)}, + }, nil + }) + s.PatchValue(&network.LXCNetDefaultConfig, lxcFakeNetConfig) + + configParams := agent.AgentConfigParams{ + Paths: agent.Paths{DataDir: dataDir}, + Tag: names.NewMachineTag("0"), + UpgradedToVersion: jujuversion.Current, + StateAddresses: []string{s.mgoInst.Addr()}, + CACert: testing.CACert, + Password: testing.DefaultMongoPassword, + Model: testing.ModelTag, + } + servingInfo := params.StateServingInfo{ + Cert: testing.ServerCert, + PrivateKey: testing.ServerKey, + CAPrivateKey: testing.CAKey, + APIPort: 1234, + StatePort: s.mgoInst.Port(), + SystemIdentity: "def456", + } + + cfg, err := agent.NewStateMachineConfig(configParams, servingInfo) + c.Assert(err, jc.ErrorIsNil) + + _, available := cfg.StateServingInfo() + c.Assert(available, jc.IsTrue) + expectBootstrapConstraints := constraints.MustParse("mem=1024M") + expectModelConstraints := constraints.MustParse("mem=512M") + expectHW := instance.MustParseHardware("mem=2048M") + initialAddrs := network.NewAddresses( + "zeroonetwothree", + "0.1.2.3", + "10.0.3.1", // lxc bridge address filtered. + "10.0.3.4", // lxc bridge address filtered (-"-). + "10.0.3.3", // not a lxc bridge address + ) + mcfg := agentbootstrap.BootstrapMachineConfig{ + Addresses: initialAddrs, + BootstrapConstraints: expectBootstrapConstraints, + ModelConstraints: expectModelConstraints, + Jobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel}, + InstanceId: "i-bootstrap", + Characteristics: expectHW, + SharedSecret: "abc123", + } + filteredAddrs := network.NewAddresses( + "zeroonetwothree", + "0.1.2.3", + "10.0.3.3", + ) + + // Prepare bootstrap config, so we can use it in the state policy. + provider, err := environs.Provider("dummy") + c.Assert(err, jc.ErrorIsNil) + envAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{ + "agent-version": jujuversion.Current.String(), + "not-for-hosted": "foo", + }) + envCfg, err := config.New(config.NoDefaults, envAttrs) + c.Assert(err, jc.ErrorIsNil) + envCfg, err = provider.BootstrapConfig(environs.BootstrapConfigParams{Config: envCfg}) + c.Assert(err, jc.ErrorIsNil) + defer dummy.Reset(c) + + hostedModelUUID := utils.MustNewUUID().String() + hostedModelConfigAttrs := map[string]interface{}{ + "name": "hosted", + "uuid": hostedModelUUID, + } + + adminUser := names.NewLocalUserTag("agent-admin") + st, m, err := agentbootstrap.InitializeState( + adminUser, cfg, envCfg, hostedModelConfigAttrs, mcfg, + mongo.DefaultDialOpts(), environs.NewStatePolicy(), + ) + c.Assert(err, jc.ErrorIsNil) + defer st.Close() + + err = cfg.Write() + c.Assert(err, jc.ErrorIsNil) + + // Check that the environment has been set up. + env, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + c.Assert(env.UUID(), gc.Equals, envCfg.UUID()) + + // Check that initial admin user has been set up correctly. + modelTag := env.Tag().(names.ModelTag) + s.assertCanLogInAsAdmin(c, modelTag, testing.DefaultMongoPassword) + user, err := st.User(env.Owner()) + c.Assert(err, jc.ErrorIsNil) + c.Assert(user.PasswordValid(testing.DefaultMongoPassword), jc.IsTrue) + + // Check that controller model configuration has been added, and + // model constraints set. + newEnvCfg, err := st.ModelConfig() + c.Assert(err, jc.ErrorIsNil) + c.Assert(newEnvCfg.AllAttrs(), gc.DeepEquals, envCfg.AllAttrs()) + gotModelConstraints, err := st.ModelConstraints() + c.Assert(err, jc.ErrorIsNil) + c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints) + + // Check that the hosted model has been added, and model constraints + // set. + hostedModelSt, err := st.ForModel(names.NewModelTag(hostedModelUUID)) + c.Assert(err, jc.ErrorIsNil) + defer hostedModelSt.Close() + gotModelConstraints, err = hostedModelSt.ModelConstraints() + c.Assert(err, jc.ErrorIsNil) + c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints) + hostedModel, err := hostedModelSt.Model() + c.Assert(err, jc.ErrorIsNil) + c.Assert(hostedModel.Name(), gc.Equals, "hosted") + hostedCfg, err := hostedModelSt.ModelConfig() + c.Assert(err, jc.ErrorIsNil) + _, hasUnexpected := hostedCfg.AllAttrs()["not-for-hosted"] + c.Assert(hasUnexpected, jc.IsFalse) + + // Check that the bootstrap machine looks correct. + c.Assert(m.Id(), gc.Equals, "0") + c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageModel}) + c.Assert(m.Series(), gc.Equals, series.HostSeries()) + c.Assert(m.CheckProvisioned(agent.BootstrapNonce), jc.IsTrue) + c.Assert(m.Addresses(), jc.DeepEquals, filteredAddrs) + gotBootstrapConstraints, err := m.Constraints() + c.Assert(err, jc.ErrorIsNil) + c.Assert(gotBootstrapConstraints, gc.DeepEquals, expectBootstrapConstraints) + c.Assert(err, jc.ErrorIsNil) + gotHW, err := m.HardwareCharacteristics() + c.Assert(err, jc.ErrorIsNil) + c.Assert(*gotHW, gc.DeepEquals, expectHW) + + // Check that the API host ports are initialised correctly. + apiHostPorts, err := st.APIHostPorts() + c.Assert(err, jc.ErrorIsNil) + c.Assert(apiHostPorts, jc.DeepEquals, [][]network.HostPort{ + network.AddressesWithPort(filteredAddrs, 1234), + }) + + // Check that the state serving info is initialised correctly. + stateServingInfo, err := st.StateServingInfo() + c.Assert(err, jc.ErrorIsNil) + c.Assert(stateServingInfo, jc.DeepEquals, state.StateServingInfo{ + APIPort: 1234, + StatePort: s.mgoInst.Port(), + Cert: testing.ServerCert, + PrivateKey: testing.ServerKey, + CAPrivateKey: testing.CAKey, + SharedSecret: "abc123", + SystemIdentity: "def456", + }) + + // Check that the machine agent's config has been written + // and that we can use it to connect to the state. + machine0 := names.NewMachineTag("0") + newCfg, err := agent.ReadConfig(agent.ConfigPath(dataDir, machine0)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(newCfg.Tag(), gc.Equals, machine0) + info, ok := cfg.MongoInfo() + c.Assert(ok, jc.IsTrue) + c.Assert(info.Password, gc.Not(gc.Equals), testing.DefaultMongoPassword) + st1, err := state.Open(newCfg.Model(), info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) + c.Assert(err, jc.ErrorIsNil) + defer st1.Close() +} + +func (s *bootstrapSuite) TestInitializeStateWithStateServingInfoNotAvailable(c *gc.C) { + configParams := agent.AgentConfigParams{ + Paths: agent.Paths{DataDir: c.MkDir()}, + Tag: names.NewMachineTag("0"), + UpgradedToVersion: jujuversion.Current, + StateAddresses: []string{s.mgoInst.Addr()}, + CACert: testing.CACert, + Password: "fake", + Model: testing.ModelTag, + } + cfg, err := agent.NewAgentConfig(configParams) + c.Assert(err, jc.ErrorIsNil) + + _, available := cfg.StateServingInfo() + c.Assert(available, jc.IsFalse) + + adminUser := names.NewLocalUserTag("agent-admin") + _, _, err = agentbootstrap.InitializeState(adminUser, cfg, nil, nil, agentbootstrap.BootstrapMachineConfig{}, mongo.DefaultDialOpts(), environs.NewStatePolicy()) + // InitializeState will fail attempting to get the api port information + c.Assert(err, gc.ErrorMatches, "state serving information not available") +} + +func (s *bootstrapSuite) TestInitializeStateFailsSecondTime(c *gc.C) { + dataDir := c.MkDir() + + configParams := agent.AgentConfigParams{ + Paths: agent.Paths{DataDir: dataDir}, + Tag: names.NewMachineTag("0"), + UpgradedToVersion: jujuversion.Current, + StateAddresses: []string{s.mgoInst.Addr()}, + CACert: testing.CACert, + Password: testing.DefaultMongoPassword, + Model: testing.ModelTag, + } + cfg, err := agent.NewAgentConfig(configParams) + c.Assert(err, jc.ErrorIsNil) + cfg.SetStateServingInfo(params.StateServingInfo{ + APIPort: 5555, + StatePort: s.mgoInst.Port(), + Cert: "foo", + PrivateKey: "bar", + SharedSecret: "baz", + SystemIdentity: "qux", + }) + expectHW := instance.MustParseHardware("mem=2048M") + mcfg := agentbootstrap.BootstrapMachineConfig{ + BootstrapConstraints: constraints.MustParse("mem=1024M"), + Jobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel}, + InstanceId: "i-bootstrap", + Characteristics: expectHW, + } + envAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{ + "agent-version": jujuversion.Current.String(), + }) + envCfg, err := config.New(config.NoDefaults, envAttrs) + c.Assert(err, jc.ErrorIsNil) + + hostedModelConfigAttrs := map[string]interface{}{ + "name": "hosted", + "uuid": utils.MustNewUUID().String(), + } + + adminUser := names.NewLocalUserTag("agent-admin") + st, _, err := agentbootstrap.InitializeState( + adminUser, cfg, envCfg, hostedModelConfigAttrs, mcfg, + mongo.DefaultDialOpts(), state.Policy(nil), + ) + c.Assert(err, jc.ErrorIsNil) + st.Close() + + st, _, err = agentbootstrap.InitializeState(adminUser, cfg, envCfg, nil, mcfg, mongo.DefaultDialOpts(), environs.NewStatePolicy()) + if err == nil { + st.Close() + } + c.Assert(err, gc.ErrorMatches, "failed to initialize mongo admin user: cannot set admin password: not authorized .*") +} + +func (s *bootstrapSuite) TestMachineJobFromParams(c *gc.C) { + var tests = []struct { + name multiwatcher.MachineJob + want state.MachineJob + err string + }{{ + name: multiwatcher.JobHostUnits, + want: state.JobHostUnits, + }, { + name: multiwatcher.JobManageModel, + want: state.JobManageModel, + }, { + name: multiwatcher.JobManageNetworking, + want: state.JobManageNetworking, + }, { + name: "invalid", + want: -1, + err: `invalid machine job "invalid"`, + }} + for _, test := range tests { + got, err := agentbootstrap.MachineJobFromParams(test.name) + if err != nil { + c.Check(err, gc.ErrorMatches, test.err) + } + c.Check(got, gc.Equals, test.want) + } +} + +func (s *bootstrapSuite) assertCanLogInAsAdmin(c *gc.C, modelTag names.ModelTag, password string) { + info := &mongo.MongoInfo{ + Info: mongo.Info{ + Addrs: []string{s.mgoInst.Addr()}, + CACert: testing.CACert, + }, + Tag: nil, // admin user + Password: password, + } + st, err := state.Open(modelTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) + c.Assert(err, jc.ErrorIsNil) + defer st.Close() + _, err = st.Machine("0") + c.Assert(err, jc.ErrorIsNil) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/export_test.go charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/export_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,8 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package agentbootstrap + +var ( + MachineJobFromParams = machineJobFromParams +) diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/package_test.go charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/package_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/agentbootstrap/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/agentbootstrap/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package agentbootstrap_test + +import ( + stdtesting "testing" + + "github.com/juju/juju/testing" +) + +func Test(t *stdtesting.T) { + testing.MgoTestPackage(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/agent.go charm-2.2.0/src/github.com/juju/juju/agent/agent.go --- charm-2.1.1/src/github.com/juju/juju/agent/agent.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/agent.go 2016-04-28 06:03:34.000000000 +0000 @@ -20,6 +20,7 @@ "github.com/juju/utils" "github.com/juju/utils/series" "github.com/juju/utils/shell" + "github.com/juju/version" "github.com/juju/juju/api" "github.com/juju/juju/apiserver/params" @@ -27,16 +28,16 @@ "github.com/juju/juju/mongo" "github.com/juju/juju/network" "github.com/juju/juju/state/multiwatcher" - "github.com/juju/juju/version" ) var logger = loggo.GetLogger("juju.agent") const ( - // UninstallAgentFile is the name of the file inside the data - // dir that, if it exists, will cause a machine agent to uninstall - // when it receives the termination signal. - UninstallAgentFile = "uninstall-agent" + // BootstrapNonce is used as a nonce for the initial controller machine. + BootstrapNonce = "user-admin:bootstrap" + + // BootstrapMachineId is the ID of the initial controller machine. + BootstrapMachineId = "0" ) // These are base values used for the corresponding defaults. @@ -154,6 +155,7 @@ const ( LxcBridge = "LXC_BRIDGE" + LxdBridge = "LXD_BRIDGE" ProviderType = "PROVIDER_TYPE" ContainerType = "CONTAINER_TYPE" Namespace = "NAMESPACE" @@ -278,6 +280,9 @@ // SetAPIHostPorts sets the API host/port addresses to connect to. SetAPIHostPorts(servers [][]network.HostPort) + // SetCACert sets the CA cert used for validating API connections. + SetCACert(string) + // Migrate takes an existing agent config and applies the given // parameters to change it. // @@ -570,20 +575,17 @@ } var addrs []string for _, serverHostPorts := range servers { - // Try the preferred approach first. - serverHP, ok := network.SelectHostPortBySpace(serverHostPorts, network.DefaultSpace) - if ok { - addrs = append(addrs, serverHP.NetAddr()) - } else { - // Fallback to the legacy approach. - hps := network.SelectInternalHostPorts(serverHostPorts, false) - addrs = append(addrs, hps...) - } + hps := network.SelectInternalHostPorts(serverHostPorts, false) + addrs = append(addrs, hps...) } c.apiDetails.addresses = addrs logger.Infof("API server address details %q written to agent config as %q", servers, addrs) } +func (c *configInternal) SetCACert(cert string) { + c.caCert = cert +} + func (c *configInternal) SetValue(key, value string) { if value == "" { delete(c.values, key) diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/agent_test.go charm-2.2.0/src/github.com/juju/juju/agent/agent_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/agent_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/agent_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,6 +11,7 @@ "github.com/juju/names" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/agent" @@ -20,7 +21,7 @@ "github.com/juju/juju/network" "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/testing" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type suite struct { @@ -55,7 +56,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, }, checkErr: "password not found in configuration", }, { @@ -63,7 +64,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", }, checkErr: "model not found in configuration", @@ -72,7 +73,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", Model: names.NewModelTag("uuid"), }, @@ -82,7 +83,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", Model: testing.ModelTag, }, @@ -92,7 +93,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", Model: testing.ModelTag, @@ -103,7 +104,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", Model: testing.ModelTag, @@ -115,7 +116,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", Model: testing.ModelTag, @@ -127,7 +128,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", Model: testing.ModelTag, @@ -138,7 +139,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", Model: testing.ModelTag, @@ -149,7 +150,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", Model: testing.ModelTag, @@ -162,7 +163,7 @@ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), Password: "sekrit", - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, CACert: "ca cert", Model: testing.ModelTag, StateAddresses: []string{"localhost:1234"}, @@ -175,7 +176,7 @@ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), Password: "sekrit", - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, CACert: "ca cert", Model: testing.ModelTag, StateAddresses: []string{"localhost:1234"}, @@ -191,7 +192,7 @@ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), Password: "sekrit", - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, CACert: "ca cert", Model: testing.ModelTag, StateAddresses: []string{"localhost:1234"}, @@ -210,7 +211,7 @@ }, Tag: names.NewMachineTag("1"), Password: "sekrit", - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, CACert: "ca cert", Model: testing.ModelTag, StateAddresses: []string{"localhost:1234"}, @@ -225,7 +226,7 @@ params: agent.AgentConfigParams{ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewUserTag("admin"), // this is a joke, the admin user is nil. - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", }, checkErr: "entity tag must be MachineTag or UnitTag, got names.UserTag", @@ -235,7 +236,7 @@ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewUnitTag("ubuntu/1"), Password: "sekrit", - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Model: testing.ModelTag, CACert: "ca cert", StateAddresses: []string{"localhost:1234"}, @@ -250,7 +251,7 @@ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), Password: "sekrit", - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, CACert: "ca cert", Model: testing.ModelTag, StateAddresses: []string{"localhost:1234"}, @@ -267,7 +268,7 @@ Paths: agent.Paths{DataDir: "/data/dir"}, Tag: names.NewMachineTag("1"), Password: "sekrit", - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, CACert: "ca cert", Model: testing.ModelTag, StateAddresses: []string{"localhost:1234"}, @@ -506,7 +507,7 @@ DataDir: "/data/dir", }, Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", StateAddresses: []string{"localhost:1234"}, @@ -525,7 +526,7 @@ c.Assert(conf.Tag(), gc.Equals, names.NewMachineTag("1")) c.Assert(conf.Dir(), gc.Equals, "/data/dir/agents/machine-1") c.Assert(conf.Nonce(), gc.Equals, "a nonce") - c.Assert(conf.UpgradedToVersion(), jc.DeepEquals, version.Current) + c.Assert(conf.UpgradedToVersion(), jc.DeepEquals, jujuversion.Current) } func (*suite) TestStateServingInfo(c *gc.C) { @@ -723,7 +724,7 @@ conf, err := agent.NewAgentConfig(attributeParams) c.Assert(err, jc.ErrorIsNil) - c.Assert(conf.UpgradedToVersion(), gc.Equals, version.Current) + c.Assert(conf.UpgradedToVersion(), gc.Equals, jujuversion.Current) expectVers := version.MustParse("3.4.5") conf.SetUpgradedToVersion(expectVers) @@ -777,3 +778,12 @@ "elsewhere.net:4444", }) } + +func (*suite) TestSetCACert(c *gc.C) { + conf, err := agent.NewAgentConfig(attributeParams) + c.Assert(err, jc.ErrorIsNil) + c.Assert(conf.CACert(), gc.Equals, "ca cert") + + conf.SetCACert("new ca cert") + c.Assert(conf.CACert(), gc.Equals, "new ca cert") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/bootstrap.go charm-2.2.0/src/github.com/juju/juju/agent/bootstrap.go --- charm-2.1.1/src/github.com/juju/juju/agent/bootstrap.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/bootstrap.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,231 +0,0 @@ -// Copyright 2013 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package agent - -import ( - "github.com/juju/errors" - "github.com/juju/names" - "github.com/juju/utils" - "github.com/juju/utils/series" - - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/constraints" - "github.com/juju/juju/environs/config" - "github.com/juju/juju/instance" - "github.com/juju/juju/mongo" - "github.com/juju/juju/network" - "github.com/juju/juju/state" - "github.com/juju/juju/state/multiwatcher" -) - -const ( - // BootstrapNonce is used as a nonce for the controller machine. - BootstrapNonce = "user-admin:bootstrap" -) - -// BootstrapMachineConfig holds configuration information -// to attach to the bootstrap machine. -type BootstrapMachineConfig struct { - // Addresses holds the bootstrap machine's addresses. - Addresses []network.Address - - // BootstrapConstraints holds the bootstrap machine's constraints. - BootstrapConstraints constraints.Value - - // ModelConstraints holds the model-level constraints. - ModelConstraints constraints.Value - - // Jobs holds the jobs that the machine agent will run. - Jobs []multiwatcher.MachineJob - - // InstanceId holds the instance id of the bootstrap machine. - InstanceId instance.Id - - // Characteristics holds hardware information on the - // bootstrap machine. - Characteristics instance.HardwareCharacteristics - - // SharedSecret is the Mongo replica set shared secret (keyfile). - SharedSecret string -} - -const BootstrapMachineId = "0" - -// InitializeState should be called on the bootstrap machine's agent -// configuration. It uses that information to create the controller, dial the -// controller, and initialize it. It also generates a new password for the -// bootstrap machine and calls Write to save the the configuration. -// -// The envCfg values will be stored in the state's ModelConfig; the -// machineCfg values will be used to configure the bootstrap Machine, -// and its constraints will be also be used for the model-level -// constraints. The connection to the controller will respect the -// given timeout parameter. -// -// InitializeState returns the newly initialized state and bootstrap -// machine. If it fails, the state may well be irredeemably compromised. -func InitializeState(adminUser names.UserTag, c ConfigSetter, envCfg *config.Config, machineCfg BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) { - if c.Tag() != names.NewMachineTag(BootstrapMachineId) { - return nil, nil, errors.Errorf("InitializeState not called with bootstrap machine's configuration") - } - servingInfo, ok := c.StateServingInfo() - if !ok { - return nil, nil, errors.Errorf("state serving information not available") - } - // N.B. no users are set up when we're initializing the state, - // so don't use any tag or password when opening it. - info, ok := c.MongoInfo() - if !ok { - return nil, nil, errors.Errorf("stateinfo not available") - } - info.Tag = nil - info.Password = c.OldPassword() - - if err := initMongoAdminUser(info.Info, dialOpts, info.Password); err != nil { - return nil, nil, errors.Annotate(err, "failed to initialize mongo admin user") - } - - logger.Debugf("initializing address %v", info.Addrs) - st, err := state.Initialize(adminUser, info, envCfg, dialOpts, policy) - if err != nil { - return nil, nil, errors.Errorf("failed to initialize state: %v", err) - } - logger.Debugf("connected to initial state") - defer func() { - if resultErr != nil { - st.Close() - } - }() - servingInfo.SharedSecret = machineCfg.SharedSecret - c.SetStateServingInfo(servingInfo) - - // Filter out any LXC bridge addresses from the machine addresses. - machineCfg.Addresses = network.FilterLXCAddresses(machineCfg.Addresses) - - if err = initAPIHostPorts(c, st, machineCfg.Addresses, servingInfo.APIPort); err != nil { - return nil, nil, err - } - ssi := paramsStateServingInfoToStateStateServingInfo(servingInfo) - if err := st.SetStateServingInfo(ssi); err != nil { - return nil, nil, errors.Errorf("cannot set state serving info: %v", err) - } - m, err := initConstraintsAndBootstrapMachine(c, st, machineCfg) - if err != nil { - return nil, nil, err - } - return st, m, nil -} - -func paramsStateServingInfoToStateStateServingInfo(i params.StateServingInfo) state.StateServingInfo { - return state.StateServingInfo{ - APIPort: i.APIPort, - StatePort: i.StatePort, - Cert: i.Cert, - PrivateKey: i.PrivateKey, - CAPrivateKey: i.CAPrivateKey, - SharedSecret: i.SharedSecret, - SystemIdentity: i.SystemIdentity, - } -} - -func initConstraintsAndBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) { - if err := st.SetModelConstraints(cfg.ModelConstraints); err != nil { - return nil, errors.Errorf("cannot set initial environ constraints: %v", err) - } - m, err := initBootstrapMachine(c, st, cfg) - if err != nil { - return nil, errors.Errorf("cannot initialize bootstrap machine: %v", err) - } - return m, nil -} - -// initMongoAdminUser adds the admin user with the specified -// password to the admin database in Mongo. -func initMongoAdminUser(info mongo.Info, dialOpts mongo.DialOpts, password string) error { - session, err := mongo.DialWithInfo(info, dialOpts) - if err != nil { - return err - } - defer session.Close() - return mongo.SetAdminMongoPassword(session, mongo.AdminUser, password) -} - -// initBootstrapMachine initializes the initial bootstrap machine in state. -func initBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) { - logger.Infof("initialising bootstrap machine with config: %+v", cfg) - - jobs := make([]state.MachineJob, len(cfg.Jobs)) - for i, job := range cfg.Jobs { - machineJob, err := machineJobFromParams(job) - if err != nil { - return nil, errors.Errorf("invalid bootstrap machine job %q: %v", job, err) - } - jobs[i] = machineJob - } - m, err := st.AddOneMachine(state.MachineTemplate{ - Addresses: cfg.Addresses, - Series: series.HostSeries(), - Nonce: BootstrapNonce, - Constraints: cfg.BootstrapConstraints, - InstanceId: cfg.InstanceId, - HardwareCharacteristics: cfg.Characteristics, - Jobs: jobs, - }) - if err != nil { - return nil, errors.Errorf("cannot create bootstrap machine in state: %v", err) - } - if m.Id() != BootstrapMachineId { - return nil, errors.Errorf("bootstrap machine expected id 0, got %q", m.Id()) - } - // Read the machine agent's password and change it to - // a new password (other agents will change their password - // via the API connection). - logger.Debugf("create new random password for machine %v", m.Id()) - - newPassword, err := utils.RandomPassword() - if err != nil { - return nil, err - } - if err := m.SetPassword(newPassword); err != nil { - return nil, err - } - if err := m.SetMongoPassword(newPassword); err != nil { - return nil, err - } - c.SetPassword(newPassword) - return m, nil -} - -// initAPIHostPorts sets the initial API host/port addresses in state. -func initAPIHostPorts(c ConfigSetter, st *state.State, addrs []network.Address, apiPort int) error { - var hostPorts []network.HostPort - // First try to select the correct address using the default space where all - // API servers should be accessible on. - spaceAddr, ok := network.SelectAddressBySpace(addrs, network.DefaultSpace) - if ok { - logger.Debugf("selected %q as API address, using space %q", spaceAddr.Value, network.DefaultSpace) - hostPorts = network.AddressesWithPort([]network.Address{spaceAddr}, apiPort) - } else { - // Fallback to using all instead. - hostPorts = network.AddressesWithPort(addrs, apiPort) - } - - return st.SetAPIHostPorts([][]network.HostPort{hostPorts}) -} - -// machineJobFromParams returns the job corresponding to params.MachineJob. -// TODO(dfc) this function should live in apiserver/params, move there once -// state does not depend on apiserver/params -func machineJobFromParams(job multiwatcher.MachineJob) (state.MachineJob, error) { - switch job { - case multiwatcher.JobHostUnits: - return state.JobHostUnits, nil - case multiwatcher.JobManageModel: - return state.JobManageModel, nil - case multiwatcher.JobManageNetworking: - return state.JobManageNetworking, nil - default: - return -1, errors.Errorf("invalid machine job %q", job) - } -} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/bootstrap_test.go charm-2.2.0/src/github.com/juju/juju/agent/bootstrap_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/bootstrap_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/bootstrap_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,324 +0,0 @@ -// Copyright 2012, 2013 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package agent_test - -import ( - "io/ioutil" - "net" - "path/filepath" - - "github.com/juju/names" - gitjujutesting "github.com/juju/testing" - jc "github.com/juju/testing/checkers" - "github.com/juju/utils" - "github.com/juju/utils/series" - gc "gopkg.in/check.v1" - - "github.com/juju/juju/agent" - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/constraints" - "github.com/juju/juju/environs" - "github.com/juju/juju/environs/config" - "github.com/juju/juju/instance" - "github.com/juju/juju/mongo" - "github.com/juju/juju/network" - "github.com/juju/juju/provider/dummy" - "github.com/juju/juju/state" - "github.com/juju/juju/state/multiwatcher" - "github.com/juju/juju/testing" - "github.com/juju/juju/version" -) - -type bootstrapSuite struct { - testing.BaseSuite - mgoInst gitjujutesting.MgoInstance -} - -var _ = gc.Suite(&bootstrapSuite{}) - -func (s *bootstrapSuite) SetUpTest(c *gc.C) { - s.BaseSuite.SetUpTest(c) - // Don't use MgoSuite, because we need to ensure - // we have a fresh mongo for each test case. - s.mgoInst.EnableAuth = true - err := s.mgoInst.Start(testing.Certs) - c.Assert(err, jc.ErrorIsNil) -} - -func (s *bootstrapSuite) TearDownTest(c *gc.C) { - s.mgoInst.Destroy() - s.BaseSuite.TearDownTest(c) -} - -func (s *bootstrapSuite) TestInitializeState(c *gc.C) { - dataDir := c.MkDir() - - lxcFakeNetConfig := filepath.Join(c.MkDir(), "lxc-net") - netConf := []byte(` - # comments ignored -LXC_BR= ignored -LXC_ADDR = "fooo" -LXC_BRIDGE="foobar" # detected -anything else ignored -LXC_BRIDGE="ignored"`[1:]) - err := ioutil.WriteFile(lxcFakeNetConfig, netConf, 0644) - c.Assert(err, jc.ErrorIsNil) - s.PatchValue(&network.InterfaceByNameAddrs, func(name string) ([]net.Addr, error) { - c.Assert(name, gc.Equals, "foobar") - return []net.Addr{ - &net.IPAddr{IP: net.IPv4(10, 0, 3, 1)}, - &net.IPAddr{IP: net.IPv4(10, 0, 3, 4)}, - }, nil - }) - s.PatchValue(&network.LXCNetDefaultConfig, lxcFakeNetConfig) - - pwHash := utils.UserPasswordHash(testing.DefaultMongoPassword, utils.CompatSalt) - configParams := agent.AgentConfigParams{ - Paths: agent.Paths{DataDir: dataDir}, - Tag: names.NewMachineTag("0"), - UpgradedToVersion: version.Current, - StateAddresses: []string{s.mgoInst.Addr()}, - CACert: testing.CACert, - Password: pwHash, - Model: testing.ModelTag, - } - servingInfo := params.StateServingInfo{ - Cert: testing.ServerCert, - PrivateKey: testing.ServerKey, - CAPrivateKey: testing.CAKey, - APIPort: 1234, - StatePort: s.mgoInst.Port(), - SystemIdentity: "def456", - } - - cfg, err := agent.NewStateMachineConfig(configParams, servingInfo) - c.Assert(err, jc.ErrorIsNil) - - _, available := cfg.StateServingInfo() - c.Assert(available, jc.IsTrue) - expectBootstrapConstraints := constraints.MustParse("mem=1024M") - expectModelConstraints := constraints.MustParse("mem=512M") - expectHW := instance.MustParseHardware("mem=2048M") - initialAddrs := network.NewAddresses( - "zeroonetwothree", - "0.1.2.3", - "10.0.3.1", // lxc bridge address filtered. - "10.0.3.4", // lxc bridge address filtered (-"-). - "10.0.3.3", // not a lxc bridge address - ) - mcfg := agent.BootstrapMachineConfig{ - Addresses: initialAddrs, - BootstrapConstraints: expectBootstrapConstraints, - ModelConstraints: expectModelConstraints, - Jobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel}, - InstanceId: "i-bootstrap", - Characteristics: expectHW, - SharedSecret: "abc123", - } - filteredAddrs := network.NewAddresses( - "zeroonetwothree", - "0.1.2.3", - "10.0.3.3", - ) - envAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{ - "agent-version": version.Current.String(), - "state-id": "1", // needed so policy can Open config - }) - envCfg, err := config.New(config.NoDefaults, envAttrs) - c.Assert(err, jc.ErrorIsNil) - - adminUser := names.NewLocalUserTag("agent-admin") - st, m, err := agent.InitializeState(adminUser, cfg, envCfg, mcfg, mongo.DefaultDialOpts(), environs.NewStatePolicy()) - c.Assert(err, jc.ErrorIsNil) - defer st.Close() - - err = cfg.Write() - c.Assert(err, jc.ErrorIsNil) - - // Check that the environment has been set up. - env, err := st.Model() - c.Assert(err, jc.ErrorIsNil) - uuid, ok := envCfg.UUID() - c.Assert(ok, jc.IsTrue) - c.Assert(env.UUID(), gc.Equals, uuid) - - // Check that initial admin user has been set up correctly. - modelTag := env.Tag().(names.ModelTag) - s.assertCanLogInAsAdmin(c, modelTag, pwHash) - user, err := st.User(env.Owner()) - c.Assert(err, jc.ErrorIsNil) - c.Assert(user.PasswordValid(testing.DefaultMongoPassword), jc.IsTrue) - - // Check that model configuration has been added, and - // model constraints set. - newEnvCfg, err := st.ModelConfig() - c.Assert(err, jc.ErrorIsNil) - c.Assert(newEnvCfg.AllAttrs(), gc.DeepEquals, envCfg.AllAttrs()) - gotModelConstraints, err := st.ModelConstraints() - c.Assert(err, jc.ErrorIsNil) - c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints) - - // Check that the bootstrap machine looks correct. - c.Assert(m.Id(), gc.Equals, "0") - c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageModel}) - c.Assert(m.Series(), gc.Equals, series.HostSeries()) - c.Assert(m.CheckProvisioned(agent.BootstrapNonce), jc.IsTrue) - c.Assert(m.Addresses(), jc.DeepEquals, filteredAddrs) - gotBootstrapConstraints, err := m.Constraints() - c.Assert(err, jc.ErrorIsNil) - c.Assert(gotBootstrapConstraints, gc.DeepEquals, expectBootstrapConstraints) - c.Assert(err, jc.ErrorIsNil) - gotHW, err := m.HardwareCharacteristics() - c.Assert(err, jc.ErrorIsNil) - c.Assert(*gotHW, gc.DeepEquals, expectHW) - - // Check that the API host ports are initialised correctly. - apiHostPorts, err := st.APIHostPorts() - c.Assert(err, jc.ErrorIsNil) - c.Assert(apiHostPorts, jc.DeepEquals, [][]network.HostPort{ - network.AddressesWithPort(filteredAddrs, 1234), - }) - - // Check that the state serving info is initialised correctly. - stateServingInfo, err := st.StateServingInfo() - c.Assert(err, jc.ErrorIsNil) - c.Assert(stateServingInfo, jc.DeepEquals, state.StateServingInfo{ - APIPort: 1234, - StatePort: s.mgoInst.Port(), - Cert: testing.ServerCert, - PrivateKey: testing.ServerKey, - CAPrivateKey: testing.CAKey, - SharedSecret: "abc123", - SystemIdentity: "def456", - }) - - // Check that the machine agent's config has been written - // and that we can use it to connect to the state. - machine0 := names.NewMachineTag("0") - newCfg, err := agent.ReadConfig(agent.ConfigPath(dataDir, machine0)) - c.Assert(err, jc.ErrorIsNil) - c.Assert(newCfg.Tag(), gc.Equals, machine0) - c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), pwHash) - c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), testing.DefaultMongoPassword) - info, ok := cfg.MongoInfo() - c.Assert(ok, jc.IsTrue) - st1, err := state.Open(newCfg.Model(), info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) - c.Assert(err, jc.ErrorIsNil) - defer st1.Close() -} - -func (s *bootstrapSuite) TestInitializeStateWithStateServingInfoNotAvailable(c *gc.C) { - configParams := agent.AgentConfigParams{ - Paths: agent.Paths{DataDir: c.MkDir()}, - Tag: names.NewMachineTag("0"), - UpgradedToVersion: version.Current, - StateAddresses: []string{s.mgoInst.Addr()}, - CACert: testing.CACert, - Password: "fake", - Model: testing.ModelTag, - } - cfg, err := agent.NewAgentConfig(configParams) - c.Assert(err, jc.ErrorIsNil) - - _, available := cfg.StateServingInfo() - c.Assert(available, jc.IsFalse) - - adminUser := names.NewLocalUserTag("agent-admin") - _, _, err = agent.InitializeState(adminUser, cfg, nil, agent.BootstrapMachineConfig{}, mongo.DefaultDialOpts(), environs.NewStatePolicy()) - // InitializeState will fail attempting to get the api port information - c.Assert(err, gc.ErrorMatches, "state serving information not available") -} - -func (s *bootstrapSuite) TestInitializeStateFailsSecondTime(c *gc.C) { - dataDir := c.MkDir() - - pwHash := utils.UserPasswordHash(testing.DefaultMongoPassword, utils.CompatSalt) - configParams := agent.AgentConfigParams{ - Paths: agent.Paths{DataDir: dataDir}, - Tag: names.NewMachineTag("0"), - UpgradedToVersion: version.Current, - StateAddresses: []string{s.mgoInst.Addr()}, - CACert: testing.CACert, - Password: pwHash, - Model: testing.ModelTag, - } - cfg, err := agent.NewAgentConfig(configParams) - c.Assert(err, jc.ErrorIsNil) - cfg.SetStateServingInfo(params.StateServingInfo{ - APIPort: 5555, - StatePort: s.mgoInst.Port(), - Cert: "foo", - PrivateKey: "bar", - SharedSecret: "baz", - SystemIdentity: "qux", - }) - expectHW := instance.MustParseHardware("mem=2048M") - mcfg := agent.BootstrapMachineConfig{ - BootstrapConstraints: constraints.MustParse("mem=1024M"), - Jobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel}, - InstanceId: "i-bootstrap", - Characteristics: expectHW, - } - envAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{ - "agent-version": version.Current.String(), - "state-id": "1", // needed so policy can Open config - }) - envCfg, err := config.New(config.NoDefaults, envAttrs) - c.Assert(err, jc.ErrorIsNil) - - adminUser := names.NewLocalUserTag("agent-admin") - st, _, err := agent.InitializeState(adminUser, cfg, envCfg, mcfg, mongo.DefaultDialOpts(), environs.NewStatePolicy()) - c.Assert(err, jc.ErrorIsNil) - st.Close() - - st, _, err = agent.InitializeState(adminUser, cfg, envCfg, mcfg, mongo.DefaultDialOpts(), environs.NewStatePolicy()) - if err == nil { - st.Close() - } - c.Assert(err, gc.ErrorMatches, "failed to initialize mongo admin user: cannot set admin password: not authorized .*") -} - -func (s *bootstrapSuite) TestMachineJobFromParams(c *gc.C) { - var tests = []struct { - name multiwatcher.MachineJob - want state.MachineJob - err string - }{{ - name: multiwatcher.JobHostUnits, - want: state.JobHostUnits, - }, { - name: multiwatcher.JobManageModel, - want: state.JobManageModel, - }, { - name: multiwatcher.JobManageNetworking, - want: state.JobManageNetworking, - }, { - name: "invalid", - want: -1, - err: `invalid machine job "invalid"`, - }} - for _, test := range tests { - got, err := agent.MachineJobFromParams(test.name) - if err != nil { - c.Check(err, gc.ErrorMatches, test.err) - } - c.Check(got, gc.Equals, test.want) - } -} - -func (s *bootstrapSuite) assertCanLogInAsAdmin(c *gc.C, modelTag names.ModelTag, password string) { - info := &mongo.MongoInfo{ - Info: mongo.Info{ - Addrs: []string{s.mgoInst.Addr()}, - CACert: testing.CACert, - }, - Tag: nil, // admin user - Password: password, - } - st, err := state.Open(modelTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy()) - c.Assert(err, jc.ErrorIsNil) - defer st.Close() - _, err = st.Machine("0") - c.Assert(err, jc.ErrorIsNil) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/export_test.go charm-2.2.0/src/github.com/juju/juju/agent/export_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/export_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -10,14 +10,6 @@ "github.com/juju/juju/state/multiwatcher" ) -func Password(config Config) string { - c := config.(*configInternal) - if c.stateDetails == nil { - return c.apiDetails.password - } - return c.stateDetails.password -} - func PatchConfig(config Config, fieldName string, value interface{}) error { conf := config.(*configInternal) switch fieldName { @@ -55,10 +47,6 @@ return err == nil } -var ( - MachineJobFromParams = machineJobFromParams -) - func EmptyConfig() Config { return &configInternal{} } diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/format-1.18.go charm-2.2.0/src/github.com/juju/juju/agent/format-1.18.go --- charm-2.1.1/src/github.com/juju/juju/agent/format-1.18.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/format-1.18.go 2016-04-28 06:03:34.000000000 +0000 @@ -10,11 +10,11 @@ "github.com/juju/errors" "github.com/juju/names" + "github.com/juju/version" goyaml "gopkg.in/yaml.v2" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/state/multiwatcher" - "github.com/juju/juju/version" ) var format_1_18 = formatter_1_18{} diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/format-1.18_whitebox_test.go charm-2.2.0/src/github.com/juju/juju/agent/format-1.18_whitebox_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/format-1.18_whitebox_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/format-1.18_whitebox_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -13,11 +13,11 @@ jc "github.com/juju/testing/checkers" "github.com/juju/utils" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/testing" - "github.com/juju/juju/version" ) type format_1_18Suite struct { diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/format_whitebox_test.go charm-2.2.0/src/github.com/juju/juju/agent/format_whitebox_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/format_whitebox_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/format_whitebox_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -17,7 +17,7 @@ "github.com/juju/juju/cloudconfig/cloudinit" "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/testing" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type formatSuite struct { @@ -30,7 +30,7 @@ // located here for easy reuse. var agentParams = AgentConfigParams{ Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, Password: "sekrit", CACert: "ca cert", diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/identity_test.go charm-2.2.0/src/github.com/juju/juju/agent/identity_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/identity_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/identity_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -15,7 +15,7 @@ "github.com/juju/juju/apiserver/params" "github.com/juju/juju/testing" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type identitySuite struct { @@ -28,7 +28,7 @@ var attributeParams = AgentConfigParams{ Tag: names.NewMachineTag("1"), - UpgradedToVersion: version.Current, + UpgradedToVersion: jujuversion.Current, Password: "sekrit", CACert: "ca cert", StateAddresses: []string{"localhost:1234"}, diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/tools/diskmanager.go charm-2.2.0/src/github.com/juju/juju/agent/tools/diskmanager.go --- charm-2.1.1/src/github.com/juju/juju/agent/tools/diskmanager.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/tools/diskmanager.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,8 +6,9 @@ import ( "io" + "github.com/juju/version" + "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) // DiskManager keeps track of a collections of Juju agent tools in a directory diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/tools/diskmanager_test.go charm-2.2.0/src/github.com/juju/juju/agent/tools/diskmanager_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/tools/diskmanager_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/tools/diskmanager_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -10,12 +10,12 @@ "path/filepath" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" agenttools "github.com/juju/juju/agent/tools" coretesting "github.com/juju/juju/testing" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) var _ = gc.Suite(&DiskManagerSuite{}) diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/tools/toolsdir.go charm-2.2.0/src/github.com/juju/juju/agent/tools/toolsdir.go --- charm-2.1.1/src/github.com/juju/juju/agent/tools/toolsdir.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/tools/toolsdir.go 2016-04-28 06:03:34.000000000 +0000 @@ -17,9 +17,9 @@ "github.com/juju/errors" "github.com/juju/utils/symlink" + "github.com/juju/version" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) const ( @@ -187,6 +187,9 @@ dir := SharedGUIDir(dataDir) toolsData, err := ioutil.ReadFile(path.Join(dir, guiArchiveFile)) if err != nil { + if os.IsNotExist(err) { + return nil, errors.NotFoundf("GUI metadata") + } return nil, fmt.Errorf("cannot read GUI metadata in tools directory: %v", err) } var gui coretools.GUIArchive diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/tools/tools.go charm-2.2.0/src/github.com/juju/juju/agent/tools/tools.go --- charm-2.1.1/src/github.com/juju/juju/agent/tools/tools.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/tools/tools.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,9 +7,9 @@ "io" "github.com/juju/loggo" + "github.com/juju/version" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) var logger = loggo.GetLogger("juju.agent.tools") diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/tools/tools_test.go charm-2.2.0/src/github.com/juju/juju/agent/tools/tools_test.go --- charm-2.1.1/src/github.com/juju/juju/agent/tools/tools_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/tools/tools_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,13 +11,14 @@ "path/filepath" "sort" + "github.com/juju/errors" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" agenttools "github.com/juju/juju/agent/tools" "github.com/juju/juju/testing" coretest "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) type ToolsSuite struct { @@ -39,7 +40,7 @@ // resulting slice has that prefix removed to keep the output short. c.Assert(testing.FindJujuCoreImports(c, "github.com/juju/juju/agent/tools"), gc.DeepEquals, - []string{"tools", "version"}) + []string{"tools"}) } // gzyesses holds the result of running: @@ -167,7 +168,8 @@ func (t *ToolsSuite) TestReadGUIArchiveErrorNotFound(c *gc.C) { gui, err := agenttools.ReadGUIArchive(t.dataDir) - c.Assert(err, gc.ErrorMatches, "cannot read GUI metadata in tools directory: .*") + c.Assert(err, gc.ErrorMatches, "GUI metadata not found") + c.Assert(err, jc.Satisfies, errors.IsNotFound) c.Assert(gui, gc.IsNil) } diff -Nru charm-2.1.1/src/github.com/juju/juju/agent/uninstall.go charm-2.2.0/src/github.com/juju/juju/agent/uninstall.go --- charm-2.1.1/src/github.com/juju/juju/agent/uninstall.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/agent/uninstall.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,49 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package agent + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/juju/names" +) + +const ( + // UninstallFile is the name of the file inside the data dir that, + // if it exists, will cause the machine agent to uninstall itself + // when it receives the termination signal or ErrTerminateAgent. + UninstallFile = "uninstall-agent" +) + +// WARNING: this is linked with the use of UninstallFile in the +// provider/manual package. Don't change it without extreme care, +// and handling for mismatches with already-deployed agents. +func uninstallFile(a Agent) string { + return filepath.Join(a.CurrentConfig().DataDir(), UninstallFile) +} + +// SetCanUninstall creates the uninstall file in the data dir. It does +// nothing if the supplied agent doesn't have a machine tag. +func SetCanUninstall(a Agent) error { + tag := a.CurrentConfig().Tag() + if _, ok := tag.(names.MachineTag); !ok { + logger.Debugf("cannot uninstall non-machine agent %q", tag) + return nil + } + logger.Infof("marking agent ready for uninstall") + return ioutil.WriteFile(uninstallFile(a), nil, 0644) +} + +// CanUninstall returns true if the uninstall file exists in the agent's +// data dir. If it encounters an error, it fails safe and returns false. +func CanUninstall(a Agent) bool { + if _, err := os.Stat(uninstallFile(a)); err != nil { + logger.Debugf("agent not marked ready for uninstall") + return false + } + logger.Infof("agent already marked ready for uninstall") + return true +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/action/run.go charm-2.2.0/src/github.com/juju/juju/api/action/run.go --- charm-2.1.1/src/github.com/juju/juju/api/action/run.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/action/run.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,27 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action + +import ( + "time" + + "github.com/juju/juju/apiserver/params" +) + +// RunOnAllMachines runs the command on all the machines with the specified +// timeout. +func (c *Client) RunOnAllMachines(commands string, timeout time.Duration) ([]params.ActionResult, error) { + var results params.ActionResults + args := params.RunParams{Commands: commands, Timeout: timeout} + err := c.facade.FacadeCall("RunOnAllMachines", args, &results) + return results.Results, err +} + +// Run the Commands specified on the machines identified through the ids +// provided in the machines, services and units slices. +func (c *Client) Run(run params.RunParams) ([]params.ActionResult, error) { + var results params.ActionResults + err := c.facade.FacadeCall("Run", run, &results) + return results.Results, err +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/agent/facade.go charm-2.2.0/src/github.com/juju/juju/api/agent/facade.go --- charm-2.1.1/src/github.com/juju/juju/api/agent/facade.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/agent/facade.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,114 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package agent + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/api/base" + "github.com/juju/juju/apiserver/params" +) + +// Life is a local representation of entity life. Should probably be +// in a core/life or core/entity package; and quite probably, so +// should the ConnFacade interface. +type Life string + +const ( + Alive Life = "alive" + Dying Life = "dying" + Dead Life = "dead" +) + +// ConnFacade exposes the parts of the Agent facade needed by the bits +// that currently live in apicaller. This criterion is every bit as +// terrible as it sounds -- surely there should be a new facade at the +// apiserver level somewhere? -- but: +// 1) this feels like a convenient/transitional method grouping, not a +// fundamental *role*; and +// 2) at least it's a narrowed interface, and eschews the object-style +// sins of *State/*Entity. +// Progress not perfection. +type ConnFacade interface { + + // Life returns Alive, Dying, Dead, ErrDenied, or some other error. + Life(names.Tag) (Life, error) + + // SetPassword returns nil, ErrDenied, or some other error. + SetPassword(names.Tag, string) error +} + +// ErrDenied is returned by Life and SetPassword to indicate that the +// requested operation is impossible (and hence that the entity is +// either dead or gone, and in either case that no further meaningful +// interaction is possible). +var ErrDenied = errors.New("entity operation impossible") + +// NewConnFacade returns a ConnFacade backed by the supplied APICaller. +func NewConnFacade(caller base.APICaller) (ConnFacade, error) { + facadeCaller := base.NewFacadeCaller(caller, "Agent") + return &connFacade{ + caller: facadeCaller, + }, nil +} + +// connFacade implements ConnFacade. +type connFacade struct { + caller base.FacadeCaller +} + +// Life is part of the ConnFacade interface. +func (facade *connFacade) Life(entity names.Tag) (Life, error) { + var results params.AgentGetEntitiesResults + args := params.Entities{ + Entities: []params.Entity{{Tag: entity.String()}}, + } + err := facade.caller.FacadeCall("GetEntities", args, &results) + if err != nil { + return "", errors.Trace(err) + } + if len(results.Entities) != 1 { + return "", errors.Errorf("expected 1 result, got %d", len(results.Entities)) + } + if err := results.Entities[0].Error; err != nil { + if params.IsCodeNotFoundOrCodeUnauthorized(err) { + return "", ErrDenied + } + return "", errors.Trace(err) + } + life := Life(results.Entities[0].Life) + switch life { + case Alive, Dying, Dead: + return life, nil + } + return "", errors.Errorf("unknown life value %q", life) +} + +// SetPassword is part of the ConnFacade interface. +func (facade *connFacade) SetPassword(entity names.Tag, password string) error { + var results params.ErrorResults + args := params.EntityPasswords{ + Changes: []params.EntityPassword{{ + Tag: entity.String(), + Password: password, + }}, + } + err := facade.caller.FacadeCall("SetPasswords", args, &results) + if err != nil { + return errors.Trace(err) + } + if len(results.Results) != 1 { + return errors.Errorf("expected 1 result, got %d", len(results.Results)) + } + if err := results.Results[0].Error; err != nil { + if params.IsCodeDead(err) { + return ErrDenied + } else if params.IsCodeNotFoundOrCodeUnauthorized(err) { + return ErrDenied + } + return errors.Trace(err) + } + return nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/agent/facade_test.go charm-2.2.0/src/github.com/juju/juju/api/agent/facade_test.go --- charm-2.1.1/src/github.com/juju/juju/api/agent/facade_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/agent/facade_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,247 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package agent_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/api/agent" + "github.com/juju/juju/api/base" + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/apiserver/params" +) + +type FacadeSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&FacadeSuite{}) + +func (s *FacadeSuite) TestLifeCallError(c *gc.C) { + apiCaller := apiCaller(c, func(request string, arg, _ interface{}) error { + c.Check(request, gc.Equals, "GetEntities") + c.Check(arg, jc.DeepEquals, params.Entities{ + Entities: []params.Entity{{ + Tag: "service-omg", + }}, + }) + return errors.New("splat") + }) + facade, err := agent.NewConnFacade(apiCaller) + c.Assert(err, jc.ErrorIsNil) + + life, err := facade.Life(names.NewServiceTag("omg")) + c.Check(err, gc.ErrorMatches, "splat") + c.Check(life, gc.Equals, agent.Life("")) +} + +func (s *FacadeSuite) TestLifeNoResult(c *gc.C) { + result := params.AgentGetEntitiesResults{} + facade, err := agent.NewConnFacade(lifeChecker(c, result)) + c.Assert(err, jc.ErrorIsNil) + + life, err := facade.Life(names.NewServiceTag("omg")) + c.Check(err, gc.ErrorMatches, "expected 1 result, got 0") + c.Check(life, gc.Equals, agent.Life("")) +} + +func (s *FacadeSuite) TestLifeOversizedResult(c *gc.C) { + result := params.AgentGetEntitiesResults{ + Entities: []params.AgentGetEntitiesResult{{}, {}}, + } + facade, err := agent.NewConnFacade(lifeChecker(c, result)) + c.Assert(err, jc.ErrorIsNil) + + life, err := facade.Life(names.NewServiceTag("omg")) + c.Check(err, gc.ErrorMatches, "expected 1 result, got 2") + c.Check(life, gc.Equals, agent.Life("")) +} + +func (s *FacadeSuite) TestLifeRandomError(c *gc.C) { + result := params.AgentGetEntitiesResult{ + Error: ¶ms.Error{Message: "squish"}, + } + life, err := testLifeAPIResult(c, result) + c.Check(err, gc.ErrorMatches, "squish") + c.Check(life, gc.Equals, agent.Life("")) +} + +func (s *FacadeSuite) TestLifeErrNotFound(c *gc.C) { + result := params.AgentGetEntitiesResult{ + Error: ¶ms.Error{Code: params.CodeNotFound}, + } + life, err := testLifeAPIResult(c, result) + c.Check(err, gc.Equals, agent.ErrDenied) + c.Check(life, gc.Equals, agent.Life("")) +} + +func (s *FacadeSuite) TestLifeErrUnauthorized(c *gc.C) { + result := params.AgentGetEntitiesResult{ + Error: ¶ms.Error{Code: params.CodeUnauthorized}, + } + life, err := testLifeAPIResult(c, result) + c.Check(err, gc.Equals, agent.ErrDenied) + c.Check(life, gc.Equals, agent.Life("")) +} + +func (s *FacadeSuite) TestLifeUnknown(c *gc.C) { + result := params.AgentGetEntitiesResult{ + Life: "revenant", + } + life, err := testLifeAPIResult(c, result) + c.Check(err, gc.ErrorMatches, `unknown life value "revenant"`) + c.Check(life, gc.Equals, agent.Life("")) +} + +func (s *FacadeSuite) TestLifeAlive(c *gc.C) { + result := params.AgentGetEntitiesResult{ + Life: "alive", + } + life, err := testLifeAPIResult(c, result) + c.Check(err, jc.ErrorIsNil) + c.Check(life, gc.Equals, agent.Alive) +} + +func (s *FacadeSuite) TestLifeDying(c *gc.C) { + result := params.AgentGetEntitiesResult{ + Life: "dying", + } + life, err := testLifeAPIResult(c, result) + c.Check(err, jc.ErrorIsNil) + c.Check(life, gc.Equals, agent.Dying) +} + +func (s *FacadeSuite) TestLifeDead(c *gc.C) { + result := params.AgentGetEntitiesResult{ + Life: "dead", + } + life, err := testLifeAPIResult(c, result) + c.Check(err, jc.ErrorIsNil) + c.Check(life, gc.Equals, agent.Dead) +} + +func (s *FacadeSuite) TestSetPasswordCallError(c *gc.C) { + apiCaller := apiCaller(c, func(request string, arg, _ interface{}) error { + c.Check(request, gc.Equals, "SetPasswords") + c.Check(arg, jc.DeepEquals, params.EntityPasswords{ + Changes: []params.EntityPassword{{ + Tag: "service-omg", + Password: "seekr1t", + }}, + }) + return errors.New("splat") + }) + facade, err := agent.NewConnFacade(apiCaller) + c.Assert(err, jc.ErrorIsNil) + + err = facade.SetPassword(names.NewServiceTag("omg"), "seekr1t") + c.Check(err, gc.ErrorMatches, "splat") +} + +func (s *FacadeSuite) TestSetPasswordNoResult(c *gc.C) { + result := params.ErrorResults{} + facade, err := agent.NewConnFacade(passwordChecker(c, result)) + c.Assert(err, jc.ErrorIsNil) + + err = facade.SetPassword(names.NewServiceTag("omg"), "blah") + c.Check(err, gc.ErrorMatches, "expected 1 result, got 0") +} + +func (s *FacadeSuite) TestSetPasswordOversizedResult(c *gc.C) { + result := params.ErrorResults{ + Results: []params.ErrorResult{{}, {}}, + } + facade, err := agent.NewConnFacade(passwordChecker(c, result)) + c.Assert(err, jc.ErrorIsNil) + + err = facade.SetPassword(names.NewServiceTag("omg"), "blah") + c.Check(err, gc.ErrorMatches, "expected 1 result, got 2") +} + +func (s *FacadeSuite) TestSetPasswordRandomError(c *gc.C) { + result := params.ErrorResult{ + Error: ¶ms.Error{Message: "squish"}, + } + err := testPasswordAPIResult(c, result) + c.Check(err, gc.ErrorMatches, "squish") +} + +func (s *FacadeSuite) TestSetPasswordErrDead(c *gc.C) { + result := params.ErrorResult{ + Error: ¶ms.Error{Code: params.CodeDead}, + } + err := testPasswordAPIResult(c, result) + c.Check(err, gc.Equals, agent.ErrDenied) +} + +func (s *FacadeSuite) TestSetPasswordErrNotFound(c *gc.C) { + result := params.ErrorResult{ + Error: ¶ms.Error{Code: params.CodeNotFound}, + } + err := testPasswordAPIResult(c, result) + c.Check(err, gc.Equals, agent.ErrDenied) +} + +func (s *FacadeSuite) TestSetPasswordErrUnauthorized(c *gc.C) { + result := params.ErrorResult{ + Error: ¶ms.Error{Code: params.CodeUnauthorized}, + } + err := testPasswordAPIResult(c, result) + c.Check(err, gc.Equals, agent.ErrDenied) +} + +func (s *FacadeSuite) TestSetPasswordSuccess(c *gc.C) { + result := params.ErrorResult{} + err := testPasswordAPIResult(c, result) + c.Check(err, jc.ErrorIsNil) +} + +func testLifeAPIResult(c *gc.C, result params.AgentGetEntitiesResult) (agent.Life, error) { + facade, err := agent.NewConnFacade(lifeChecker(c, params.AgentGetEntitiesResults{ + Entities: []params.AgentGetEntitiesResult{result}, + })) + c.Assert(err, jc.ErrorIsNil) + + return facade.Life(names.NewServiceTag("omg")) +} + +func lifeChecker(c *gc.C, result params.AgentGetEntitiesResults) base.APICaller { + return apiCaller(c, func(_ string, _, out interface{}) error { + typed, ok := out.(*params.AgentGetEntitiesResults) + c.Assert(ok, jc.IsTrue) + *typed = result + return nil + }) +} + +func testPasswordAPIResult(c *gc.C, result params.ErrorResult) error { + facade, err := agent.NewConnFacade(passwordChecker(c, params.ErrorResults{ + Results: []params.ErrorResult{result}, + })) + c.Assert(err, jc.ErrorIsNil) + + return facade.SetPassword(names.NewServiceTag("omg"), "blah") +} + +func passwordChecker(c *gc.C, result params.ErrorResults) base.APICaller { + return apiCaller(c, func(_ string, _, out interface{}) error { + typed, ok := out.(*params.ErrorResults) + c.Assert(ok, jc.IsTrue) + *typed = result + return nil + }) +} + +func apiCaller(c *gc.C, check func(request string, arg, result interface{}) error) base.APICaller { + return apitesting.APICallerFunc(func(facade string, version int, id, request string, arg, result interface{}) error { + c.Check(facade, gc.Equals, "Agent") + c.Check(version, gc.Equals, 0) // because of BestFacadeVersion test infrastructure + c.Check(id, gc.Equals, "") + return check(request, arg, result) + }) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/agent/machine_test.go charm-2.2.0/src/github.com/juju/juju/api/agent/machine_test.go --- charm-2.1.1/src/github.com/juju/juju/api/agent/machine_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/agent/machine_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -14,6 +14,7 @@ "gopkg.in/mgo.v2" "github.com/juju/juju/api" + apiagent "github.com/juju/juju/api/agent" apiserveragent "github.com/juju/juju/apiserver/agent" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/environs" @@ -53,7 +54,7 @@ StatePort: ssi.StatePort, } s.State.SetStateServingInfo(ssi) - info, err := st.Agent().StateServingInfo() + info, err := apiagent.NewState(st).StateServingInfo() c.Assert(err, jc.ErrorIsNil) c.Assert(info, jc.DeepEquals, expected) } @@ -61,7 +62,7 @@ func (s *servingInfoSuite) TestStateServingInfoPermission(c *gc.C) { st, _ := s.OpenAPIAsNewMachine(c) - _, err := st.Agent().StateServingInfo() + _, err := apiagent.NewState(st).StateServingInfo() c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: "permission denied", Code: "unauthorized access", @@ -78,7 +79,7 @@ st, _ := s.OpenAPIAsNewMachine(c, state.JobManageModel) expected := true - result, err := st.Agent().IsMaster() + result, err := apiagent.NewState(st).IsMaster() c.Assert(err, jc.ErrorIsNil) c.Assert(result, gc.Equals, expected) @@ -87,7 +88,7 @@ func (s *servingInfoSuite) TestIsMasterPermission(c *gc.C) { st, _ := s.OpenAPIAsNewMachine(c) - _, err := st.Agent().IsMaster() + _, err := apiagent.NewState(st).IsMaster() c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: "permission denied", Code: "unauthorized access", @@ -109,12 +110,12 @@ func (s *machineSuite) TestMachineEntity(c *gc.C) { tag := names.NewMachineTag("42") - m, err := s.st.Agent().Entity(tag) + m, err := apiagent.NewState(s.st).Entity(tag) c.Assert(err, gc.ErrorMatches, "permission denied") c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) c.Assert(m, gc.IsNil) - m, err = s.st.Agent().Entity(s.machine.Tag()) + m, err = apiagent.NewState(s.st).Entity(s.machine.Tag()) c.Assert(err, jc.ErrorIsNil) c.Assert(m.Tag(), gc.Equals, s.machine.Tag().String()) c.Assert(m.Life(), gc.Equals, params.Alive) @@ -125,14 +126,14 @@ err = s.machine.Remove() c.Assert(err, jc.ErrorIsNil) - m, err = s.st.Agent().Entity(s.machine.Tag()) + m, err = apiagent.NewState(s.st).Entity(s.machine.Tag()) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("machine %s not found", s.machine.Id())) c.Assert(err, jc.Satisfies, params.IsCodeNotFound) c.Assert(m, gc.IsNil) } func (s *machineSuite) TestEntitySetPassword(c *gc.C) { - entity, err := s.st.Agent().Entity(s.machine.Tag()) + entity, err := apiagent.NewState(s.st).Entity(s.machine.Tag()) c.Assert(err, jc.ErrorIsNil) err = entity.SetPassword("foo") @@ -167,7 +168,7 @@ c.Assert(err, jc.ErrorIsNil) c.Assert(rFlag, jc.IsTrue) - entity, err := s.st.Agent().Entity(s.machine.Tag()) + entity, err := apiagent.NewState(s.st).Entity(s.machine.Tag()) c.Assert(err, jc.ErrorIsNil) err = entity.ClearReboot() diff -Nru charm-2.1.1/src/github.com/juju/juju/api/agent/unit_test.go charm-2.2.0/src/github.com/juju/juju/api/agent/unit_test.go --- charm-2.1.1/src/github.com/juju/juju/api/agent/unit_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/agent/unit_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -12,6 +12,7 @@ gc "gopkg.in/check.v1" "github.com/juju/juju/api" + apiagent "github.com/juju/juju/api/agent" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" @@ -41,12 +42,12 @@ func (s *unitSuite) TestUnitEntity(c *gc.C) { tag := names.NewUnitTag("wordpress/1") - m, err := s.st.Agent().Entity(tag) + m, err := apiagent.NewState(s.st).Entity(tag) c.Assert(err, gc.ErrorMatches, "permission denied") c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) c.Assert(m, gc.IsNil) - m, err = s.st.Agent().Entity(s.unit.Tag()) + m, err = apiagent.NewState(s.st).Entity(s.unit.Tag()) c.Assert(err, jc.ErrorIsNil) c.Assert(m.Tag(), gc.Equals, s.unit.Tag().String()) c.Assert(m.Life(), gc.Equals, params.Alive) @@ -57,7 +58,7 @@ err = s.unit.Remove() c.Assert(err, jc.ErrorIsNil) - m, err = s.st.Agent().Entity(s.unit.Tag()) + m, err = apiagent.NewState(s.st).Entity(s.unit.Tag()) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("unit %q not found", s.unit.Name())) c.Assert(err, jc.Satisfies, params.IsCodeNotFound) c.Assert(m, gc.IsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/apiclient.go charm-2.2.0/src/github.com/juju/juju/api/apiclient.go --- charm-2.1.1/src/github.com/juju/juju/api/apiclient.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/apiclient.go 2016-04-28 06:03:34.000000000 +0000 @@ -21,15 +21,16 @@ "github.com/juju/names" "github.com/juju/utils" "github.com/juju/utils/parallel" + "github.com/juju/version" "golang.org/x/net/websocket" "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" "github.com/juju/juju/api/base" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/network" "github.com/juju/juju/rpc" "github.com/juju/juju/rpc/jsoncodec" - "github.com/juju/juju/version" ) var logger = loggo.GetLogger("juju.api") @@ -94,11 +95,12 @@ // access it safely. loggedIn int32 - // tag and password and nonce hold the cached login credentials. - // These are only valid if loggedIn is 1. - tag string - password string - nonce string + // tag, password, macaroons and nonce hold the cached login + // credentials. These are only valid if loggedIn is 1. + tag string + password string + macaroons []macaroon.Slice + nonce string // serverRootAddress holds the cached API server address and port used // to login. @@ -132,11 +134,14 @@ // This unexported open method is used both directly above in the Open // function, and also the OpenWithVersion function below to explicitly cause // the API server to think that the client is older than it really is. -func open(info *Info, opts DialOpts, loginFunc func(st *state, tag names.Tag, pwd, nonce string) error) (Connection, error) { - if info.UseMacaroons { - if info.Tag != nil || info.Password != "" { - return nil, errors.New("open should specifiy UseMacaroons or a username & password. Not both") - } +func open( + info *Info, + opts DialOpts, + loginFunc func(st *state, tag names.Tag, pwd, nonce string, ms []macaroon.Slice) error, +) (Connection, error) { + + if err := info.Validate(); err != nil { + return nil, errors.Annotate(err, "validating info for opening an API connection") } conn, tlsConfig, err := connectWebsocket(info, opts) if err != nil { @@ -179,12 +184,13 @@ // state structure BEFORE login ?!? tag: tagToString(info.Tag), password: info.Password, + macaroons: info.Macaroons, nonce: info.Nonce, tlsConfig: tlsConfig, bakeryClient: bakeryClient, } - if info.Tag != nil || info.Password != "" || info.UseMacaroons { - if err := loginFunc(st, info.Tag, info.Password, info.Nonce); err != nil { + if !info.SkipLogin { + if err := loginFunc(st, info.Tag, info.Password, info.Nonce, info.Macaroons); err != nil { conn.Close() return nil, err } @@ -219,7 +225,7 @@ // on. This allows the caller to pretend to be an older client, and is used // only in testing. func OpenWithVersion(info *Info, opts DialOpts, loginVersion int) (Connection, error) { - var loginFunc func(st *state, tag names.Tag, pwd, nonce string) error + var loginFunc func(st *state, tag names.Tag, pwd, nonce string, ms []macaroon.Slice) error switch loginVersion { case 2: loginFunc = (*state).loginV2 @@ -241,11 +247,19 @@ if len(info.Addrs) == 0 { return nil, nil, errors.New("no API addresses to connect to") } - tlsConfig, err := tlsConfigForCACert(info.CACert) - if err != nil { - return nil, nil, errors.Annotatef(err, "cannot make TLS configuration") + tlsConfig := &tls.Config{ + // We want to be specific here (rather than just using "anything". + // See commit 7fc118f015d8480dfad7831788e4b8c0432205e8 (PR 899). + ServerName: "juju-apiserver", + InsecureSkipVerify: opts.InsecureSkipVerify, + } + if !tlsConfig.InsecureSkipVerify { + certPool, err := CreateCertPool(info.CACert) + if err != nil { + return nil, nil, errors.Annotate(err, "cert pool creation failed") + } + tlsConfig.RootCAs = certPool } - tlsConfig.InsecureSkipVerify = opts.InsecureSkipVerify path := "/" if info.ModelTag.Id() != "" { path = apiPath(info.ModelTag, "/api") @@ -258,19 +272,6 @@ return conn, tlsConfig, nil } -func tlsConfigForCACert(caCert string) (*tls.Config, error) { - certPool, err := CreateCertPool(caCert) - if err != nil { - return nil, errors.Annotate(err, "cert pool creation failed") - } - return &tls.Config{ - RootCAs: certPool, - // We want to be specific here (rather than just using "anything". - // See commit 7fc118f015d8480dfad7831788e4b8c0432205e8 (PR 899). - ServerName: "juju-apiserver", - }, nil -} - // dialWebSocket dials a websocket with one of the provided addresses, the // specified URL path, TLS configuration, and dial options. Each of the // specified addresses will be attempted concurrently, and the first diff -Nru charm-2.1.1/src/github.com/juju/juju/api/apiclient_test.go charm-2.2.0/src/github.com/juju/juju/api/apiclient_test.go --- charm-2.1.1/src/github.com/juju/juju/api/apiclient_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/apiclient_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -19,7 +19,7 @@ "github.com/juju/juju/apiserver/params" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type apiclientSuite struct { @@ -28,14 +28,6 @@ var _ = gc.Suite(&apiclientSuite{}) -func (s *apiclientSuite) TestOpenFailsIfUsernameAndUseMacaroon(c *gc.C) { - info := s.APIInfo(c) - info.Tag = names.NewUserTag("foobar") - info.UseMacaroons = true - _, err := api.Open(info, api.DialOpts{}) - c.Assert(err, gc.ErrorMatches, "open should specifiy UseMacaroons or a username & password. Not both") -} - func (s *apiclientSuite) TestConnectWebsocketToEnv(c *gc.C) { info := s.APIInfo(c) conn, _, err := api.ConnectWebsocket(info, api.DialOpts{}) @@ -114,7 +106,7 @@ remoteVersion, versionSet := st.ServerVersion() c.Assert(versionSet, jc.IsTrue) - c.Assert(remoteVersion, gc.Equals, version.Current) + c.Assert(remoteVersion, gc.Equals, jujuversion.Current) } func (s *apiclientSuite) TestOpenHonorsModelTag(c *gc.C) { diff -Nru charm-2.1.1/src/github.com/juju/juju/api/backups/download_test.go charm-2.2.0/src/github.com/juju/juju/api/backups/download_test.go --- charm-2.1.1/src/github.com/juju/juju/api/backups/download_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/backups/download_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -12,6 +12,7 @@ "github.com/juju/juju/apiserver/params" "github.com/juju/juju/state/backups" + "github.com/juju/juju/testing" ) type downloadSuite struct { @@ -28,6 +29,8 @@ r := strings.NewReader("") meta, err := backups.NewMetadataState(s.State, "0") c.Assert(err, jc.ErrorIsNil) + c.Assert(meta.CACert, gc.Equals, testing.CACert) + c.Assert(meta.CAPrivateKey, gc.Equals, testing.CAKey) // The Add method requires the length to be set // otherwise the content is assumed to have length 0. meta.Raw.Size = int64(r.Len()) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/backups/restore.go charm-2.2.0/src/github.com/juju/juju/api/backups/restore.go --- charm-2.1.1/src/github.com/juju/juju/api/backups/restore.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/backups/restore.go 2016-04-28 06:03:34.000000000 +0000 @@ -38,14 +38,14 @@ // ClientConnection type represents a function capable of spawning a new Client connection // it is used to pass around connection factories when necessary. // TODO(perrito666) This is a workaround for lp:1399722 . -type ClientConnection func() (*Client, func() error, error) +type ClientConnection func() (*Client, error) // closerfunc is a function that allows you to close a client connection. type closerFunc func() error -func prepareAttempt(client *Client, closer closerFunc) (error, error) { +func prepareAttempt(client *Client) (error, error) { var remoteError error - defer closer() + defer client.Close() err := client.facade.FacadeCall("PrepareRestore", nil, &remoteError) return err, remoteError } @@ -59,11 +59,11 @@ // by restore. for a := restoreStrategy.Start(); a.Next(); { logger.Debugf("Will attempt to call 'PrepareRestore'") - client, clientCloser, clientErr := newClient() + client, clientErr := newClient() if clientErr != nil { return errors.Trace(clientErr) } - err, remoteError = prepareAttempt(client, clientCloser) + err, remoteError = prepareAttempt(client) if err == nil && remoteError == nil { return nil } @@ -100,9 +100,9 @@ return c.restore(backupId, newClient) } -func restoreAttempt(client *Client, closer closerFunc, restoreArgs params.RestoreArgs) (error, error) { +func restoreAttempt(client *Client, restoreArgs params.RestoreArgs) (error, error) { var remoteError error - defer closer() + defer client.Close() err := client.facade.FacadeCall("Restore", restoreArgs, &remoteError) return err, remoteError } @@ -125,13 +125,12 @@ for a := restoreStrategy.Start(); a.Next(); { logger.Debugf("Attempting Restore of %q", backupId) var restoreClient *Client - var restoreClientCloser func() error - restoreClient, restoreClientCloser, err = newClient() + restoreClient, err = newClient() if err != nil { return errors.Trace(err) } - err, remoteError = restoreAttempt(restoreClient, restoreClientCloser, restoreArgs) + err, remoteError = restoreAttempt(restoreClient, restoreArgs) // A ShutdownErr signals that Restore almost certainly finished and // triggered Exit. @@ -160,9 +159,9 @@ return nil } -func finishAttempt(client *Client, closer closerFunc) (error, error) { +func finishAttempt(client *Client) (error, error) { var remoteError error - defer closer() + defer client.Close() err := client.facade.FacadeCall("FinishRestore", nil, &remoteError) return err, remoteError } @@ -176,13 +175,12 @@ for a := restoreStrategy.Start(); a.Next(); { logger.Debugf("Attempting finishRestore") var finishClient *Client - var finishClientCloser func() error - finishClient, finishClientCloser, err = newClient() + finishClient, err = newClient() if err != nil { return errors.Trace(err) } - err, remoteError = finishAttempt(finishClient, finishClientCloser) + err, remoteError = finishAttempt(finishClient) if err == nil && remoteError == nil { return nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/base/testing/apicaller.go charm-2.2.0/src/github.com/juju/juju/api/base/testing/apicaller.go --- charm-2.1.1/src/github.com/juju/juju/api/base/testing/apicaller.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/base/testing/apicaller.go 2016-04-28 06:03:34.000000000 +0000 @@ -27,6 +27,8 @@ } func (APICallerFunc) BestFacadeVersion(facade string) int { + // TODO(fwereade): this should return something arbitrary (e.g. 37) + // so that it can't be confused with mere uninitialized data. return 0 } @@ -129,6 +131,24 @@ return err }, ) +} + +// CheckingAPICallerMultiArgs checks each call against the indexed expected argument. Once expected +// arguments run out it doesn't check them. This is useful if your test continues to make calls after +// you have checked the ones you care about. +func CheckingAPICallerMultiArgs(c *gc.C, args []CheckArgs, numCalls *int, err error) base.APICallCloser { + if numCalls == nil { + panic("numCalls must be non-nill") + } + return APICallerFunc( + func(facade string, version int, id, method string, inArgs, outResults interface{}) error { + if len(args) > *numCalls { + checkArgs(c, &args[*numCalls], facade, version, id, method, inArgs, outResults) + } + *numCalls++ + return err + }, + ) } // StubFacadeCaller is a testing stub implementation of api/base.FacadeCaller. diff -Nru charm-2.1.1/src/github.com/juju/juju/api/certpool_test.go charm-2.2.0/src/github.com/juju/juju/api/certpool_test.go --- charm-2.1.1/src/github.com/juju/juju/api/certpool_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/certpool_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -127,7 +127,7 @@ func (s *certPoolSuite) addCert(c *gc.C, filename string) { expiry := time.Now().UTC().AddDate(10, 0, 0) - pem, _, err := cert.NewCA("random env name", expiry) + pem, _, err := cert.NewCA("random env name", "1", expiry) c.Assert(err, jc.ErrorIsNil) err = ioutil.WriteFile(filename, []byte(pem), 0644) c.Assert(err, jc.ErrorIsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/client.go charm-2.2.0/src/github.com/juju/juju/api/client.go --- charm-2.1.1/src/github.com/juju/juju/api/client.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,13 +11,14 @@ "net/url" "os" "strings" - "time" "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" + "github.com/juju/version" "golang.org/x/net/websocket" "gopkg.in/juju/charm.v6-unstable" + csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "gopkg.in/macaroon.v1" "github.com/juju/juju/api/base" @@ -25,7 +26,6 @@ "github.com/juju/juju/constraints" "github.com/juju/juju/network" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) // Client represents the client-accessible part of the state. @@ -185,33 +185,6 @@ return tag.Id() } -// ShareModel allows the given users access to the model. -func (c *Client) ShareModel(users ...names.UserTag) error { - var args params.ModifyModelUsers - for _, user := range users { - if &user != nil { - args.Changes = append(args.Changes, params.ModifyModelUser{ - UserTag: user.String(), - Action: params.AddModelUser, - }) - } - } - - var result params.ErrorResults - err := c.facade.FacadeCall("ShareModel", args, &result) - if err != nil { - return errors.Trace(err) - } - - for i, r := range result.Results { - if r.Error != nil && r.Error.Code == params.CodeAlreadyExists { - logger.Warningf("model is already shared with %s", users[i].Canonical()) - result.Results[i].Error = nil - } - } - return result.Combine() -} - // ModelUserInfo returns information on all users in the model. func (c *Client) ModelUserInfo() ([]params.ModelUserInfo, error) { var results params.ModelUserInfoResults @@ -230,33 +203,6 @@ return info, nil } -// UnshareModel removes access to the model for the given users. -func (c *Client) UnshareModel(users ...names.UserTag) error { - var args params.ModifyModelUsers - for _, user := range users { - if &user != nil { - args.Changes = append(args.Changes, params.ModifyModelUser{ - UserTag: user.String(), - Action: params.RemoveModelUser, - }) - } - } - - var result params.ErrorResults - err := c.facade.FacadeCall("ShareModel", args, &result) - if err != nil { - return errors.Trace(err) - } - - for i, r := range result.Results { - if r.Error != nil && r.Error.Code == params.CodeNotFound { - logger.Warningf("model was not previously shared with user %s", users[i].Canonical()) - result.Results[i].Error = nil - } - } - return result.Combine() -} - // WatchAll holds the id of the newly-created AllWatcher/AllModelWatcher. type WatchAll struct { AllWatcherId string @@ -324,23 +270,6 @@ return result, err } -// RunOnAllMachines runs the command on all the machines with the specified -// timeout. -func (c *Client) RunOnAllMachines(commands string, timeout time.Duration) ([]params.RunResult, error) { - var results params.RunResults - args := params.RunParams{Commands: commands, Timeout: timeout} - err := c.facade.FacadeCall("RunOnAllMachines", args, &results) - return results.Results, err -} - -// Run the Commands specified on the machines identified through the ids -// provided in the machines, services and units slices. -func (c *Client) Run(run params.RunParams) ([]params.RunResult, error) { - var results params.RunResults - err := c.facade.FacadeCall("Run", run, &results) - return results.Results, err -} - // DestroyModel puts the model into a "dying" state, // and removes all non-manager machine instances. DestroyModel // will fail if there are any manually-provisioned non-manager machines @@ -356,10 +285,11 @@ if curl.Schema != "local" { return nil, errors.Errorf("expected charm URL with local: schema, got %q", curl.String()) } - httpClient, err := c.st.HTTPClient() - if err != nil { + + if err := c.validateCharmVersion(ch); err != nil { return nil, errors.Trace(err) } + // Package the charm for uploading. var archive *os.File switch ch := ch.(type) { @@ -386,23 +316,57 @@ return nil, errors.Errorf("unknown charm type %T", ch) } - req, err := http.NewRequest("POST", "/charms?series="+curl.Series, nil) + curl, err := c.UploadCharm(curl, archive) if err != nil { - return nil, errors.Annotate(err, "cannot create upload request") + return nil, errors.Trace(err) } - req.Header.Set("Content-Type", "application/zip") + return curl, nil +} +// UploadCharm sends the content to the API server using an HTTP post. +func (c *Client) UploadCharm(curl *charm.URL, content io.ReadSeeker) (*charm.URL, error) { + endpoint := "/charms?series=" + curl.Series + contentType := "application/zip" var resp params.CharmsResponse - if err := httpClient.Do(req, archive, &resp); err != nil { + if err := c.httpPost(content, endpoint, contentType, &resp); err != nil { return nil, errors.Trace(err) } - curl, err = charm.ParseURL(resp.CharmURL) + + curl, err := charm.ParseURL(resp.CharmURL) if err != nil { return nil, errors.Annotatef(err, "bad charm URL in response") } return curl, nil } +type minJujuVersionErr struct { + *errors.Err +} + +func minVersionError(minver, jujuver version.Number) error { + err := errors.NewErr("charm's min version (%s) is higher than this juju environment's version (%s)", + minver, jujuver) + err.SetLocation(1) + return minJujuVersionErr{&err} +} + +func (c *Client) validateCharmVersion(ch charm.Charm) error { + minver := ch.Meta().MinJujuVersion + if minver != version.Zero { + agentver, err := c.AgentVersion() + if err != nil { + return errors.Trace(err) + } + + if minver.Compare(agentver) > 0 { + return minVersionError(minver, agentver) + } + } + return nil +} + +// TODO(ericsnow) Use charmstore.CharmID for AddCharm() & AddCharmWithAuth(). + // AddCharm adds the given charm URL (which must include revision) to // the model, if it does not exist yet. Local charms are not // supported, only charm store URLs. See also AddLocalCharm() in the @@ -411,11 +375,15 @@ // If the AddCharm API call fails because of an authorization error // when retrieving the charm from the charm store, an error // satisfying params.IsCodeUnauthorized will be returned. -func (c *Client) AddCharm(curl *charm.URL) error { - args := params.CharmURL{ - URL: curl.String(), +func (c *Client) AddCharm(curl *charm.URL, channel csparams.Channel) error { + args := params.AddCharm{ + URL: curl.String(), + Channel: string(channel), + } + if err := c.facade.FacadeCall("AddCharm", args, nil); err != nil { + return errors.Trace(err) } - return c.facade.FacadeCall("AddCharm", args, nil) + return nil } // AddCharmWithAuthorization is like AddCharm except it also provides @@ -427,12 +395,16 @@ // If the AddCharmWithAuthorization API call fails because of an // authorization error when retrieving the charm from the charm store, // an error satisfying params.IsCodeUnauthorized will be returned. -func (c *Client) AddCharmWithAuthorization(curl *charm.URL, csMac *macaroon.Macaroon) error { +func (c *Client) AddCharmWithAuthorization(curl *charm.URL, channel csparams.Channel, csMac *macaroon.Macaroon) error { args := params.AddCharmWithAuthorization{ URL: curl.String(), + Channel: string(channel), CharmStoreMacaroon: csMac, } - return c.facade.FacadeCall("AddCharmWithAuthorization", args, nil) + if err := c.facade.FacadeCall("AddCharmWithAuthorization", args, nil); err != nil { + return errors.Trace(err) + } + return nil } // ResolveCharm resolves the best available charm URLs with series, for charm @@ -456,33 +428,31 @@ // UploadTools uploads tools at the specified location to the API server over HTTPS. func (c *Client) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (*tools.Tools, error) { endpoint := fmt.Sprintf("/tools?binaryVersion=%s&series=%s", vers, strings.Join(additionalSeries, ",")) + contentType := "application/x-tar-gz" + var resp params.ToolsResult + if err := c.httpPost(r, endpoint, contentType, &resp); err != nil { + return nil, errors.Trace(err) + } + return resp.Tools, nil +} +func (c *Client) httpPost(content io.ReadSeeker, endpoint, contentType string, response interface{}) error { req, err := http.NewRequest("POST", endpoint, nil) if err != nil { - return nil, errors.Annotate(err, "cannot create upload request") + return errors.Annotate(err, "cannot create upload request") } - req.Header.Set("Content-Type", "application/x-tar-gz") + req.Header.Set("Content-Type", contentType) + // The returned httpClient sets the base url to /model/ if it can. httpClient, err := c.st.HTTPClient() if err != nil { - return nil, errors.Trace(err) + return errors.Trace(err) } - var resp params.ToolsResult - err = httpClient.Do(req, r, &resp) - if err != nil { - msg := err.Error() - if params.ErrCode(err) == "" && strings.Contains(msg, params.CodeOperationBlocked) { - // We're probably talking to an old version of the API server - // that doesn't provide error codes. - // See https://bugs.launchpad.net/juju-core/+bug/1499277 - err = ¶ms.Error{ - Code: params.CodeOperationBlocked, - Message: msg, - } - } - return nil, errors.Trace(err) + + if err := httpClient.Do(req, content, response); err != nil { + return errors.Trace(err) } - return resp.Tools, nil + return nil } // APIHostPorts returns a slice of network.HostPort for each API server. diff -Nru charm-2.1.1/src/github.com/juju/juju/api/client_macaroon_test.go charm-2.2.0/src/github.com/juju/juju/api/client_macaroon_test.go --- charm-2.1.1/src/github.com/juju/juju/api/client_macaroon_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/client_macaroon_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -56,6 +56,8 @@ } func (s *clientMacaroonSuite) TestAddLocalCharmSuccess(c *gc.C) { + c.Skip("dimitern: disabled as flaky - see http://pad.lv/1560511 as possible root cause") + charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") curl := charm.MustParseURL( fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), diff -Nru charm-2.1.1/src/github.com/juju/juju/api/client_test.go charm-2.2.0/src/github.com/juju/juju/api/client_test.go --- charm-2.1.1/src/github.com/juju/juju/api/client_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -20,6 +20,7 @@ "github.com/juju/loggo" "github.com/juju/names" jc "github.com/juju/testing/checkers" + "github.com/juju/version" "golang.org/x/net/websocket" gc "gopkg.in/check.v1" "gopkg.in/juju/charm.v6-unstable" @@ -34,7 +35,6 @@ "github.com/juju/juju/testcharms" coretesting "github.com/juju/juju/testing" "github.com/juju/juju/testing/factory" - "github.com/juju/juju/version" ) type clientSuite struct { @@ -171,17 +171,81 @@ c.Assert(err, gc.ErrorMatches, `POST http://.*/model/deadbeef-0bad-400d-8000-4b1d0d06f00d/charms\?series=quantal: the POST method is not allowed`) } +func (s *clientSuite) TestMinVersionLocalCharm(c *gc.C) { + tests := []minverTest{ + {"2.0.0", "1.0.0", true}, + {"1.0.0", "2.0.0", false}, + {"1.25.0", "1.24.0", true}, + {"1.24.0", "1.25.0", false}, + {"1.25.1", "1.25.0", true}, + {"1.25.0", "1.25.1", false}, + {"1.25.0", "1.25.0", true}, + {"1.25.0", "1.25-alpha1", true}, + {"1.25-alpha1", "1.25.0", false}, + } + client := s.APIState.Client() + for _, t := range tests { + testMinVer(client, t, c) + } +} + +type minverTest struct { + juju string + charm string + ok bool +} + +func testMinVer(client *api.Client, t minverTest, c *gc.C) { + charmMinVer := version.MustParse(t.charm) + jujuVer := version.MustParse(t.juju) + + cleanup := api.PatchClientFacadeCall(client, + func(request string, paramsIn interface{}, response interface{}) error { + c.Assert(paramsIn, gc.IsNil) + if response, ok := response.(*params.AgentVersionResult); ok { + response.Version = jujuVer + } else { + c.Log("wrong output structure") + c.Fail() + } + return nil + }, + ) + defer cleanup() + + charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") + curl := charm.MustParseURL( + fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), + ) + charmArchive.Meta().MinJujuVersion = charmMinVer + + _, err := client.AddLocalCharm(curl, charmArchive) + + if t.ok { + if err != nil { + c.Errorf("Unexpected non-nil error for jujuver %v, minver %v: %#v", t.juju, t.charm, err) + } + } else { + if err == nil { + c.Errorf("Unexpected nil error for jujuver %v, minver %v", t.juju, t.charm) + } else if !api.IsMinVersionError(err) { + c.Errorf("Wrong error for jujuver %v, minver %v: expected minVersionError, got: %#v", t.juju, t.charm, err) + } + } +} + func fakeAPIEndpoint(c *gc.C, client *api.Client, address, method string, handle func(http.ResponseWriter, *http.Request)) net.Listener { lis, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, jc.ErrorIsNil) - http.HandleFunc(address, func(w http.ResponseWriter, r *http.Request) { + mux := http.NewServeMux() + mux.HandleFunc(address, func(w http.ResponseWriter, r *http.Request) { if r.Method == method { handle(w, r) } }) go func() { - http.Serve(lis, nil) + http.Serve(lis, mux) }() api.SetServerAddress(client, "http", lis.Addr().String()) return lis @@ -232,38 +296,6 @@ }) } -func (s *clientSuite) TestShareEnvironmentExistingUser(c *gc.C) { - client := s.APIState.Client() - user := s.Factory.MakeModelUser(c, nil) - cleanup := api.PatchClientFacadeCall(client, - func(request string, paramsIn interface{}, response interface{}) error { - if users, ok := paramsIn.(params.ModifyModelUsers); ok { - c.Assert(users.Changes, gc.HasLen, 1) - c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.AddModelUser)) - c.Logf(users.Changes[0].UserTag, gc.Equals, user.UserTag().String()) - } else { - c.Fatalf("wrong input structure") - } - if result, ok := response.(*params.ErrorResults); ok { - err := ¶ms.Error{ - Message: "error message", - Code: params.CodeAlreadyExists, - } - *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}} - } else { - c.Fatalf("wrong input structure") - } - return nil - }, - ) - defer cleanup() - - err := client.ShareModel(user.UserTag()) - c.Assert(err, jc.ErrorIsNil) - logMsg := fmt.Sprintf("WARNING juju.api model is already shared with %s", user.UserName()) - c.Assert(c.GetTestLog(), jc.Contains, logMsg) -} - func (s *clientSuite) TestDestroyEnvironment(c *gc.C) { client := s.APIState.Client() var called bool @@ -280,108 +312,6 @@ c.Assert(called, jc.IsTrue) } -func (s *clientSuite) TestShareEnvironmentThreeUsers(c *gc.C) { - client := s.APIState.Client() - existingUser := s.Factory.MakeModelUser(c, nil) - localUser := s.Factory.MakeUser(c, nil) - newUserTag := names.NewUserTag("foo@bar") - cleanup := api.PatchClientFacadeCall(client, - func(request string, paramsIn interface{}, response interface{}) error { - if users, ok := paramsIn.(params.ModifyModelUsers); ok { - c.Assert(users.Changes, gc.HasLen, 3) - c.Assert(string(users.Changes[0].Action), gc.Equals, string(params.AddModelUser)) - c.Assert(users.Changes[0].UserTag, gc.Equals, existingUser.UserTag().String()) - c.Assert(string(users.Changes[1].Action), gc.Equals, string(params.AddModelUser)) - c.Assert(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String()) - c.Assert(string(users.Changes[2].Action), gc.Equals, string(params.AddModelUser)) - c.Assert(users.Changes[2].UserTag, gc.Equals, newUserTag.String()) - } else { - c.Log("wrong input structure") - c.Fail() - } - if result, ok := response.(*params.ErrorResults); ok { - err := ¶ms.Error{Message: "existing user"} - *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}} - } else { - c.Log("wrong output structure") - c.Fail() - } - return nil - }, - ) - defer cleanup() - - err := client.ShareModel(existingUser.UserTag(), localUser.UserTag(), newUserTag) - c.Assert(err, gc.ErrorMatches, `existing user`) -} - -func (s *clientSuite) TestUnshareEnvironmentThreeUsers(c *gc.C) { - client := s.APIState.Client() - missingUser := s.Factory.MakeModelUser(c, nil) - localUser := s.Factory.MakeUser(c, nil) - newUserTag := names.NewUserTag("foo@bar") - cleanup := api.PatchClientFacadeCall(client, - func(request string, paramsIn interface{}, response interface{}) error { - if users, ok := paramsIn.(params.ModifyModelUsers); ok { - c.Assert(users.Changes, gc.HasLen, 3) - c.Assert(string(users.Changes[0].Action), gc.Equals, string(params.RemoveModelUser)) - c.Assert(users.Changes[0].UserTag, gc.Equals, missingUser.UserTag().String()) - c.Assert(string(users.Changes[1].Action), gc.Equals, string(params.RemoveModelUser)) - c.Assert(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String()) - c.Assert(string(users.Changes[2].Action), gc.Equals, string(params.RemoveModelUser)) - c.Assert(users.Changes[2].UserTag, gc.Equals, newUserTag.String()) - } else { - c.Log("wrong input structure") - c.Fail() - } - if result, ok := response.(*params.ErrorResults); ok { - err := ¶ms.Error{Message: "error unsharing user"} - *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}} - } else { - c.Log("wrong output structure") - c.Fail() - } - return nil - }, - ) - defer cleanup() - - err := client.UnshareModel(missingUser.UserTag(), localUser.UserTag(), newUserTag) - c.Assert(err, gc.ErrorMatches, "error unsharing user") -} - -func (s *clientSuite) TestUnshareEnvironmentMissingUser(c *gc.C) { - client := s.APIState.Client() - user := names.NewUserTag("bob@local") - cleanup := api.PatchClientFacadeCall(client, - func(request string, paramsIn interface{}, response interface{}) error { - if users, ok := paramsIn.(params.ModifyModelUsers); ok { - c.Assert(users.Changes, gc.HasLen, 1) - c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.RemoveModelUser)) - c.Logf(users.Changes[0].UserTag, gc.Equals, user.String()) - } else { - c.Fatalf("wrong input structure") - } - if result, ok := response.(*params.ErrorResults); ok { - err := ¶ms.Error{ - Message: "error message", - Code: params.CodeNotFound, - } - *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}} - } else { - c.Fatalf("wrong input structure") - } - return nil - }, - ) - defer cleanup() - - err := client.UnshareModel(user) - c.Assert(err, jc.ErrorIsNil) - logMsg := fmt.Sprintf("WARNING juju.api model was not previously shared with user %s", user.Canonical()) - c.Assert(c.GetTestLog(), jc.Contains, logMsg) -} - func (s *clientSuite) TestWatchDebugLogConnected(c *gc.C) { client := s.APIState.Client() // Use the no tail option so we don't try to start a tailing cursor diff -Nru charm-2.1.1/src/github.com/juju/juju/api/controller/controller.go charm-2.2.0/src/github.com/juju/juju/api/controller/controller.go --- charm-2.1.1/src/github.com/juju/juju/api/controller/controller.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/controller/controller.go 2016-04-28 06:03:34.000000000 +0000 @@ -133,3 +133,74 @@ } return results, nil } + +// ModelMigrationSpec holds the details required to start the +// migration of a single model. +type ModelMigrationSpec struct { + ModelUUID string + TargetControllerUUID string + TargetAddrs []string + TargetCACert string + TargetUser string + TargetPassword string +} + +// Validate performs sanity checks on the migration configuration it +// holds. +func (s *ModelMigrationSpec) Validate() error { + if !names.IsValidModel(s.ModelUUID) { + return errors.NotValidf("model UUID") + } + if !names.IsValidModel(s.TargetControllerUUID) { + return errors.NotValidf("controller UUID") + } + if len(s.TargetAddrs) < 1 { + return errors.NotValidf("empty target API addresses") + } + if s.TargetCACert == "" { + return errors.NotValidf("empty target CA cert") + } + if !names.IsValidUser(s.TargetUser) { + return errors.NotValidf("target user") + } + if s.TargetPassword == "" { + return errors.NotValidf("empty target password") + } + return nil +} + +// InitiateModelMigration attempts to start a migration for the +// specified model, returning the migration's ID. +// +// The API server supports starting multiple migrations in one request +// but we don't need that at the client side yet (and may never) so +// this call just supports starting one migration at a time. +func (c *Client) InitiateModelMigration(spec ModelMigrationSpec) (string, error) { + if err := spec.Validate(); err != nil { + return "", errors.Trace(err) + } + args := params.InitiateModelMigrationArgs{ + Specs: []params.ModelMigrationSpec{{ + ModelTag: names.NewModelTag(spec.ModelUUID).String(), + TargetInfo: params.ModelMigrationTargetInfo{ + ControllerTag: names.NewModelTag(spec.TargetControllerUUID).String(), + Addrs: spec.TargetAddrs, + CACert: spec.TargetCACert, + AuthTag: names.NewUserTag(spec.TargetUser).String(), + Password: spec.TargetPassword, + }, + }}, + } + response := params.InitiateModelMigrationResults{} + if err := c.facade.FacadeCall("InitiateModelMigration", args, &response); err != nil { + return "", errors.Trace(err) + } + if len(response.Results) != 1 { + return "", errors.New("unexpected number of results returned") + } + result := response.Results[0] + if result.Error != nil { + return "", errors.Trace(result.Error) + } + return result.Id, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/controller/controller_test.go charm-2.2.0/src/github.com/juju/juju/api/controller/controller_test.go --- charm-2.1.1/src/github.com/juju/juju/api/controller/controller_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/controller/controller_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,8 +7,10 @@ "fmt" "time" + "github.com/juju/errors" "github.com/juju/names" jc "github.com/juju/testing/checkers" + "github.com/juju/utils" gc "gopkg.in/check.v1" "github.com/juju/juju/api/base" @@ -54,7 +56,7 @@ obtained = append(obtained, fmt.Sprintf("%s/%s", env.Owner, env.Name)) } expected := []string{ - "admin@local/dummymodel", + "admin@local/admin", "user@remote/first", "user@remote/second", } @@ -65,11 +67,13 @@ sysManager := s.OpenAPI(c) env, err := sysManager.ModelConfig() c.Assert(err, jc.ErrorIsNil) - c.Assert(env["name"], gc.Equals, "dummymodel") + c.Assert(env["name"], gc.Equals, "admin") } func (s *controllerSuite) TestDestroyController(c *gc.C) { - s.Factory.MakeModel(c, &factory.ModelParams{Name: "foo"}).Close() + st := s.Factory.MakeModel(c, &factory.ModelParams{Name: "foo"}) + factory.NewFactory(st).MakeMachine(c, nil) // make it non-empty + st.Close() sysManager := s.OpenAPI(c) err := sysManager.DestroyController(false) @@ -86,7 +90,7 @@ c.Assert(err, jc.ErrorIsNil) c.Assert(results, jc.DeepEquals, []params.ModelBlockInfo{ params.ModelBlockInfo{ - Name: "dummymodel", + Name: "admin", UUID: s.State.ModelUUID(), OwnerTag: s.AdminUserTag(c).String(), Blocks: []string{ @@ -160,3 +164,51 @@ Life: params.Alive, }}) } + +func (s *controllerSuite) TestInitiateModelMigration(c *gc.C) { + st := s.Factory.MakeModel(c, nil) + defer st.Close() + + _, err := st.GetModelMigration() + c.Assert(errors.IsNotFound(err), jc.IsTrue) + + spec := controller.ModelMigrationSpec{ + ModelUUID: st.ModelUUID(), + TargetControllerUUID: randomUUID(), + TargetAddrs: []string{"1.2.3.4:5"}, + TargetCACert: "cert", + TargetUser: "someone", + TargetPassword: "secret", + } + + controller := s.OpenAPI(c) + id, err := controller.InitiateModelMigration(spec) + c.Assert(err, jc.ErrorIsNil) + expectedId := st.ModelUUID() + ":0" + c.Check(id, gc.Equals, expectedId) + + // Check database. + mig, err := st.GetModelMigration() + c.Assert(err, jc.ErrorIsNil) + c.Check(mig.Id(), gc.Equals, expectedId) +} + +func (s *controllerSuite) TestInitiateModelMigrationError(c *gc.C) { + spec := controller.ModelMigrationSpec{ + ModelUUID: randomUUID(), // Model doesn't exist. + TargetControllerUUID: randomUUID(), + TargetAddrs: []string{"1.2.3.4:5"}, + TargetCACert: "cert", + TargetUser: "someone", + TargetPassword: "secret", + } + + controller := s.OpenAPI(c) + id, err := controller.InitiateModelMigration(spec) + c.Check(id, gc.Equals, "") + c.Check(err, gc.ErrorMatches, "unable to read model: .+") +} + +func randomUUID() string { + return utils.MustNewUUID().String() +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/deployer/deployer_test.go charm-2.2.0/src/github.com/juju/juju/api/deployer/deployer_test.go --- charm-2.1.1/src/github.com/juju/juju/api/deployer/deployer_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/deployer/deployer_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -71,7 +71,7 @@ c.Assert(err, jc.ErrorIsNil) // Create the deployer facade. - s.st = s.stateAPI.Deployer() + s.st = deployer.NewState(s.stateAPI) c.Assert(s.st, gc.NotNil) s.APIAddresserTests = apitesting.NewAPIAddresserTests(s.st, s.BackingState) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/export_test.go charm-2.2.0/src/github.com/juju/juju/api/export_test.go --- charm-2.1.1/src/github.com/juju/juju/api/export_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,6 +4,7 @@ package api import ( + "github.com/juju/errors" "github.com/juju/juju/api/base" "github.com/juju/juju/network" ) @@ -88,3 +89,10 @@ func (f *resultCaller) RawAPICaller() base.APICaller { return nil } + +// IsMinVersionError returns true if the given error was caused by the charm +// having a minjujuversion higher than the juju environment's version. +func IsMinVersionError(err error) bool { + _, ok := errors.Cause(err).(minJujuVersionErr) + return ok +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/facadeversions.go charm-2.2.0/src/github.com/juju/juju/api/facadeversions.go --- charm-2.1.1/src/github.com/juju/juju/api/facadeversions.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/facadeversions.go 2016-04-28 06:03:34.000000000 +0000 @@ -19,15 +19,15 @@ "Addresser": 2, "Agent": 2, "AgentTools": 1, - "AllWatcher": 1, "AllModelWatcher": 2, + "AllWatcher": 1, "Annotations": 2, "Backups": 1, "Block": 2, - "Charms": 2, "CharmRevisionUpdater": 1, - "Client": 1, + "Charms": 2, "Cleaner": 2, + "Client": 1, "Controller": 2, "Deployer": 1, "DiscoverSpaces": 2, @@ -42,13 +42,20 @@ "KeyManager": 1, "KeyUpdater": 1, "LeadershipService": 2, + "LifeFlag": 1, "Logger": 1, + "MachineActions": 1, "MachineManager": 2, "Machiner": 1, - "MetricsDebug": 1, - "MetricsManager": 1, "MeterStatus": 1, "MetricsAdder": 2, + "MetricsDebug": 1, + "MetricsManager": 1, + "MigrationFlag": 1, + "MigrationMaster": 1, + "MigrationMinion": 1, + "MigrationStatusWatcher": 1, + "MigrationTarget": 1, "ModelManager": 2, "NotifyWatcher": 1, "Pinger": 1, @@ -59,18 +66,20 @@ "Resumer": 2, "RetryStrategy": 1, "Service": 3, - "Storage": 2, + "ServiceScaler": 1, + "Singular": 1, "Spaces": 2, - "Subnets": 2, "StatusHistory": 2, + "Storage": 2, "StorageProvisioner": 2, "StringsWatcher": 1, - "Upgrader": 1, + "Subnets": 2, + "Undertaker": 1, "UnitAssigner": 1, "Uniter": 3, + "Upgrader": 1, "UserManager": 1, "VolumeAttachmentsWatcher": 2, - "Undertaker": 1, } // RegisterFacadeVersion sets the API client to prefer the given version diff -Nru charm-2.1.1/src/github.com/juju/juju/api/gui.go charm-2.2.0/src/github.com/juju/juju/api/gui.go --- charm-2.1.1/src/github.com/juju/juju/api/gui.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/gui.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,89 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package api + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/juju/errors" + "github.com/juju/version" + + "github.com/juju/juju/apiserver/params" +) + +const ( + guiArchivePath = "/gui-archive" + guiVersionPath = "/gui-version" +) + +// GUIArchives retrieves information about Juju GUI archives currently present +// in the Juju controller. +func (c *Client) GUIArchives() ([]params.GUIArchiveVersion, error) { + httpClient, err := c.st.RootHTTPClient() + if err != nil { + return nil, errors.Annotate(err, "cannot retrieve HTTP client") + } + var resp params.GUIArchiveResponse + if err = httpClient.Get(guiArchivePath, &resp); err != nil { + return nil, errors.Annotate(err, "cannot retrieve GUI archives info") + } + return resp.Versions, nil +} + +// UploadGUIArchive uploads a GUI archive to the controller over HTTPS, and +// reports about whether the upload updated the current GUI served by Juju. +func (c *Client) UploadGUIArchive(r io.ReadSeeker, hash string, size int64, vers version.Number) (current bool, err error) { + // Prepare the request. + v := url.Values{} + v.Set("version", vers.String()) + v.Set("hash", hash) + req, err := http.NewRequest("POST", guiArchivePath+"?"+v.Encode(), nil) + if err != nil { + return false, errors.Annotate(err, "cannot create upload request") + } + req.Header.Set("Content-Type", "application/x-tar-bzip2") + req.ContentLength = size + + // Retrieve a client and send the request. + httpClient, err := c.st.RootHTTPClient() + if err != nil { + return false, errors.Annotate(err, "cannot retrieve HTTP client") + } + var resp params.GUIArchiveVersion + if err = httpClient.Do(req, r, &resp); err != nil { + return false, errors.Annotate(err, "cannot upload the GUI archive") + } + return resp.Current, nil +} + +// SelectGUIVersion selects which version of the Juju GUI is served by the +// controller. +func (c *Client) SelectGUIVersion(vers version.Number) error { + // Prepare the request. + req, err := http.NewRequest("PUT", guiVersionPath, nil) + if err != nil { + return errors.Annotate(err, "cannot create PUT request") + } + req.Header.Set("Content-Type", params.ContentTypeJSON) + content, err := json.Marshal(params.GUIVersionRequest{ + Version: vers, + }) + if err != nil { + errors.Annotate(err, "cannot marshal request body") + } + + // Retrieve a client and send the request. + httpClient, err := c.st.RootHTTPClient() + if err != nil { + return errors.Annotate(err, "cannot retrieve HTTP client") + } + if err = httpClient.Do(req, bytes.NewReader(content), nil); err != nil { + return errors.Annotate(err, "cannot select GUI version") + } + return nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/gui_test.go charm-2.2.0/src/github.com/juju/juju/api/gui_test.go --- charm-2.1.1/src/github.com/juju/juju/api/gui_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/gui_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,166 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package api_test + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + jc "github.com/juju/testing/checkers" + "github.com/juju/version" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/params" +) + +// sendJSONResponse encodes the given content as JSON and writes it to the +// given response writer. +func sendJSONResponse(c *gc.C, w http.ResponseWriter, content interface{}) { + w.Header().Set("Content-Type", params.ContentTypeJSON) + encoder := json.NewEncoder(w) + err := encoder.Encode(content) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *clientSuite) TestGUIArchives(c *gc.C) { + client := s.APIState.Client() + called := false + response := params.GUIArchiveResponse{ + Versions: []params.GUIArchiveVersion{{ + Version: version.MustParse("1.0.0"), + SHA256: "hash1", + Current: false, + }, { + Version: version.MustParse("2.0.0"), + SHA256: "hash2", + Current: true, + }}, + } + + // Set up a fake endpoint for tests. + defer fakeAPIEndpoint(c, client, "/gui-archive", "GET", + func(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + called = true + sendJSONResponse(c, w, response) + }, + ).Close() + + versions, err := client.GUIArchives() + c.Assert(err, jc.ErrorIsNil) + c.Assert(versions, jc.DeepEquals, response.Versions) + c.Assert(called, jc.IsTrue) +} + +func (s *clientSuite) TestGUIArchivesError(c *gc.C) { + client := s.APIState.Client() + + // Set up a fake endpoint for tests. + defer fakeAPIEndpoint(c, client, "/gui-archive", "GET", + func(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + w.WriteHeader(http.StatusBadRequest) + }, + ).Close() + + versions, err := client.GUIArchives() + c.Assert(err, gc.ErrorMatches, "cannot retrieve GUI archives info: .*") + c.Assert(versions, gc.IsNil) +} + +func (s *clientSuite) TestUploadGUIArchive(c *gc.C) { + client := s.APIState.Client() + called := false + archive := []byte("archive content") + hash, size, vers := "archive-hash", int64(len(archive)), version.MustParse("2.1.0") + + // Set up a fake endpoint for tests. + defer fakeAPIEndpoint(c, client, "/gui-archive", "POST", + func(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + called = true + err := req.ParseForm() + c.Assert(err, jc.ErrorIsNil) + // Check version and content length. + c.Assert(req.Form.Get("version"), gc.Equals, vers.String()) + c.Assert(req.ContentLength, gc.Equals, size) + // Check request body. + obtainedArchive, err := ioutil.ReadAll(req.Body) + c.Assert(err, jc.ErrorIsNil) + c.Assert(obtainedArchive, gc.DeepEquals, archive) + // Check hash. + h := req.Form.Get("hash") + c.Assert(h, gc.Equals, hash) + // Send the response. + sendJSONResponse(c, w, params.GUIArchiveVersion{ + Current: true, + }) + }, + ).Close() + + current, err := client.UploadGUIArchive(bytes.NewReader(archive), hash, size, vers) + c.Assert(err, jc.ErrorIsNil) + c.Assert(current, jc.IsTrue) + c.Assert(called, jc.IsTrue) +} + +func (s *clientSuite) TestUploadGUIArchiveError(c *gc.C) { + client := s.APIState.Client() + archive := []byte("archive content") + hash, size, vers := "archive-hash", int64(len(archive)), version.MustParse("2.1.0") + + // Set up a fake endpoint for tests. + defer fakeAPIEndpoint(c, client, "/gui-archive", "POST", + func(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + w.WriteHeader(http.StatusBadRequest) + }, + ).Close() + + current, err := client.UploadGUIArchive(bytes.NewReader(archive), hash, size, vers) + c.Assert(err, gc.ErrorMatches, "cannot upload the GUI archive: .*") + c.Assert(current, jc.IsFalse) +} + +func (s *clientSuite) TestSelectGUIVersion(c *gc.C) { + client := s.APIState.Client() + called := false + vers := version.MustParse("2.0.42") + + // Set up a fake endpoint for tests. + defer fakeAPIEndpoint(c, client, "/gui-version", "PUT", + func(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + called = true + // Check request body. + var request params.GUIVersionRequest + decoder := json.NewDecoder(req.Body) + err := decoder.Decode(&request) + c.Assert(err, jc.ErrorIsNil) + c.Assert(request.Version, gc.Equals, vers) + }, + ).Close() + + err := client.SelectGUIVersion(vers) + c.Assert(err, jc.ErrorIsNil) + c.Assert(called, jc.IsTrue) +} + +func (s *clientSuite) TestSelectGUIVersionError(c *gc.C) { + client := s.APIState.Client() + vers := version.MustParse("2.0.42") + + // Set up a fake endpoint for tests. + defer fakeAPIEndpoint(c, client, "/gui-version", "PUT", + func(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + w.WriteHeader(http.StatusBadRequest) + }, + ).Close() + + err := client.SelectGUIVersion(vers) + c.Assert(err, gc.ErrorMatches, "cannot select GUI version: .*") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/http.go charm-2.2.0/src/github.com/juju/juju/api/http.go --- charm-2.1.1/src/github.com/juju/juju/api/http.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/http.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,26 +5,43 @@ import ( "bytes" + "encoding/base64" "encoding/json" "io" "net/http" + "net/url" "github.com/juju/errors" "github.com/juju/httprequest" "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" "github.com/juju/juju/apiserver/params" ) -// HTTPClient implements Connection.APICaller.HTTPClient. +// HTTPClient implements Connection.APICaller.HTTPClient and returns an HTTP +// client pointing to the API server "/model/:uuid/" path. func (s *state) HTTPClient() (*httprequest.Client, error) { - if !s.isLoggedIn() { - return nil, errors.New("no HTTP client available without logging in") - } baseURL, err := s.apiEndpoint("/", "") if err != nil { return nil, errors.Trace(err) } + return s.httpClient(baseURL) +} + +// HTTPClient implements Connection.APICaller.HTTPClient and returns an HTTP +// client pointing to the API server root path. +func (s *state) RootHTTPClient() (*httprequest.Client, error) { + return s.httpClient(&url.URL{ + Scheme: s.serverScheme, + Host: s.Addr(), + }) +} + +func (s *state) httpClient(baseURL *url.URL) (*httprequest.Client, error) { + if !s.isLoggedIn() { + return nil, errors.New("no HTTP client available without logging in") + } return &httprequest.Client{ BaseURL: baseURL.String(), Doer: httpRequestDoer{ @@ -55,8 +72,19 @@ // Add basic auth if appropriate // Call doer.bakeryClient.DoWithBodyAndCustomError if doer.st.tag != "" { + // Note that password may be empty here; we still + // want to pass the tag along. An empty password + // indicates that we're using macaroon authentication. req.SetBasicAuth(doer.st.tag, doer.st.password) } + // Add any explicitly-specified macaroons. + for _, ms := range doer.st.macaroons { + encoded, err := encodeMacaroonSlice(ms) + if err != nil { + return nil, errors.Trace(err) + } + req.Header.Add(httpbakery.MacaroonsHeader, encoded) + } return doer.st.bakeryClient.DoWithBodyAndCustomError(req, body, func(resp *http.Response) error { // At this point we are only interested in errors that // the bakery cares about, and the CodeDischargeRequired @@ -69,6 +97,15 @@ }) } +// encodeMacaroonSlice base64-JSON-encodes a slice of macaroons. +func encodeMacaroonSlice(ms macaroon.Slice) (string, error) { + data, err := json.Marshal(ms) + if err != nil { + return "", errors.Trace(err) + } + return base64.StdEncoding.EncodeToString(data), nil +} + // unmarshalHTTPErrorResponse unmarshals an error response from // an HTTP endpoint. For historical reasons, these endpoints // return several different incompatible error response formats. diff -Nru charm-2.1.1/src/github.com/juju/juju/api/interface.go charm-2.2.0/src/github.com/juju/juju/api/interface.go --- charm-2.1.1/src/github.com/juju/juju/api/interface.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/interface.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,20 +6,20 @@ import ( "time" + "github.com/juju/errors" "github.com/juju/names" + "github.com/juju/version" "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" "github.com/juju/juju/api/addresser" - "github.com/juju/juju/api/agent" "github.com/juju/juju/api/base" "github.com/juju/juju/api/charmrevisionupdater" "github.com/juju/juju/api/cleaner" - "github.com/juju/juju/api/deployer" "github.com/juju/juju/api/discoverspaces" "github.com/juju/juju/api/firewaller" "github.com/juju/juju/api/imagemetadata" "github.com/juju/juju/api/instancepoller" - "github.com/juju/juju/api/machiner" "github.com/juju/juju/api/provisioner" "github.com/juju/juju/api/reboot" "github.com/juju/juju/api/unitassigner" @@ -27,7 +27,6 @@ "github.com/juju/juju/api/upgrader" "github.com/juju/juju/network" "github.com/juju/juju/rpc" - "github.com/juju/juju/version" ) // Info encapsulates information about a server holding juju state and @@ -50,9 +49,9 @@ // ...but this block of fields is all about the authentication mechanism // to use after connecting -- if any -- and should probably be extracted. - // UseMacaroons, when true, enables macaroon-based login and ignores - // the provided username and password. - UseMacaroons bool `yaml:"use-macaroons,omitempty"` + // SkipLogin, if true, skips the Login call on connection. It is an + // error to set Tag, Password, or Macaroons if SkipLogin is true. + SkipLogin bool `yaml:"-"` // Tag holds the name of the entity that is connecting. // If this is nil, and the password is empty, no login attempt will be made. @@ -63,11 +62,37 @@ // Password holds the password for the administrator or connecting entity. Password string + // Macaroons holds a slice of macaroon.Slice that may be used to + // authenticate with the API server. + Macaroons []macaroon.Slice `yaml:",omitempty"` + // Nonce holds the nonce used when provisioning the machine. Used // only by the machine agent. Nonce string `yaml:",omitempty"` } +// Validate validates the API info. +func (info *Info) Validate() error { + if len(info.Addrs) == 0 { + return errors.NotValidf("missing addresses") + } + if info.CACert == "" { + return errors.NotValidf("missing CA certificate") + } + if info.SkipLogin { + if info.Tag != nil { + return errors.NotValidf("specifying Tag and SkipLogin") + } + if info.Password != "" { + return errors.NotValidf("specifying Password and SkipLogin") + } + if len(info.Macaroons) > 0 { + return errors.NotValidf("specifying Macaroons and SkipLogin") + } + } + return nil +} + // DialOpts holds configuration parameters that control the // Dialing behavior when connecting to a controller. type DialOpts struct { @@ -125,7 +150,7 @@ // These are a bit off -- ServerVersion is apparently not known until after // Login()? Maybe evidence of need for a separate AuthenticatedConnection..? - Login(name names.Tag, password, nonce string) error + Login(name names.Tag, password, nonce string, ms []macaroon.Slice) error ServerVersion() (version.Number, bool) // APICaller provides the facility to make API calls directly. @@ -161,14 +186,11 @@ // will be easy to remove, but until we're using them via manifolds it's // prohibitively ugly to do so. Client() *Client - Machiner() *machiner.State Provisioner() *provisioner.State Uniter() (*uniter.State, error) Firewaller() *firewaller.State - Agent() *agent.State Upgrader() *upgrader.State Reboot() (reboot.State, error) - Deployer() *deployer.State Addresser() *addresser.API DiscoverSpaces() *discoverspaces.API InstancePoller() *instancepoller.API diff -Nru charm-2.1.1/src/github.com/juju/juju/api/lifeflag/facade.go charm-2.2.0/src/github.com/juju/juju/api/lifeflag/facade.go --- charm-2.1.1/src/github.com/juju/juju/api/lifeflag/facade.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/lifeflag/facade.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,98 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/api/base" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/life" + "github.com/juju/juju/watcher" +) + +// NewWatcherFunc exists to let us test Watch properly. +type NewWatcherFunc func(base.APICaller, params.NotifyWatchResult) watcher.NotifyWatcher + +// Facade makes calls to the LifeFlag facade. +type Facade struct { + caller base.FacadeCaller + newWatcher NewWatcherFunc +} + +// NewFacade returns a new Facade using the supplied caller. +func NewFacade(caller base.APICaller, newWatcher NewWatcherFunc) *Facade { + return &Facade{ + caller: base.NewFacadeCaller(caller, "LifeFlag"), + newWatcher: newWatcher, + } +} + +// ErrNotFound indicates that the requested entity no longer exists. +// +// We avoid errors.NotFound, because errors.NotFound is non-specific, and +// it's our job to communicate *this specific condition*. There are many +// possible sources of errors.NotFound in the world, and it's not safe or +// sane for a client to treat a generic NotFound as specific to the entity +// in question. +// +// We're still vulnerable to apiservers returning unjustified CodeNotFound +// but at least we're safe from accidental errors.NotFound injection in +// the api client mechanism. +var ErrNotFound = errors.New("entity not found") + +// Watch returns a NotifyWatcher that sends a value whenever the +// entity's life value may have changed; or ErrNotFound; or some +// other error. +func (facade *Facade) Watch(entity names.Tag) (watcher.NotifyWatcher, error) { + args := params.Entities{ + Entities: []params.Entity{{Tag: entity.String()}}, + } + var results params.NotifyWatchResults + err := facade.caller.FacadeCall("Watch", args, &results) + if err != nil { + return nil, errors.Trace(err) + } + if count := len(results.Results); count != 1 { + return nil, errors.Errorf("expected 1 Watch result, got %d", count) + } + result := results.Results[0] + if err := result.Error; err != nil { + if params.IsCodeNotFound(err) { + return nil, ErrNotFound + } + return nil, errors.Trace(result.Error) + } + w := facade.newWatcher(facade.caller.RawAPICaller(), result) + return w, nil +} + +// Life returns the entity's life value; or ErrNotFound; or some +// other error. +func (facade *Facade) Life(entity names.Tag) (life.Value, error) { + args := params.Entities{ + Entities: []params.Entity{{Tag: entity.String()}}, + } + var results params.LifeResults + err := facade.caller.FacadeCall("Life", args, &results) + if err != nil { + return "", errors.Trace(err) + } + if count := len(results.Results); count != 1 { + return "", errors.Errorf("expected 1 Life result, got %d", count) + } + result := results.Results[0] + if err := result.Error; err != nil { + if params.IsCodeNotFound(err) { + return "", ErrNotFound + } + return "", errors.Trace(result.Error) + } + life := life.Value(result.Life) + if err := life.Validate(); err != nil { + return "", errors.Trace(err) + } + return life, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/lifeflag/facade_test.go charm-2.2.0/src/github.com/juju/juju/api/lifeflag/facade_test.go --- charm-2.1.1/src/github.com/juju/juju/api/lifeflag/facade_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/lifeflag/facade_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,236 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/api/base" + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/lifeflag" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/life" + "github.com/juju/juju/watcher" +) + +type FacadeSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&FacadeSuite{}) + +func (*FacadeSuite) TestLifeCall(c *gc.C) { + var called bool + caller := apiCaller(c, func(request string, args, _ interface{}) error { + called = true + c.Check(request, gc.Equals, "Life") + c.Check(args, jc.DeepEquals, params.Entities{ + Entities: []params.Entity{{Tag: "service-blah"}}, + }) + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + facade.Life(names.NewServiceTag("blah")) + c.Check(called, jc.IsTrue) +} + +func (*FacadeSuite) TestLifeCallError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, _ interface{}) error { + return errors.New("crunch belch") + }) + facade := lifeflag.NewFacade(caller, nil) + + result, err := facade.Life(names.NewServiceTag("blah")) + c.Check(err, gc.ErrorMatches, "crunch belch") + c.Check(result, gc.Equals, life.Value("")) +} + +func (*FacadeSuite) TestLifeNoResultsError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, _ interface{}) error { + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + result, err := facade.Life(names.NewServiceTag("blah")) + c.Check(err, gc.ErrorMatches, "expected 1 Life result, got 0") + c.Check(result, gc.Equals, life.Value("")) +} + +func (*FacadeSuite) TestLifeExtraResultsError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, results interface{}) error { + typed, ok := results.(*params.LifeResults) + c.Assert(ok, jc.IsTrue) + *typed = params.LifeResults{ + Results: make([]params.LifeResult, 2), + } + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + result, err := facade.Life(names.NewServiceTag("blah")) + c.Check(err, gc.ErrorMatches, "expected 1 Life result, got 2") + c.Check(result, gc.Equals, life.Value("")) +} + +func (*FacadeSuite) TestLifeNotFoundError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, results interface{}) error { + typed, ok := results.(*params.LifeResults) + c.Assert(ok, jc.IsTrue) + *typed = params.LifeResults{ + Results: []params.LifeResult{{ + Error: ¶ms.Error{Code: params.CodeNotFound}, + }}, + } + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + result, err := facade.Life(names.NewServiceTag("blah")) + c.Check(err, gc.Equals, lifeflag.ErrNotFound) + c.Check(result, gc.Equals, life.Value("")) +} + +func (*FacadeSuite) TestLifeInvalidResultError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, results interface{}) error { + typed, ok := results.(*params.LifeResults) + c.Assert(ok, jc.IsTrue) + *typed = params.LifeResults{ + Results: []params.LifeResult{{Life: "decomposed"}}, + } + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + result, err := facade.Life(names.NewServiceTag("blah")) + c.Check(err, gc.ErrorMatches, `life value "decomposed" not valid`) + c.Check(result, gc.Equals, life.Value("")) +} + +func (*FacadeSuite) TestLifeSuccess(c *gc.C) { + caller := apiCaller(c, func(_ string, _, results interface{}) error { + typed, ok := results.(*params.LifeResults) + c.Assert(ok, jc.IsTrue) + *typed = params.LifeResults{ + Results: []params.LifeResult{{Life: "dying"}}, + } + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + result, err := facade.Life(names.NewServiceTag("blah")) + c.Check(err, jc.ErrorIsNil) + c.Check(result, gc.Equals, life.Dying) +} + +func (*FacadeSuite) TestWatchCall(c *gc.C) { + var called bool + caller := apiCaller(c, func(request string, args, _ interface{}) error { + called = true + c.Check(request, gc.Equals, "Watch") + c.Check(args, jc.DeepEquals, params.Entities{ + Entities: []params.Entity{{Tag: "service-blah"}}, + }) + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + facade.Watch(names.NewServiceTag("blah")) + c.Check(called, jc.IsTrue) +} + +func (*FacadeSuite) TestWatchCallError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, _ interface{}) error { + return errors.New("crunch belch") + }) + facade := lifeflag.NewFacade(caller, nil) + + watcher, err := facade.Watch(names.NewServiceTag("blah")) + c.Check(err, gc.ErrorMatches, "crunch belch") + c.Check(watcher, gc.IsNil) +} + +func (*FacadeSuite) TestWatchNoResultsError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, _ interface{}) error { + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + watcher, err := facade.Watch(names.NewServiceTag("blah")) + c.Check(err, gc.ErrorMatches, "expected 1 Watch result, got 0") + c.Check(watcher, gc.IsNil) +} + +func (*FacadeSuite) TestWatchExtraResultsError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, results interface{}) error { + typed, ok := results.(*params.NotifyWatchResults) + c.Assert(ok, jc.IsTrue) + *typed = params.NotifyWatchResults{ + Results: make([]params.NotifyWatchResult, 2), + } + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + watcher, err := facade.Watch(names.NewServiceTag("blah")) + c.Check(err, gc.ErrorMatches, "expected 1 Watch result, got 2") + c.Check(watcher, gc.IsNil) +} + +func (*FacadeSuite) TestWatchNotFoundError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, results interface{}) error { + typed, ok := results.(*params.NotifyWatchResults) + c.Assert(ok, jc.IsTrue) + *typed = params.NotifyWatchResults{ + Results: []params.NotifyWatchResult{{ + Error: ¶ms.Error{Code: params.CodeNotFound}, + }}, + } + return nil + }) + facade := lifeflag.NewFacade(caller, nil) + + watcher, err := facade.Watch(names.NewServiceTag("blah")) + c.Check(err, gc.Equals, lifeflag.ErrNotFound) + c.Check(watcher, gc.IsNil) +} + +func (*FacadeSuite) TestWatchSuccess(c *gc.C) { + caller := apiCaller(c, func(_ string, _, results interface{}) error { + typed, ok := results.(*params.NotifyWatchResults) + c.Assert(ok, jc.IsTrue) + *typed = params.NotifyWatchResults{ + Results: []params.NotifyWatchResult{{ + NotifyWatcherId: "123", + }}, + } + return nil + }) + expectWatcher := &struct{ watcher.NotifyWatcher }{} + newWatcher := func(apiCaller base.APICaller, result params.NotifyWatchResult) watcher.NotifyWatcher { + c.Check(apiCaller, gc.NotNil) // uncomparable + c.Check(result, jc.DeepEquals, params.NotifyWatchResult{ + NotifyWatcherId: "123", + }) + return expectWatcher + } + facade := lifeflag.NewFacade(caller, newWatcher) + + watcher, err := facade.Watch(names.NewServiceTag("blah")) + c.Check(err, jc.ErrorIsNil) + c.Check(watcher, gc.Equals, expectWatcher) +} + +func apiCaller(c *gc.C, check func(request string, arg, result interface{}) error) base.APICaller { + return apitesting.APICallerFunc(func(facade string, version int, id, request string, arg, result interface{}) error { + c.Check(facade, gc.Equals, "LifeFlag") + c.Check(version, gc.Equals, 0) + c.Check(id, gc.Equals, "") + return check(request, arg, result) + }) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/lifeflag/package_test.go charm-2.2.0/src/github.com/juju/juju/api/lifeflag/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/lifeflag/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/lifeflag/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/machineactions/action.go charm-2.2.0/src/github.com/juju/juju/api/machineactions/action.go --- charm-2.1.1/src/github.com/juju/juju/api/machineactions/action.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/machineactions/action.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,29 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package machineactions + +// Action represents a single instance of an Action call, by name and params. +// TODO(bogdantelega): This is currently copied from uniter.Actions, +// but until the implementations converge, it's saner to duplicate the code since +// the "correct" abstraction over both is not obvious. +type Action struct { + name string + params map[string]interface{} +} + +// NewAction makes a new Action with specified name and params map. +func NewAction(name string, params map[string]interface{}) *Action { + return &Action{name: name, params: params} +} + +// Name retrieves the name of the Action. +func (a *Action) Name() string { + return a.name +} + +// Params retrieves the params map of the Action. +func (a *Action) Params() map[string]interface{} { + return a.params +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/machineactions/machineactions.go charm-2.2.0/src/github.com/juju/juju/api/machineactions/machineactions.go --- charm-2.1.1/src/github.com/juju/juju/api/machineactions/machineactions.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/machineactions/machineactions.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,149 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +// machineactions implements the the api side of +// running actions on machines +package machineactions + +import ( + "github.com/juju/errors" + "github.com/juju/juju/api/base" + apiwatcher "github.com/juju/juju/api/watcher" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/watcher" + "github.com/juju/names" +) + +type Client struct { + facade base.FacadeCaller +} + +func NewClient(caller base.APICaller) *Client { + return &Client{base.NewFacadeCaller(caller, "MachineActions")} +} + +// WatchActionNotifications returns a StringsWatcher for observing the +// IDs of Actions added to the Machine. The initial event will contain the +// IDs of any Actions pending at the time the Watcher is made. +func (c *Client) WatchActionNotifications(agent names.MachineTag) (watcher.StringsWatcher, error) { + var results params.StringsWatchResults + args := params.Entities{ + Entities: []params.Entity{{Tag: agent.String()}}, + } + + err := c.facade.FacadeCall("WatchActionNotifications", args, &results) + if err != nil { + return nil, errors.Trace(err) + } + + if len(results.Results) != 1 { + return nil, errors.Errorf("expected 1 result, got %d", len(results.Results)) + } + + result := results.Results[0] + if result.Error != nil { + return nil, errors.Trace(result.Error) + } + w := apiwatcher.NewStringsWatcher(c.facade.RawAPICaller(), result) + return w, nil +} + +func (c *Client) getOneAction(tag names.ActionTag) (params.ActionResult, error) { + nothing := params.ActionResult{} + + args := params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + } + + var results params.ActionResults + err := c.facade.FacadeCall("Actions", args, &results) + if err != nil { + return nothing, errors.Trace(err) + } + + if len(results.Results) > 1 { + return nothing, errors.Errorf("expected only 1 action query result, got %d", len(results.Results)) + } + + result := results.Results[0] + if result.Error != nil { + return nothing, errors.Trace(result.Error) + } + + return result, nil +} + +// Action returns the Action with the given tag. +func (c *Client) Action(tag names.ActionTag) (*Action, error) { + result, err := c.getOneAction(tag) + if err != nil { + return nil, errors.Trace(err) + } + return &Action{ + name: result.Action.Name, + params: result.Action.Parameters, + }, nil +} + +// ActionBegin marks an action as running. +func (c *Client) ActionBegin(tag names.ActionTag) error { + var results params.ErrorResults + + args := params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + } + + err := c.facade.FacadeCall("BeginActions", args, &results) + if err != nil { + return errors.Trace(err) + } + + return results.OneError() +} + +// ActionFinish captures the structured output of an action. +func (c *Client) ActionFinish(tag names.ActionTag, status string, actionResults map[string]interface{}, message string) error { + var results params.ErrorResults + + args := params.ActionExecutionResults{ + Results: []params.ActionExecutionResult{{ + ActionTag: tag.String(), + Status: status, + Results: actionResults, + Message: message, + }}, + } + + err := c.facade.FacadeCall("FinishActions", args, &results) + if err != nil { + return errors.Trace(err) + } + + return results.OneError() +} + +// RunningActions returns a list of actions running for the given machine tag. +func (c *Client) RunningActions(agent names.MachineTag) ([]params.ActionResult, error) { + var results params.ActionsByReceivers + + args := params.Entities{ + Entities: []params.Entity{{Tag: agent.String()}}, + } + + err := c.facade.FacadeCall("RunningActions", args, &results) + if err != nil { + return nil, errors.Trace(err) + } + + if len(results.Actions) != 1 { + return nil, errors.Errorf("expected 1 result, got %d", len(results.Actions)) + } + + result := results.Actions[0] + if result.Error != nil { + return nil, result.Error + } + + return result.Actions, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/machineactions/machineactions_test.go charm-2.2.0/src/github.com/juju/juju/api/machineactions/machineactions_test.go --- charm-2.1.1/src/github.com/juju/juju/api/machineactions/machineactions_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/machineactions/machineactions_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,561 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package machineactions_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + jujutesting "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils" + gc "gopkg.in/check.v1" + + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/machineactions" + "github.com/juju/juju/apiserver/params" +) + +type ClientSuite struct { + jujutesting.IsolationSuite +} + +var _ = gc.Suite(&ClientSuite{}) + +func (s *ClientSuite) TestWatchFails(c *gc.C) { + tag := names.NewMachineTag("2") + expectErr := errors.Errorf("kuso") + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.WatchActionNotifications", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.StringsWatchResults{}) + res := result.(*params.StringsWatchResults) + res.Results = make([]params.StringsWatchResult, 1) + return expectErr + }) + + client := machineactions.NewClient(apiCaller) + w, err := client.WatchActionNotifications(tag) + c.Assert(errors.Cause(err), gc.Equals, expectErr) + c.Assert(w, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestWatchResultError(c *gc.C) { + tag := names.NewMachineTag("2") + expectErr := ¶ms.Error{ + Message: "rigged", + Code: params.CodeNotAssigned, + } + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.WatchActionNotifications", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.StringsWatchResults{}) + res := result.(*params.StringsWatchResults) + res.Results = make([]params.StringsWatchResult, 1) + res.Results[0].Error = expectErr + return nil + }) + + client := machineactions.NewClient(apiCaller) + w, err := client.WatchActionNotifications(tag) + c.Assert(errors.Cause(err), gc.Equals, expectErr) + c.Assert(w, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestWatchResultTooMany(c *gc.C) { + tag := names.NewMachineTag("2") + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.WatchActionNotifications", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.StringsWatchResults{}) + res := result.(*params.StringsWatchResults) + res.Results = make([]params.StringsWatchResult, 2) + return nil + }) + + client := machineactions.NewClient(apiCaller) + w, err := client.WatchActionNotifications(tag) + c.Assert(err, gc.ErrorMatches, "expected 1 result, got 2") + c.Assert(w, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionBeginSuccess(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.BeginActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + *(result.(*params.ErrorResults)) = params.ErrorResults{ + Results: []params.ErrorResult{{}}, + } + + return nil + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionBegin(tag) + c.Assert(err, jc.ErrorIsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionBeginError(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.BeginActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + expectedErr := errors.Errorf("blam") + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + return expectedErr + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionBegin(tag) + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionBeginResultError(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.BeginActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + expectedErr := ¶ms.Error{ + Message: "rigged", + Code: params.CodeNotAssigned, + } + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + *(result.(*params.ErrorResults)) = params.ErrorResults{ + Results: []params.ErrorResult{{expectedErr}}, + } + + return nil + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionBegin(tag) + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionBeginTooManyResults(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.BeginActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + res := result.(*params.ErrorResults) + res.Results = make([]params.ErrorResult, 2) + return nil + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionBegin(tag) + c.Assert(err, gc.ErrorMatches, "expected 1 result, got 2") + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionFinishSuccess(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + status := "stubstatus" + actionResults := map[string]interface{}{"stub": "stub"} + message := "stubmsg" + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.FinishActions", + []interface{}{"", params.ActionExecutionResults{ + Results: []params.ActionExecutionResult{{ + ActionTag: tag.String(), + Status: status, + Results: actionResults, + Message: message, + }}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + *(result.(*params.ErrorResults)) = params.ErrorResults{ + Results: []params.ErrorResult{{}}, + } + return nil + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionFinish(tag, status, actionResults, message) + c.Assert(err, jc.ErrorIsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionFinishError(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.FinishActions", + []interface{}{"", params.ActionExecutionResults{ + Results: []params.ActionExecutionResult{{ + ActionTag: tag.String(), + Status: "", + Results: nil, + Message: "", + }}, + }}, + }} + expectedErr := errors.Errorf("blam") + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + return expectedErr + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionFinish(tag, "", nil, "") + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionFinishResultError(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.FinishActions", + []interface{}{"", params.ActionExecutionResults{ + Results: []params.ActionExecutionResult{{ + ActionTag: tag.String(), + Status: "", + Results: nil, + Message: "", + }}, + }}, + }} + expectedErr := ¶ms.Error{ + Message: "rigged", + Code: params.CodeNotAssigned, + } + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + *(result.(*params.ErrorResults)) = params.ErrorResults{ + Results: []params.ErrorResult{{expectedErr}}, + } + + return nil + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionFinish(tag, "", nil, "") + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestActionFinishTooManyResults(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.FinishActions", + []interface{}{"", params.ActionExecutionResults{ + Results: []params.ActionExecutionResult{{ + ActionTag: tag.String(), + Status: "", + Results: nil, + Message: "", + }}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) + res := result.(*params.ErrorResults) + res.Results = make([]params.ErrorResult, 2) + return nil + }) + + client := machineactions.NewClient(apiCaller) + err := client.ActionFinish(tag, "", nil, "") + c.Assert(err, gc.ErrorMatches, "expected 1 result, got 2") + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestGetActionSuccess(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.Actions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + expectedName := "ack" + expectedParams := map[string]interface{}{"floob": "zgloob"} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionResults{}) + *(result.(*params.ActionResults)) = params.ActionResults{ + Results: []params.ActionResult{{ + Action: ¶ms.Action{ + Name: expectedName, + Parameters: expectedParams, + }, + }}, + } + return nil + }) + + client := machineactions.NewClient(apiCaller) + action, err := client.Action(tag) + c.Assert(err, jc.ErrorIsNil) + c.Assert(action.Name(), gc.Equals, expectedName) + c.Assert(action.Params(), gc.DeepEquals, expectedParams) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestGetActionError(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.Actions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + expectedErr := errors.Errorf("blam") + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionResults{}) + return expectedErr + }) + + client := machineactions.NewClient(apiCaller) + action, err := client.Action(tag) + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + c.Assert(action, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestGetActionResultError(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.Actions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + expectedErr := ¶ms.Error{ + Message: "rigged", + Code: params.CodeNotAssigned, + } + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionResults{}) + *(result.(*params.ActionResults)) = params.ActionResults{ + Results: []params.ActionResult{{ + Error: expectedErr, + }}, + } + return nil + }) + + client := machineactions.NewClient(apiCaller) + action, err := client.Action(tag) + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + c.Assert(action, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestGetActionTooManyResults(c *gc.C) { + tag := names.NewActionTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.Actions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionResults{}) + res := result.(*params.ActionResults) + res.Results = make([]params.ActionResult, 2) + return nil + }) + + client := machineactions.NewClient(apiCaller) + action, err := client.Action(tag) + c.Assert(err, gc.ErrorMatches, "expected only 1 action query result, got 2") + c.Assert(action, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestRunningActionSuccess(c *gc.C) { + tag := names.NewMachineTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.RunningActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + actionsList := []params.ActionResult{ + {Action: ¶ms.Action{Name: "foo"}}, + {Action: ¶ms.Action{Name: "baz"}}, + } + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionsByReceivers{}) + *(result.(*params.ActionsByReceivers)) = params.ActionsByReceivers{ + Actions: []params.ActionsByReceiver{{ + Actions: actionsList, + }}, + } + return nil + }) + + client := machineactions.NewClient(apiCaller) + actions, err := client.RunningActions(tag) + c.Assert(err, jc.ErrorIsNil) + c.Assert(actions, jc.DeepEquals, actionsList) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestRunningActionsError(c *gc.C) { + tag := names.NewMachineTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.RunningActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + expectedErr := errors.Errorf("blam") + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionsByReceivers{}) + return expectedErr + }) + + client := machineactions.NewClient(apiCaller) + actions, err := client.RunningActions(tag) + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + c.Assert(actions, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestRunningActionsResultError(c *gc.C) { + tag := names.NewMachineTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.RunningActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + expectedErr := ¶ms.Error{ + Message: "rigged", + Code: params.CodeNotAssigned, + } + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionsByReceivers{}) + *(result.(*params.ActionsByReceivers)) = params.ActionsByReceivers{ + Actions: []params.ActionsByReceiver{{ + Error: expectedErr, + }}, + } + return nil + }) + + client := machineactions.NewClient(apiCaller) + action, err := client.RunningActions(tag) + c.Assert(errors.Cause(err), gc.Equals, expectedErr) + c.Assert(action, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} + +func (s *ClientSuite) TestRunningActionsTooManyResults(c *gc.C) { + tag := names.NewMachineTag(utils.MustNewUUID().String()) + expectedCalls := []jujutesting.StubCall{{ + "MachineActions.RunningActions", + []interface{}{"", params.Entities{ + Entities: []params.Entity{{Tag: tag.String()}}, + }}, + }} + var stub jujutesting.Stub + + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + c.Check(result, gc.FitsTypeOf, ¶ms.ActionsByReceivers{}) + res := result.(*params.ActionsByReceivers) + res.Actions = make([]params.ActionsByReceiver, 2) + return nil + }) + + client := machineactions.NewClient(apiCaller) + actions, err := client.RunningActions(tag) + c.Assert(err, gc.ErrorMatches, "expected 1 result, got 2") + c.Assert(actions, gc.IsNil) + stub.CheckCalls(c, expectedCalls) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/machineactions/package_test.go charm-2.2.0/src/github.com/juju/juju/api/machineactions/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/machineactions/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/machineactions/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,15 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package machineactions_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/machiner/machine.go charm-2.2.0/src/github.com/juju/juju/api/machiner/machine.go --- charm-2.1.1/src/github.com/juju/juju/api/machiner/machine.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/machiner/machine.go 2016-04-28 06:03:34.000000000 +0000 @@ -109,3 +109,31 @@ } return &result, nil } + +// SetObservedNetworkConfig sets the machine network config as observed on the +// machine. +func (m *Machine) SetObservedNetworkConfig(netConfig []params.NetworkConfig) error { + args := params.SetMachineNetworkConfig{ + Tag: m.Tag().String(), + Config: netConfig, + } + err := m.st.facade.FacadeCall("SetObservedNetworkConfig", args, nil) + if err != nil { + return errors.Trace(err) + } + return nil +} + +// SetProviderNetworkConfig sets the machine network config as seen by the +// provider. +func (m *Machine) SetProviderNetworkConfig() error { + var result params.ErrorResults + args := params.Entities{ + Entities: []params.Entity{{Tag: m.tag.String()}}, + } + err := m.st.facade.FacadeCall("SetProviderNetworkConfig", args, &result) + if err != nil { + return err + } + return result.OneError() +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/machiner/machiner_test.go charm-2.2.0/src/github.com/juju/juju/api/machiner/machiner_test.go --- charm-2.1.1/src/github.com/juju/juju/api/machiner/machiner_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/machiner/machiner_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -48,7 +48,7 @@ s.st, s.machine = s.OpenAPIAsNewMachine(c) // Create the machiner API facade. - s.machiner = s.st.Machiner() + s.machiner = machiner.NewState(s.st) c.Assert(s.machiner, gc.NotNil) s.APIAddresserTests = apitesting.NewAPIAddresserTests(s.machiner, s.BackingState) } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationflag/facade.go charm-2.2.0/src/github.com/juju/juju/api/migrationflag/facade.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationflag/facade.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationflag/facade.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,88 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package migrationflag + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/api/base" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/watcher" +) + +// NewWatcherFunc exists to let us unit test Facade without patching. +type NewWatcherFunc func(base.APICaller, params.NotifyWatchResult) watcher.NotifyWatcher + +// NewFacade returns a Facade backed by the supplied api caller. +func NewFacade(apiCaller base.APICaller, newWatcher NewWatcherFunc) *Facade { + facadeCaller := base.NewFacadeCaller(apiCaller, "MigrationFlag") + return &Facade{ + caller: facadeCaller, + newWatcher: newWatcher, + } +} + +// Facade lets a client watch and query a model's migration phase. +type Facade struct { + caller base.FacadeCaller + newWatcher NewWatcherFunc +} + +// Phase returns the current migration.Phase for the supplied model UUID. +func (facade *Facade) Phase(uuid string) (migration.Phase, error) { + results := params.PhaseResults{} + err := facade.call("Phase", uuid, &results) + if err != nil { + return migration.UNKNOWN, errors.Trace(err) + } + if count := len(results.Results); count != 1 { + return migration.UNKNOWN, countError(count) + } + result := results.Results[0] + if result.Error != nil { + return migration.UNKNOWN, errors.Trace(result.Error) + } + phase, ok := migration.ParsePhase(result.Phase) + if !ok { + err := errors.Errorf("unknown phase %q", result.Phase) + return migration.UNKNOWN, err + } + return phase, nil +} + +// Watch returns a NotifyWatcher that will inform of potential changes +// to the result of Phase for the supplied model UUID. +func (facade *Facade) Watch(uuid string) (watcher.NotifyWatcher, error) { + results := params.NotifyWatchResults{} + err := facade.call("Watch", uuid, &results) + if err != nil { + return nil, errors.Trace(err) + } + if count := len(results.Results); count != 1 { + return nil, countError(count) + } + result := results.Results[0] + if result.Error != nil { + return nil, errors.Trace(result.Error) + } + apiCaller := facade.caller.RawAPICaller() + watcher := facade.newWatcher(apiCaller, result) + return watcher, nil +} + +// call converts the supplied model uuid into a params.Entities and +// invokes the facade caller. +func (facade *Facade) call(name, uuid string, results interface{}) error { + model := names.NewModelTag(uuid).String() + args := params.Entities{[]params.Entity{{model}}} + err := facade.caller.FacadeCall(name, args, results) + return errors.Trace(err) +} + +// countError complains about malformed results. +func countError(count int) error { + return errors.Errorf("expected 1 result, got %d", count) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationflag/facade_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationflag/facade_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationflag/facade_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationflag/facade_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,236 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationflag_test + +import ( + "errors" + + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/api/base" + basetesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/migrationflag" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/watcher" +) + +type FacadeSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&FacadeSuite{}) + +func (*FacadeSuite) TestPhaseCallError(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(interface{}) error { + return errors.New("bork") + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + phase, err := facade.Phase(someUUID) + c.Check(err, gc.ErrorMatches, "bork") + c.Check(phase, gc.Equals, migration.UNKNOWN) + checkCalls(c, stub, "Phase") +} + +func (*FacadeSuite) TestPhaseNoResults(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(interface{}) error { + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + phase, err := facade.Phase(someUUID) + c.Check(err, gc.ErrorMatches, "expected 1 result, got 0") + c.Check(phase, gc.Equals, migration.UNKNOWN) + checkCalls(c, stub, "Phase") +} + +func (*FacadeSuite) TestPhaseExtraResults(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(response interface{}) error { + outPtr, ok := response.(*params.PhaseResults) + c.Assert(ok, jc.IsTrue) + outPtr.Results = []params.PhaseResult{ + {Phase: "ABORT"}, + {Phase: "DONE"}, + } + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + phase, err := facade.Phase(someUUID) + c.Check(err, gc.ErrorMatches, "expected 1 result, got 2") + c.Check(phase, gc.Equals, migration.UNKNOWN) + checkCalls(c, stub, "Phase") +} + +func (*FacadeSuite) TestPhaseError(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(response interface{}) error { + outPtr, ok := response.(*params.PhaseResults) + c.Assert(ok, jc.IsTrue) + outPtr.Results = []params.PhaseResult{ + {Error: ¶ms.Error{Message: "mneh"}}, + } + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + phase, err := facade.Phase(someUUID) + c.Check(err, gc.ErrorMatches, "mneh") + c.Check(phase, gc.Equals, migration.UNKNOWN) + checkCalls(c, stub, "Phase") +} + +func (*FacadeSuite) TestPhaseInvalid(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(response interface{}) error { + outPtr, ok := response.(*params.PhaseResults) + c.Assert(ok, jc.IsTrue) + outPtr.Results = []params.PhaseResult{{Phase: "COLLABORATE"}} + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + phase, err := facade.Phase(someUUID) + c.Check(err, gc.ErrorMatches, `unknown phase "COLLABORATE"`) + c.Check(phase, gc.Equals, migration.UNKNOWN) + checkCalls(c, stub, "Phase") +} + +func (*FacadeSuite) TestPhaseSuccess(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(response interface{}) error { + outPtr, ok := response.(*params.PhaseResults) + c.Assert(ok, jc.IsTrue) + outPtr.Results = []params.PhaseResult{{Phase: "ABORT"}} + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + phase, err := facade.Phase(someUUID) + c.Check(err, jc.ErrorIsNil) + c.Check(phase, gc.Equals, migration.ABORT) + checkCalls(c, stub, "Phase") +} + +func (*FacadeSuite) TestWatchCallError(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(interface{}) error { + return errors.New("bork") + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + watch, err := facade.Watch(someUUID) + c.Check(err, gc.ErrorMatches, "bork") + c.Check(watch, gc.IsNil) + checkCalls(c, stub, "Watch") +} + +func (*FacadeSuite) TestWatchNoResults(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(interface{}) error { + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + watch, err := facade.Watch(someUUID) + c.Check(err, gc.ErrorMatches, "expected 1 result, got 0") + c.Check(watch, gc.IsNil) + checkCalls(c, stub, "Watch") +} + +func (*FacadeSuite) TestWatchExtraResults(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(response interface{}) error { + outPtr, ok := response.(*params.NotifyWatchResults) + c.Assert(ok, jc.IsTrue) + outPtr.Results = []params.NotifyWatchResult{ + {NotifyWatcherId: "123"}, + {NotifyWatcherId: "456"}, + } + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + watch, err := facade.Watch(someUUID) + c.Check(err, gc.ErrorMatches, "expected 1 result, got 2") + c.Check(watch, gc.IsNil) + checkCalls(c, stub, "Watch") +} + +func (*FacadeSuite) TestWatchError(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(response interface{}) error { + outPtr, ok := response.(*params.NotifyWatchResults) + c.Assert(ok, jc.IsTrue) + outPtr.Results = []params.NotifyWatchResult{ + {Error: ¶ms.Error{Message: "snfl"}}, + } + return nil + }) + facade := migrationflag.NewFacade(apiCaller, nil) + + watch, err := facade.Watch(someUUID) + c.Check(err, gc.ErrorMatches, "snfl") + c.Check(watch, gc.IsNil) + checkCalls(c, stub, "Watch") +} + +func (*FacadeSuite) TestWatchSuccess(c *gc.C) { + stub := &testing.Stub{} + apiCaller := apiCaller(c, stub, func(response interface{}) error { + outPtr, ok := response.(*params.NotifyWatchResults) + c.Assert(ok, jc.IsTrue) + outPtr.Results = []params.NotifyWatchResult{ + {NotifyWatcherId: "789"}, + } + return nil + }) + expectWatch := &struct{ watcher.NotifyWatcher }{} + newWatcher := func(gotCaller base.APICaller, result params.NotifyWatchResult) watcher.NotifyWatcher { + c.Check(gotCaller, gc.NotNil) // uncomparable + c.Check(result, jc.DeepEquals, params.NotifyWatchResult{ + NotifyWatcherId: "789", + }) + return expectWatch + } + facade := migrationflag.NewFacade(apiCaller, newWatcher) + + watch, err := facade.Watch(someUUID) + c.Check(err, jc.ErrorIsNil) + c.Check(watch, gc.Equals, expectWatch) + checkCalls(c, stub, "Watch") +} + +func apiCaller(c *gc.C, stub *testing.Stub, set func(interface{}) error) base.APICaller { + return basetesting.APICallerFunc(func( + objType string, version int, + id, request string, + args, response interface{}, + ) error { + c.Check(objType, gc.Equals, "MigrationFlag") + c.Check(version, gc.Equals, 0) + c.Check(id, gc.Equals, "") + stub.AddCall(request, args) + return set(response) + }) +} + +func checkCalls(c *gc.C, stub *testing.Stub, names ...string) { + stub.CheckCallNames(c, names...) + for _, call := range stub.Calls() { + c.Check(call.Args, jc.DeepEquals, []interface{}{ + params.Entities{ + []params.Entity{{"model-some-uuid"}}, + }, + }) + } +} + +const someUUID = "some-uuid" diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationflag/package_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationflag/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationflag/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationflag/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package migrationflag_test + +import ( + stdtesting "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *stdtesting.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationmaster/client.go charm-2.2.0/src/github.com/juju/juju/api/migrationmaster/client.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationmaster/client.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationmaster/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,130 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/api/base" + apiwatcher "github.com/juju/juju/api/watcher" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/watcher" +) + +// Client describes the client side API for the MigrationMaster facade +// (used by the migration master worker). +type Client interface { + // Watch returns a watcher which reports when a migration is + // active for the model associated with the API connection. + Watch() (watcher.NotifyWatcher, error) + + // GetMigrationStatus returns the details and progress of the + // latest model migration. + GetMigrationStatus() (MigrationStatus, error) + + // SetPhase updates the phase of the currently active model + // migration. + SetPhase(migration.Phase) error + + // Export returns a serialized representation of the model + // associated with the API connection. + Export() ([]byte, error) +} + +// MigrationStatus returns the details for a migration as needed by +// the migration master worker. +type MigrationStatus struct { + ModelUUID string + Attempt int + Phase migration.Phase + TargetInfo migration.TargetInfo +} + +// NewClient returns a new Client based on an existing API connection. +func NewClient(caller base.APICaller) Client { + return &client{base.NewFacadeCaller(caller, "MigrationMaster")} +} + +// client implements Client. +type client struct { + caller base.FacadeCaller +} + +// Watch implements Client. +func (c *client) Watch() (watcher.NotifyWatcher, error) { + var result params.NotifyWatchResult + err := c.caller.FacadeCall("Watch", nil, &result) + if err != nil { + return nil, errors.Trace(err) + } + if result.Error != nil { + return nil, result.Error + } + w := apiwatcher.NewNotifyWatcher(c.caller.RawAPICaller(), result) + return w, nil +} + +// GetMigrationStatus implements Client. +func (c *client) GetMigrationStatus() (MigrationStatus, error) { + var empty MigrationStatus + var status params.FullMigrationStatus + err := c.caller.FacadeCall("GetMigrationStatus", nil, &status) + if err != nil { + return empty, errors.Trace(err) + } + + modelTag, err := names.ParseModelTag(status.Spec.ModelTag) + if err != nil { + return empty, errors.Annotatef(err, "parsing model tag") + } + + phase, ok := migration.ParsePhase(status.Phase) + if !ok { + return empty, errors.New("unable to parse phase") + } + + target := status.Spec.TargetInfo + controllerTag, err := names.ParseModelTag(target.ControllerTag) + if err != nil { + return empty, errors.Annotatef(err, "parsing controller tag") + } + + authTag, err := names.ParseUserTag(target.AuthTag) + if err != nil { + return empty, errors.Annotatef(err, "unable to parse auth tag") + } + + return MigrationStatus{ + ModelUUID: modelTag.Id(), + Attempt: status.Attempt, + Phase: phase, + TargetInfo: migration.TargetInfo{ + ControllerTag: controllerTag, + Addrs: target.Addrs, + CACert: target.CACert, + AuthTag: authTag, + Password: target.Password, + }, + }, nil +} + +// SetPhase implements Client. +func (c *client) SetPhase(phase migration.Phase) error { + args := params.SetMigrationPhaseArgs{ + Phase: phase.String(), + } + return c.caller.FacadeCall("SetPhase", args, nil) +} + +// Export implements Client. +func (c *client) Export() ([]byte, error) { + var serialized params.SerializedModel + err := c.caller.FacadeCall("Export", nil, &serialized) + if err != nil { + return nil, err + } + return serialized.Bytes, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationmaster/client_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationmaster/client_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationmaster/client_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationmaster/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,175 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster_test + +import ( + "time" + + "github.com/juju/errors" + "github.com/juju/names" + jujutesting "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils" + gc "gopkg.in/check.v1" + + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/migrationmaster" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" + coretesting "github.com/juju/juju/testing" + "github.com/juju/juju/worker" +) + +type ClientSuite struct { + jujutesting.IsolationSuite +} + +var _ = gc.Suite(&ClientSuite{}) + +func (s *ClientSuite) TestWatch(c *gc.C) { + var stub jujutesting.Stub + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + switch request { + case "Watch": + *(result.(*params.NotifyWatchResult)) = params.NotifyWatchResult{ + NotifyWatcherId: "abc", + } + case "Next": + // The full success case is tested in api/watcher. + return errors.New("boom") + case "Stop": + } + return nil + }) + + client := migrationmaster.NewClient(apiCaller) + w, err := client.Watch() + c.Assert(err, jc.ErrorIsNil) + defer worker.Stop(w) + + errC := make(chan error) + go func() { + errC <- w.Wait() + }() + + select { + case err := <-errC: + c.Assert(err, gc.ErrorMatches, "boom") + expectedCalls := []jujutesting.StubCall{ + {"MigrationMaster.Watch", []interface{}{"", nil}}, + {"NotifyWatcher.Next", []interface{}{"abc", nil}}, + {"NotifyWatcher.Stop", []interface{}{"abc", nil}}, + } + // The Stop API call happens in a separate goroutine which + // might execute after the worker has exited so wait for the + // expected calls to arrive. + for a := coretesting.LongAttempt.Start(); a.Next(); { + if len(stub.Calls()) >= len(expectedCalls) { + return + } + } + stub.CheckCalls(c, expectedCalls) + case <-time.After(coretesting.LongWait): + c.Fatal("timed out waiting for watcher to die") + } +} + +func (s *ClientSuite) TestWatchErr(c *gc.C) { + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + return errors.New("boom") + }) + client := migrationmaster.NewClient(apiCaller) + _, err := client.Watch() + c.Assert(err, gc.ErrorMatches, "boom") +} + +func (s *ClientSuite) TestGetMigrationStatus(c *gc.C) { + modelUUID := utils.MustNewUUID().String() + controllerUUID := utils.MustNewUUID().String() + apiCaller := apitesting.APICallerFunc(func(_ string, _ int, _, _ string, _, result interface{}) error { + out := result.(*params.FullMigrationStatus) + *out = params.FullMigrationStatus{ + Spec: params.ModelMigrationSpec{ + ModelTag: names.NewModelTag(modelUUID).String(), + TargetInfo: params.ModelMigrationTargetInfo{ + ControllerTag: names.NewModelTag(controllerUUID).String(), + Addrs: []string{"2.2.2.2:2"}, + CACert: "cert", + AuthTag: names.NewUserTag("admin").String(), + Password: "secret", + }, + }, + Attempt: 3, + Phase: "READONLY", + } + return nil + }) + client := migrationmaster.NewClient(apiCaller) + + status, err := client.GetMigrationStatus() + c.Assert(err, jc.ErrorIsNil) + c.Assert(status, gc.DeepEquals, migrationmaster.MigrationStatus{ + ModelUUID: modelUUID, + Attempt: 3, + Phase: migration.READONLY, + TargetInfo: migration.TargetInfo{ + ControllerTag: names.NewModelTag(controllerUUID), + Addrs: []string{"2.2.2.2:2"}, + CACert: "cert", + AuthTag: names.NewUserTag("admin"), + Password: "secret", + }, + }) +} + +func (s *ClientSuite) TestSetPhase(c *gc.C) { + var stub jujutesting.Stub + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + return nil + }) + client := migrationmaster.NewClient(apiCaller) + err := client.SetPhase(migration.QUIESCE) + c.Assert(err, jc.ErrorIsNil) + expectedArg := params.SetMigrationPhaseArgs{Phase: "QUIESCE"} + stub.CheckCalls(c, []jujutesting.StubCall{ + {"MigrationMaster.SetPhase", []interface{}{"", expectedArg}}, + }) +} + +func (s *ClientSuite) TestSetPhaseError(c *gc.C) { + apiCaller := apitesting.APICallerFunc(func(string, int, string, string, interface{}, interface{}) error { + return errors.New("boom") + }) + client := migrationmaster.NewClient(apiCaller) + err := client.SetPhase(migration.QUIESCE) + c.Assert(err, gc.ErrorMatches, "boom") +} + +func (s *ClientSuite) TestExport(c *gc.C) { + var stub jujutesting.Stub + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + out := result.(*params.SerializedModel) + *out = params.SerializedModel{Bytes: []byte("foo")} + return nil + }) + client := migrationmaster.NewClient(apiCaller) + bytes, err := client.Export() + c.Assert(err, jc.ErrorIsNil) + stub.CheckCalls(c, []jujutesting.StubCall{ + {"MigrationMaster.Export", []interface{}{"", nil}}, + }) + c.Assert(string(bytes), gc.Equals, "foo") +} + +func (s *ClientSuite) TestExportError(c *gc.C) { + apiCaller := apitesting.APICallerFunc(func(string, int, string, string, interface{}, interface{}) error { + return errors.New("blam") + }) + client := migrationmaster.NewClient(apiCaller) + _, err := client.Export() + c.Assert(err, gc.ErrorMatches, "blam") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationmaster/package_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationmaster/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationmaster/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationmaster/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationminion/client.go charm-2.2.0/src/github.com/juju/juju/api/migrationminion/client.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationminion/client.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationminion/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,46 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion + +import ( + "github.com/juju/errors" + + "github.com/juju/juju/api/base" + apiwatcher "github.com/juju/juju/api/watcher" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/watcher" +) + +// Client describes the client side API for the MigrationMinion facade +// (used by the migration minion worker). +type Client interface { + // Watch returns a watcher which reports when the status changes + // for the migration for the model associated with the API + // connection. + Watch() (watcher.MigrationStatusWatcher, error) +} + +// NewClient returns a new Client based on an existing API connection. +func NewClient(caller base.APICaller) Client { + return &client{base.NewFacadeCaller(caller, "MigrationMinion")} +} + +// client implements Client. +type client struct { + caller base.FacadeCaller +} + +// Watch implements Client. +func (c *client) Watch() (watcher.MigrationStatusWatcher, error) { + var result params.NotifyWatchResult + err := c.caller.FacadeCall("Watch", nil, &result) + if err != nil { + return nil, errors.Trace(err) + } + if result.Error != nil { + return nil, result.Error + } + w := apiwatcher.NewMigrationStatusWatcher(c.caller.RawAPICaller(), result.NotifyWatcherId) + return w, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationminion/client_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationminion/client_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationminion/client_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationminion/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,83 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion_test + +import ( + "time" + + "github.com/juju/errors" + jujutesting "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/migrationminion" + "github.com/juju/juju/apiserver/params" + coretesting "github.com/juju/juju/testing" + "github.com/juju/juju/worker" +) + +type ClientSuite struct { + jujutesting.IsolationSuite +} + +var _ = gc.Suite(&ClientSuite{}) + +func (s *ClientSuite) TestWatch(c *gc.C) { + var stub jujutesting.Stub + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + switch request { + case "Watch": + *(result.(*params.NotifyWatchResult)) = params.NotifyWatchResult{ + NotifyWatcherId: "abc", + } + case "Next": + // The full success case is tested in api/watcher. + return errors.New("boom") + case "Stop": + } + return nil + }) + + client := migrationminion.NewClient(apiCaller) + w, err := client.Watch() + c.Assert(err, jc.ErrorIsNil) + defer worker.Stop(w) + + errC := make(chan error) + go func() { + errC <- w.Wait() + }() + + select { + case err := <-errC: + c.Assert(err, gc.ErrorMatches, "boom") + expectedCalls := []jujutesting.StubCall{ + {"Migrationminion.Watch", []interface{}{"", nil}}, + {"MigrationStatusWatcher.Next", []interface{}{"abc", nil}}, + {"MigrationStatusWatcher.Stop", []interface{}{"abc", nil}}, + } + // The Stop API call happens in a separate goroutine which + // might execute after the worker has exited so wait for the + // expected calls to arrive. + for a := coretesting.LongAttempt.Start(); a.Next(); { + if len(stub.Calls()) >= len(expectedCalls) { + return + } + } + stub.CheckCalls(c, expectedCalls) + case <-time.After(coretesting.LongWait): + c.Fatal("timed out waiting for watcher to die") + } +} + +func (s *ClientSuite) TestWatchErr(c *gc.C) { + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + return errors.New("boom") + }) + client := migrationminion.NewClient(apiCaller) + _, err := client.Watch() + c.Assert(err, gc.ErrorMatches, "boom") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationminion/package_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationminion/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationminion/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationminion/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/client.go charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/client.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/client.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,55 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationtarget + +import ( + "github.com/juju/names" + + "github.com/juju/juju/api/base" + "github.com/juju/juju/apiserver/params" +) + +// Client describes the client side API for the MigrationTarget +// facade. It is called by the migration master worker to talk to the +// target controller during a migration. +type Client interface { + // Import takes a serialized model and imports it into the target + // controller. + Import([]byte) error + + // Abort removes all data relating to a previously imported + // model. + Abort(string) error + + // Activate marks a migrated model as being ready to use. + Activate(string) error +} + +// NewClient returns a new Client based on an existing API connection. +func NewClient(caller base.APICaller) Client { + return &client{base.NewFacadeCaller(caller, "MigrationTarget")} +} + +// client implements Client. +type client struct { + caller base.FacadeCaller +} + +// Import implements Client. +func (c *client) Import(bytes []byte) error { + serialized := params.SerializedModel{Bytes: bytes} + return c.caller.FacadeCall("Import", serialized, nil) +} + +// Abort implements Client. +func (c *client) Abort(modelUUID string) error { + args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()} + return c.caller.FacadeCall("Abort", args, nil) +} + +// Activate implements Client. +func (c *client) Activate(modelUUID string) error { + args := params.ModelArgs{ModelTag: names.NewModelTag(modelUUID).String()} + return c.caller.FacadeCall("Activate", args, nil) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/client_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/client_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/client_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,67 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationtarget_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + jujutesting "github.com/juju/testing" + gc "gopkg.in/check.v1" + + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/migrationtarget" + "github.com/juju/juju/apiserver/params" +) + +type ClientSuite struct { + jujutesting.IsolationSuite +} + +var _ = gc.Suite(&ClientSuite{}) + +func (s *ClientSuite) getClientAndStub(c *gc.C) (migrationtarget.Client, *jujutesting.Stub) { + var stub jujutesting.Stub + apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + stub.AddCall(objType+"."+request, id, arg) + return errors.New("boom") + }) + client := migrationtarget.NewClient(apiCaller) + return client, &stub +} + +func (s *ClientSuite) TestImport(c *gc.C) { + client, stub := s.getClientAndStub(c) + + err := client.Import([]byte("foo")) + + expectedArg := params.SerializedModel{Bytes: []byte("foo")} + stub.CheckCalls(c, []jujutesting.StubCall{ + {"MigrationTarget.Import", []interface{}{"", expectedArg}}, + }) + c.Assert(err, gc.ErrorMatches, "boom") +} + +func (s *ClientSuite) TestAbort(c *gc.C) { + client, stub := s.getClientAndStub(c) + + uuid := "fake" + err := client.Abort(uuid) + s.AssertModelCall(c, stub, names.NewModelTag(uuid), "Abort", err) +} + +func (s *ClientSuite) TestActivate(c *gc.C) { + client, stub := s.getClientAndStub(c) + + uuid := "fake" + err := client.Activate(uuid) + s.AssertModelCall(c, stub, names.NewModelTag(uuid), "Activate", err) +} + +func (s *ClientSuite) AssertModelCall(c *gc.C, stub *jujutesting.Stub, tag names.ModelTag, call string, err error) { + expectedArg := params.ModelArgs{ModelTag: tag.String()} + stub.CheckCalls(c, []jujutesting.StubCall{ + {"MigrationTarget." + call, []interface{}{"", expectedArg}}, + }) + c.Assert(err, gc.ErrorMatches, "boom") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/doc.go charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/doc.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/doc.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/doc.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,7 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// Package migrationtarget defines the client side API facade for use by the +// migration master worker when communicating with the target +// controller. +package migrationtarget diff -Nru charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/package_test.go charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/migrationtarget/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/migrationtarget/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationtarget_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/modelmanager/access_test.go charm-2.2.0/src/github.com/juju/juju/api/modelmanager/access_test.go --- charm-2.1.1/src/github.com/juju/juju/api/modelmanager/access_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/modelmanager/access_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,192 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package modelmanager_test + +import ( + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + basetesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/modelmanager" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/testing" +) + +type accessSuite struct { + testing.BaseSuite +} + +type accessFunc func(string, string, ...string) error + +var _ = gc.Suite(&accessSuite{}) + +const ( + someModelUUID = "63f5e78f-2d21-4d0c-a5c1-73463f3443bf" + someModelTag = "model-" + someModelUUID +) + +func accessCall(client *modelmanager.Client, action params.ModelAction, user, access string, modelUUIDs ...string) error { + switch action { + case params.GrantModelAccess: + return client.GrantModel(user, access, modelUUIDs...) + case params.RevokeModelAccess: + return client.RevokeModel(user, access, modelUUIDs...) + default: + panic(action) + } +} + +func (s *accessSuite) TestGrantModelReadOnlyUser(c *gc.C) { + s.readOnlyUser(c, params.GrantModelAccess) +} + +func (s *accessSuite) TestRevokeModelReadOnlyUser(c *gc.C) { + s.readOnlyUser(c, params.RevokeModelAccess) +} + +func checkCall(c *gc.C, objType string, id, request string) { + c.Check(objType, gc.Equals, "ModelManager") + c.Check(id, gc.Equals, "") + c.Check(request, gc.Equals, "ModifyModelAccess") +} + +func assertRequest(c *gc.C, a interface{}) params.ModifyModelAccessRequest { + req, ok := a.(params.ModifyModelAccessRequest) + c.Assert(ok, jc.IsTrue, gc.Commentf("wrong request type")) + return req +} + +func assertResponse(c *gc.C, result interface{}) *params.ErrorResults { + resp, ok := result.(*params.ErrorResults) + c.Assert(ok, jc.IsTrue, gc.Commentf("wrong response type")) + return resp +} + +func (s *accessSuite) readOnlyUser(c *gc.C, action params.ModelAction) { + apiCaller := basetesting.APICallerFunc( + func(objType string, version int, id, request string, a, result interface{}) error { + checkCall(c, objType, id, request) + + req := assertRequest(c, a) + c.Assert(req.Changes, gc.HasLen, 1) + c.Assert(string(req.Changes[0].Action), gc.Equals, string(action)) + c.Assert(string(req.Changes[0].Access), gc.Equals, string(params.ModelReadAccess)) + c.Assert(req.Changes[0].ModelTag, gc.Equals, someModelTag) + + resp := assertResponse(c, result) + *resp = params.ErrorResults{Results: []params.ErrorResult{{Error: nil}}} + + return nil + }) + client := modelmanager.NewClient(apiCaller) + err := accessCall(client, action, "bob", "read", someModelUUID) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *accessSuite) TestGrantModelAdminUser(c *gc.C) { + s.adminUser(c, params.GrantModelAccess) +} + +func (s *accessSuite) TestRevokeModelAdminUser(c *gc.C) { + s.adminUser(c, params.RevokeModelAccess) +} + +func (s *accessSuite) adminUser(c *gc.C, action params.ModelAction) { + apiCaller := basetesting.APICallerFunc( + func(objType string, version int, id, request string, a, result interface{}) error { + checkCall(c, objType, id, request) + + req := assertRequest(c, a) + c.Assert(req.Changes, gc.HasLen, 1) + c.Assert(string(req.Changes[0].Action), gc.Equals, string(action)) + c.Assert(string(req.Changes[0].Access), gc.Equals, string(params.ModelWriteAccess)) + c.Assert(req.Changes[0].ModelTag, gc.Equals, someModelTag) + + resp := assertResponse(c, result) + *resp = params.ErrorResults{Results: []params.ErrorResult{{Error: nil}}} + + return nil + }) + client := modelmanager.NewClient(apiCaller) + err := accessCall(client, action, "bob", "write", someModelUUID) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *accessSuite) TestGrantThreeModels(c *gc.C) { + s.threeModels(c, params.GrantModelAccess) +} + +func (s *accessSuite) TestRevokeThreeModels(c *gc.C) { + s.threeModels(c, params.RevokeModelAccess) +} + +func (s *accessSuite) threeModels(c *gc.C, action params.ModelAction) { + apiCaller := basetesting.APICallerFunc( + func(objType string, version int, id, request string, a, result interface{}) error { + checkCall(c, objType, id, request) + + req := assertRequest(c, a) + c.Assert(req.Changes, gc.HasLen, 3) + for i := range req.Changes { + c.Assert(string(req.Changes[i].Action), gc.Equals, string(action)) + c.Assert(string(req.Changes[i].Access), gc.Equals, string(params.ModelReadAccess)) + c.Assert(req.Changes[i].ModelTag, gc.Equals, someModelTag) + } + + resp := assertResponse(c, result) + *resp = params.ErrorResults{Results: []params.ErrorResult{{Error: nil}, {Error: nil}, {Error: nil}}} + + return nil + }) + client := modelmanager.NewClient(apiCaller) + err := accessCall(client, action, "carol", "read", someModelUUID, someModelUUID, someModelUUID) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *accessSuite) TestGrantErrorResult(c *gc.C) { + s.errorResult(c, params.GrantModelAccess) +} + +func (s *accessSuite) TestRevokeErrorResult(c *gc.C) { + s.errorResult(c, params.RevokeModelAccess) +} + +func (s *accessSuite) errorResult(c *gc.C, action params.ModelAction) { + apiCaller := basetesting.APICallerFunc( + func(objType string, version int, id, request string, a, result interface{}) error { + checkCall(c, objType, id, request) + + req := assertRequest(c, a) + c.Assert(req.Changes, gc.HasLen, 1) + c.Assert(string(req.Changes[0].Action), gc.Equals, string(action)) + c.Assert(req.Changes[0].UserTag, gc.Equals, names.NewUserTag("aaa").String()) + c.Assert(req.Changes[0].ModelTag, gc.Equals, someModelTag) + + resp := assertResponse(c, result) + err := ¶ms.Error{Message: "unfortunate mishap"} + *resp = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}} + + return nil + }) + client := modelmanager.NewClient(apiCaller) + err := accessCall(client, action, "aaa", "write", someModelUUID) + c.Assert(err, gc.ErrorMatches, "unfortunate mishap") +} + +func (s *accessSuite) TestInvalidResultCount(c *gc.C) { + apiCaller := basetesting.APICallerFunc( + func(objType string, version int, id, request string, a, result interface{}) error { + checkCall(c, objType, id, request) + assertRequest(c, a) + + resp := assertResponse(c, result) + *resp = params.ErrorResults{Results: nil} + + return nil + }) + client := modelmanager.NewClient(apiCaller) + err := client.GrantModel("bob", "write", someModelUUID, someModelUUID) + c.Assert(err, gc.ErrorMatches, "expected 2 results, got 0") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/modelmanager/export_test.go charm-2.2.0/src/github.com/juju/juju/api/modelmanager/export_test.go --- charm-2.1.1/src/github.com/juju/juju/api/modelmanager/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/modelmanager/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package modelmanager + +import ( + "github.com/juju/juju/api/base/testing" +) + +// PatchFacadeCall changes the internal FacadeCaller to an arbitrary call +// function for testing. +func PatchFacadeCall(p testing.Patcher, client *Client, call func(request string, params, response interface{}) error) { + testing.PatchFacadeCall(p, &client.facade, call) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/modelmanager/modelinfo_test.go charm-2.2.0/src/github.com/juju/juju/api/modelmanager/modelinfo_test.go --- charm-2.1.1/src/github.com/juju/juju/api/modelmanager/modelinfo_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/modelmanager/modelinfo_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,70 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package modelmanager_test + +import ( + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + basetesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/modelmanager" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/testing" +) + +type modelInfoSuite struct { + testing.BaseSuite +} + +var _ = gc.Suite(&modelInfoSuite{}) + +func (s *modelInfoSuite) checkCall(c *gc.C, objType string, id, request string) { + c.Check(objType, gc.Equals, "ModelManager") + c.Check(id, gc.Equals, "") + c.Check(request, gc.Equals, "ModelInfo") +} + +func (s *modelInfoSuite) assertResponse(c *gc.C, result interface{}) *params.ModelInfoResults { + c.Assert(result, gc.FitsTypeOf, ¶ms.ModelInfoResults{}) + return result.(*params.ModelInfoResults) +} + +func (s *modelInfoSuite) TestModelInfo(c *gc.C) { + results := params.ModelInfoResults{ + Results: []params.ModelInfoResult{{ + Result: ¶ms.ModelInfo{Name: "name", UUID: "etc."}, + }, { + Error: ¶ms.Error{Message: "woop"}, + }}, + } + apiCaller := basetesting.APICallerFunc( + func(objType string, version int, id, request string, a, result interface{}) error { + s.checkCall(c, objType, id, request) + resp := s.assertResponse(c, result) + *resp = results + return nil + }) + client := modelmanager.NewClient(apiCaller) + info, err := client.ModelInfo([]names.ModelTag{testing.ModelTag, testing.ModelTag}) + c.Assert(err, jc.ErrorIsNil) + c.Assert(info, jc.DeepEquals, results.Results) +} + +func (s *modelInfoSuite) TestInvalidResultCount(c *gc.C) { + apiCaller := basetesting.APICallerFunc( + func(objType string, version int, id, request string, a, result interface{}) error { + s.checkCall(c, objType, id, request) + c.Assert(a, jc.DeepEquals, params.Entities{ + Entities: []params.Entity{{testing.ModelTag.String()}, {testing.ModelTag.String()}}, + }) + resp := s.assertResponse(c, result) + *resp = params.ModelInfoResults{Results: []params.ModelInfoResult{{}}} + return nil + }, + ) + client := modelmanager.NewClient(apiCaller) + _, err := client.ModelInfo([]names.ModelTag{testing.ModelTag, testing.ModelTag}) + c.Assert(err, gc.ErrorMatches, "expected 2 result\\(s\\), got 1") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/modelmanager/modelmanager.go charm-2.2.0/src/github.com/juju/juju/api/modelmanager/modelmanager.go --- charm-2.1.1/src/github.com/juju/juju/api/modelmanager/modelmanager.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/modelmanager/modelmanager.go 2016-04-28 06:03:34.000000000 +0000 @@ -10,6 +10,7 @@ "github.com/juju/juju/api/base" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/juju/permission" ) var logger = loggo.GetLogger("juju.api.modelmanager") @@ -100,3 +101,95 @@ } return result, nil } + +func (c *Client) ModelInfo(tags []names.ModelTag) ([]params.ModelInfoResult, error) { + entities := params.Entities{ + Entities: make([]params.Entity, len(tags)), + } + for i, tag := range tags { + entities.Entities[i].Tag = tag.String() + } + var results params.ModelInfoResults + err := c.facade.FacadeCall("ModelInfo", entities, &results) + if err != nil { + return nil, errors.Trace(err) + } + if len(results.Results) != len(tags) { + return nil, errors.Errorf("expected %d result(s), got %d", len(tags), len(results.Results)) + } + return results.Results, nil +} + +// ParseModelAccess parses an access permission argument into +// a type suitable for making an API facade call. +func ParseModelAccess(access string) (params.ModelAccessPermission, error) { + var fail params.ModelAccessPermission + + modelAccess, err := permission.ParseModelAccess(access) + if err != nil { + return fail, errors.Trace(err) + } + var accessPermission params.ModelAccessPermission + switch modelAccess { + case permission.ModelReadAccess: + accessPermission = params.ModelReadAccess + case permission.ModelWriteAccess: + accessPermission = params.ModelWriteAccess + default: + return fail, errors.Errorf("unsupported model access permission %v", modelAccess) + } + return accessPermission, nil +} + +// GrantModel grants a user access to the specified models. +func (c *Client) GrantModel(user, access string, modelUUIDs ...string) error { + return c.modifyModelUser(params.GrantModelAccess, user, access, modelUUIDs) +} + +// RevokeModel revokes a user's access to the specified models. +func (c *Client) RevokeModel(user, access string, modelUUIDs ...string) error { + return c.modifyModelUser(params.RevokeModelAccess, user, access, modelUUIDs) +} + +func (c *Client) modifyModelUser(action params.ModelAction, user, access string, modelUUIDs []string) error { + var args params.ModifyModelAccessRequest + + if !names.IsValidUser(user) { + return errors.Errorf("invalid username: %q", user) + } + userTag := names.NewUserTag(user) + + accessPermission, err := ParseModelAccess(access) + if err != nil { + return errors.Trace(err) + } + for _, model := range modelUUIDs { + if !names.IsValidModel(model) { + return errors.Errorf("invalid model: %q", model) + } + modelTag := names.NewModelTag(model) + args.Changes = append(args.Changes, params.ModifyModelAccess{ + UserTag: userTag.String(), + Action: action, + Access: accessPermission, + ModelTag: modelTag.String(), + }) + } + + var result params.ErrorResults + err = c.facade.FacadeCall("ModifyModelAccess", args, &result) + if err != nil { + return errors.Trace(err) + } + if len(result.Results) != len(args.Changes) { + return errors.Errorf("expected %d results, got %d", len(args.Changes), len(result.Results)) + } + + for i, r := range result.Results { + if r.Error != nil && r.Error.Code == params.CodeAlreadyExists { + logger.Warningf("model %q is already shared with %q", modelUUIDs[i], userTag.Canonical()) + result.Results[i].Error = nil + } + } + return result.Combine() +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/modelmanager/modelmanager_test.go charm-2.2.0/src/github.com/juju/juju/api/modelmanager/modelmanager_test.go --- charm-2.1.1/src/github.com/juju/juju/api/modelmanager/modelmanager_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/modelmanager/modelmanager_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -41,10 +41,11 @@ // Numbers coming over the api are floats, not ints. c.Assert(result, jc.DeepEquals, params.ModelConfig{ - "type": "dummy", - "ca-cert": coretesting.CACert, - "state-port": float64(1234), - "api-port": float64(apiPort), + "type": "dummy", + "controller-uuid": coretesting.ModelTag.Id(), + "ca-cert": coretesting.CACert, + "state-port": float64(1234), + "api-port": float64(apiPort), }) } @@ -58,7 +59,7 @@ func (s *modelmanagerSuite) TestCreateModelMissingConfig(c *gc.C) { modelManager := s.OpenAPI(c) _, err := modelManager.CreateModel("owner", nil, nil) - c.Assert(err, gc.ErrorMatches, `creating config from values failed: name: expected string, got nothing`) + c.Assert(err, gc.ErrorMatches, `failed to create config: creating config from values failed: name: expected string, got nothing`) } func (s *modelmanagerSuite) TestCreateModel(c *gc.C) { diff -Nru charm-2.1.1/src/github.com/juju/juju/api/provisioner/machine.go charm-2.2.0/src/github.com/juju/juju/api/provisioner/machine.go --- charm-2.1.1/src/github.com/juju/juju/api/provisioner/machine.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/provisioner/machine.go 2016-04-28 06:03:34.000000000 +0000 @@ -212,12 +212,11 @@ return result.Result, nil } -// SetInstanceInfo sets the provider specific instance id, nonce, -// metadata, networks and interfaces for this machine. Once set, the -// instance id cannot be changed. +// SetInstanceInfo sets the provider specific instance id, nonce, metadata, +// network config for this machine. Once set, the instance id cannot be changed. func (m *Machine) SetInstanceInfo( id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics, - networks []params.Network, interfaces []params.NetworkInterface, volumes []params.Volume, + networkConfig []params.NetworkConfig, volumes []params.Volume, volumeAttachments map[string]params.VolumeAttachmentInfo, ) error { var result params.ErrorResults @@ -227,10 +226,9 @@ InstanceId: id, Nonce: nonce, Characteristics: characteristics, - Networks: networks, - Interfaces: interfaces, Volumes: volumes, VolumeAttachments: volumeAttachments, + NetworkConfig: networkConfig, }}, } err := m.st.facade.FacadeCall("SetInstanceInfo", args, &result) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/provisioner/provisioner.go charm-2.2.0/src/github.com/juju/juju/api/provisioner/provisioner.go --- charm-2.1.1/src/github.com/juju/juju/api/provisioner/provisioner.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/provisioner/provisioner.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "github.com/juju/errors" "github.com/juju/names" + "github.com/juju/version" "github.com/juju/juju/api/base" "github.com/juju/juju/api/common" @@ -13,7 +14,6 @@ "github.com/juju/juju/apiserver/params" "github.com/juju/juju/network" "github.com/juju/juju/tools" - "github.com/juju/juju/version" "github.com/juju/juju/watcher" ) @@ -210,23 +210,28 @@ return nil, err } ifaceInfo := make([]network.InterfaceInfo, len(result.Results[0].Config)) - for i, netInfo := range result.Results[0].Config { + for i, cfg := range result.Results[0].Config { ifaceInfo[i] = network.InterfaceInfo{ - DeviceIndex: netInfo.DeviceIndex, - MACAddress: netInfo.MACAddress, - CIDR: netInfo.CIDR, - NetworkName: netInfo.NetworkName, - ProviderId: network.Id(netInfo.ProviderId), - ProviderSubnetId: network.Id(netInfo.ProviderSubnetId), - VLANTag: netInfo.VLANTag, - InterfaceName: netInfo.InterfaceName, - Disabled: netInfo.Disabled, - NoAutoStart: netInfo.NoAutoStart, - ConfigType: network.InterfaceConfigType(netInfo.ConfigType), - Address: network.NewAddress(netInfo.Address), - DNSServers: network.NewAddresses(netInfo.DNSServers...), - GatewayAddress: network.NewAddress(netInfo.GatewayAddress), - ExtraConfig: netInfo.ExtraConfig, + DeviceIndex: cfg.DeviceIndex, + MACAddress: cfg.MACAddress, + CIDR: cfg.CIDR, + MTU: cfg.MTU, + ProviderId: network.Id(cfg.ProviderId), + ProviderSubnetId: network.Id(cfg.ProviderSubnetId), + ProviderSpaceId: network.Id(cfg.ProviderSpaceId), + ProviderVLANId: network.Id(cfg.ProviderVLANId), + ProviderAddressId: network.Id(cfg.ProviderAddressId), + VLANTag: cfg.VLANTag, + InterfaceName: cfg.InterfaceName, + ParentInterfaceName: cfg.ParentInterfaceName, + InterfaceType: network.InterfaceType(cfg.InterfaceType), + Disabled: cfg.Disabled, + NoAutoStart: cfg.NoAutoStart, + ConfigType: network.InterfaceConfigType(cfg.ConfigType), + Address: network.NewAddress(cfg.Address), + DNSServers: network.NewAddresses(cfg.DNSServers...), + DNSSearchDomains: cfg.DNSSearchDomains, + GatewayAddress: network.NewAddress(cfg.GatewayAddress), } } return ifaceInfo, nil diff -Nru charm-2.1.1/src/github.com/juju/juju/api/provisioner/provisioner_test.go charm-2.2.0/src/github.com/juju/juju/api/provisioner/provisioner_test.go --- charm-2.1.1/src/github.com/juju/juju/api/provisioner/provisioner_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/provisioner/provisioner_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -17,6 +17,7 @@ "github.com/juju/utils" "github.com/juju/utils/arch" "github.com/juju/utils/series" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/api" @@ -36,7 +37,7 @@ "github.com/juju/juju/storage/poolmanager" "github.com/juju/juju/storage/provider" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" "github.com/juju/juju/watcher/watchertest" ) @@ -83,6 +84,7 @@ } func (s *provisionerSuite) TestPrepareContainerInterfaceInfoNoFeatureFlag(c *gc.C) { + c.Skip("dimitern: test disabled as no longer relevant in the face of removing address-allocation feature flag") s.SetFeatureFlags() // clear the flag ifaceInfo, err := s.provisioner.PrepareContainerInterfaceInfo(names.NewMachineTag("42")) // We'll still attempt to reserve an address, in case we're running on MAAS @@ -286,58 +288,6 @@ c.Assert(err, jc.ErrorIsNil) c.Assert(ifacesMachine, gc.HasLen, 0) - networks := []params.Network{{ - Tag: "network-net1", - ProviderId: "net1", - CIDR: "0.1.2.0/24", - VLANTag: 0, - }, { - Tag: "network-vlan42", - ProviderId: "vlan42", - CIDR: "0.2.2.0/24", - VLANTag: 42, - }, { - Tag: "network-vlan69", - ProviderId: "vlan69", - CIDR: "0.3.2.0/24", - VLANTag: 69, - }, { - Tag: "network-vlan42", // duplicated; ignored - ProviderId: "vlan42", - CIDR: "0.2.2.0/24", - VLANTag: 42, - }} - ifaces := []params.NetworkInterface{{ - MACAddress: "aa:bb:cc:dd:ee:f0", - NetworkTag: "network-net1", - InterfaceName: "eth0", - IsVirtual: false, - }, { - MACAddress: "aa:bb:cc:dd:ee:f1", - NetworkTag: "network-net1", - InterfaceName: "eth1", - IsVirtual: false, - }, { - MACAddress: "aa:bb:cc:dd:ee:f1", - NetworkTag: "network-vlan42", - InterfaceName: "eth1.42", - IsVirtual: true, - }, { - MACAddress: "aa:bb:cc:dd:ee:f1", - NetworkTag: "network-vlan69", - InterfaceName: "eth1.69", - IsVirtual: true, - }, { - MACAddress: "aa:bb:cc:dd:ee:f1", // duplicated mac+net; ignored - NetworkTag: "network-vlan42", - InterfaceName: "eth2", - IsVirtual: true, - }, { - MACAddress: "aa:bb:cc:dd:ee:f4", - NetworkTag: "network-net1", - InterfaceName: "eth1", // duplicated name+machine id; ignored - IsVirtual: false, - }} volumes := []params.Volume{{ VolumeTag: "volume-1-0", Info: params.VolumeInfo{ @@ -352,7 +302,7 @@ } err = apiMachine.SetInstanceInfo( - "i-will", "fake_nonce", &hwChars, networks, ifaces, volumes, volumeAttachments, + "i-will", "fake_nonce", &hwChars, nil, volumes, volumeAttachments, ) c.Assert(err, jc.ErrorIsNil) @@ -361,7 +311,7 @@ c.Assert(instanceId, gc.Equals, instance.Id("i-will")) // Try it again - should fail. - err = apiMachine.SetInstanceInfo("i-wont", "fake", nil, nil, nil, nil, nil) + err = apiMachine.SetInstanceInfo("i-wont", "fake", nil, nil, nil, nil) c.Assert(err, gc.ErrorMatches, `cannot record provisioning info for "i-wont": cannot set instance data for machine "1": already set`) // Now try to get machine 0's instance id. @@ -371,39 +321,6 @@ c.Assert(err, jc.ErrorIsNil) c.Assert(instanceId, gc.Equals, instance.Id("i-manager")) - // Check the networks are created. - for i := range networks { - if i == 3 { - // Last one was ignored, so skip it. - break - } - tag, err := names.ParseNetworkTag(networks[i].Tag) - c.Assert(err, jc.ErrorIsNil) - networkName := tag.Id() - nw, err := s.State.Network(networkName) - c.Assert(err, jc.ErrorIsNil) - c.Check(nw.Name(), gc.Equals, networkName) - c.Check(nw.ProviderId(), gc.Equals, network.Id(networks[i].ProviderId)) - c.Check(nw.Tag().String(), gc.Equals, networks[i].Tag) - c.Check(nw.VLANTag(), gc.Equals, networks[i].VLANTag) - c.Check(nw.CIDR(), gc.Equals, networks[i].CIDR) - } - - // And the network interfaces as well. - ifacesMachine, err = notProvisionedMachine.NetworkInterfaces() - c.Assert(err, jc.ErrorIsNil) - c.Assert(ifacesMachine, gc.HasLen, 4) - actual := make([]params.NetworkInterface, len(ifacesMachine)) - for i, iface := range ifacesMachine { - actual[i].InterfaceName = iface.InterfaceName() - actual[i].NetworkTag = iface.NetworkTag().String() - actual[i].MACAddress = iface.MACAddress() - actual[i].IsVirtual = iface.IsVirtual() - c.Check(iface.MachineTag(), gc.Equals, notProvisionedMachine.Tag()) - c.Check(iface.MachineId(), gc.Equals, notProvisionedMachine.Id()) - } - c.Assert(actual, jc.SameContents, ifaces[:4]) // skip the rest as they are ignored. - // Now check volumes and volume attachments. volume, err := s.State.Volume(names.NewVolumeTag("1/0")) c.Assert(err, jc.ErrorIsNil) @@ -456,7 +373,7 @@ c.Assert(err, jc.ErrorIsNil) wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) - err = apiMachine.SetInstanceInfo("i-d", "fake", nil, nil, nil, nil, nil) + err = apiMachine.SetInstanceInfo("i-d", "fake", nil, nil, nil, nil) c.Assert(err, jc.ErrorIsNil) instances, err = apiMachine.DistributionGroup() c.Assert(err, jc.ErrorIsNil) @@ -770,7 +687,7 @@ c.Assert(result.ProviderType, gc.Equals, "dummy") c.Assert(result.AuthorizedKeys, gc.Equals, s.Environ.Config().AuthorizedKeys()) c.Assert(result.SSLHostnameVerification, jc.IsTrue) - c.Assert(result.PreferIPv6, jc.IsTrue) + c.Assert(result.PreferIPv6, jc.IsFalse) } func (s *provisionerSuite) TestSetSupportedContainers(c *gc.C) { @@ -819,7 +736,7 @@ func (s *provisionerSuite) testFindTools(c *gc.C, matchArch bool, apiError, logicError error) { current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -836,7 +753,7 @@ called = true c.Assert(request, gc.Equals, "FindTools") expected := params.FindToolsParams{ - Number: version.Current, + Number: jujuversion.Current, Series: series.HostSeries(), Arch: a, MinorVersion: -1, @@ -850,7 +767,7 @@ } return apiError }) - apiList, err := s.provisioner.FindTools(version.Current, series.HostSeries(), a) + apiList, err := s.provisioner.FindTools(jujuversion.Current, series.HostSeries(), a) c.Assert(called, jc.IsTrue) if apiError != nil { c.Assert(err, gc.Equals, apiError) @@ -863,6 +780,7 @@ } func (s *provisionerSuite) TestPrepareContainerInterfaceInfo(c *gc.C) { + c.Skip("dimitern: test disabled as it needs proper fixing in the face of removing address-allocation feature flag") // This test exercises just the success path, all the other cases // are already tested in the apiserver package. template := state.MachineTemplate{ @@ -879,11 +797,11 @@ expectInfo := []network.InterfaceInfo{{ DeviceIndex: 0, CIDR: "0.10.0.0/24", - NetworkName: "juju-private", ProviderId: "dummy-eth0", ProviderSubnetId: "dummy-private", VLANTag: 0, InterfaceName: "eth0", + InterfaceType: "ethernet", Disabled: false, NoAutoStart: false, ConfigType: network.ConfigStatic, diff -Nru charm-2.1.1/src/github.com/juju/juju/api/proxyupdater/proxyupdater.go charm-2.2.0/src/github.com/juju/juju/api/proxyupdater/proxyupdater.go --- charm-2.1.1/src/github.com/juju/juju/api/proxyupdater/proxyupdater.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/proxyupdater/proxyupdater.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,25 +4,87 @@ package proxyupdater import ( + "fmt" + + "github.com/juju/errors" "github.com/juju/juju/api/base" - "github.com/juju/juju/api/common" + apiwatcher "github.com/juju/juju/api/watcher" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/watcher" + "github.com/juju/names" + "github.com/juju/utils/proxy" ) -const apiName = "ProxyUpdater" +const proxyUpdaterFacade = "ProxyUpdater" + +// API provides access to the ProxyUpdater API facade. +type API struct { + tag names.Tag + facade base.FacadeCaller +} + +// NewAPI returns a new api client facade instance. +func NewAPI(caller base.APICaller, tag names.Tag) (*API, error) { + if caller == nil { + return nil, fmt.Errorf("caller is nil") + } + + if tag == nil { + return nil, fmt.Errorf("tag is nil") + } + + return &API{ + facade: base.NewFacadeCaller(caller, proxyUpdaterFacade), + tag: tag, + }, nil +} + +// WatchForProxyConfigAndAPIHostPortChanges returns a NotifyWatcher waiting for +// changes in the proxy configuration or API host ports +func (api *API) WatchForProxyConfigAndAPIHostPortChanges() (watcher.NotifyWatcher, error) { + var results params.NotifyWatchResults + args := params.Entities{ + Entities: []params.Entity{{Tag: api.tag.String()}}, + } + err := api.facade.FacadeCall("WatchForProxyConfigAndAPIHostPortChanges", args, &results) + if err != nil { + return nil, err + } + if len(results.Results) != 1 { + return nil, errors.Errorf("expected 1 result, got %d", len(results.Results)) + } + result := results.Results[0] + if result.Error != nil { + return nil, result.Error + } -// Facade provides access to a machine model worker's view of the world. -type Facade struct { - *common.ModelWatcher + return apiwatcher.NewNotifyWatcher(api.facade.RawAPICaller(), result), nil } -// NewFacade returns a new api client facade instance. -func NewFacade(caller base.APICaller) *Facade { - facadeCaller := base.NewFacadeCaller(caller, apiName) - return &Facade{ - ModelWatcher: common.NewModelWatcher(facadeCaller), +func proxySettingsParamToProxySettings(cfg params.ProxyConfig) proxy.Settings { + return proxy.Settings{ + Http: cfg.HTTP, + Https: cfg.HTTPS, + Ftp: cfg.FTP, + NoProxy: cfg.NoProxy, } } -// TODO(wallyworld) - add methods for getting proxy settings specifically, -// rather than the entire model config. -// Also WatchProxySettings instead of WatchForModelConfigChanges. +// ProxyConfig returns the proxy settings for the current environment +func (api *API) ProxyConfig() (proxySettings, APTProxySettings proxy.Settings, err error) { + var results params.ProxyConfigResults + args := params.Entities{ + Entities: []params.Entity{{Tag: api.tag.String()}}, + } + err = api.facade.FacadeCall("ProxyConfig", args, &results) + if err != nil { + return proxySettings, APTProxySettings, err + } + if len(results.Results) != 1 { + return proxySettings, APTProxySettings, errors.NotFoundf("ProxyConfig for %q", api.tag) + } + result := results.Results[0] + proxySettings = proxySettingsParamToProxySettings(result.ProxySettings) + APTProxySettings = proxySettingsParamToProxySettings(result.APTProxySettings) + return proxySettings, APTProxySettings, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/proxyupdater/proxyupdater_test.go charm-2.2.0/src/github.com/juju/juju/api/proxyupdater/proxyupdater_test.go --- charm-2.1.1/src/github.com/juju/juju/api/proxyupdater/proxyupdater_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/proxyupdater/proxyupdater_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,27 +4,112 @@ package proxyupdater_test import ( + jc "github.com/juju/testing/checkers" + "github.com/juju/utils/proxy" gc "gopkg.in/check.v1" - apitesting "github.com/juju/juju/api/testing" - jujutesting "github.com/juju/juju/juju/testing" + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/proxyupdater" + "github.com/juju/juju/apiserver/params" + coretesting "github.com/juju/juju/testing" + "github.com/juju/juju/worker/workertest" + "github.com/juju/names" ) -type modelSuite struct { - jujutesting.JujuConnSuite - *apitesting.ModelWatcherTests +type ProxyUpdaterSuite struct { + coretesting.BaseSuite } -var _ = gc.Suite(&modelSuite{}) +var _ = gc.Suite(&ProxyUpdaterSuite{}) -func (s *modelSuite) SetUpTest(c *gc.C) { - s.JujuConnSuite.SetUpTest(c) +func newAPI(c *gc.C, args []apitesting.CheckArgs) (*int, *proxyupdater.API) { + var called int + apiCaller := apitesting.CheckingAPICallerMultiArgs(c, args, &called, nil) + api, err := proxyupdater.NewAPI(apiCaller, names.NewUnitTag("u/0")) + c.Assert(err, gc.IsNil) + c.Assert(api, gc.NotNil) + c.Assert(called, gc.Equals, 0) - stateAPI, _ := s.OpenAPIAsNewMachine(c) + return &called, api +} + +func (s *ProxyUpdaterSuite) TestNewAPISuccess(c *gc.C) { + newAPI(c, nil) +} + +func (s *ProxyUpdaterSuite) TestNilAPICallerFails(c *gc.C) { + api, err := proxyupdater.NewAPI(nil, names.NewUnitTag("u/0")) + c.Check(api, gc.IsNil) + c.Check(err, gc.ErrorMatches, "caller is nil") +} + +func (s *ProxyUpdaterSuite) TestNilTagFails(c *gc.C) { + var args []apitesting.CheckArgs + var calls int + apiCaller := apitesting.CheckingAPICallerMultiArgs(c, args, &calls, nil) + api, err := proxyupdater.NewAPI(apiCaller, nil) + c.Check(api, gc.IsNil) + c.Check(err, gc.ErrorMatches, "tag is nil") +} - agentAPI := stateAPI.Agent() - c.Assert(agentAPI, gc.NotNil) +func (s *ProxyUpdaterSuite) TestWatchForProxyConfigAndAPIHostPortChanges(c *gc.C) { + res := params.NotifyWatchResults{ + Results: []params.NotifyWatchResult{params.NotifyWatchResult{ + NotifyWatcherId: "4242", + }}, + } + args := []apitesting.CheckArgs{{ + Facade: "ProxyUpdater", + Method: "WatchForProxyConfigAndAPIHostPortChanges", + Results: res, + }} + called, api := newAPI(c, args) + + watcher, err := api.WatchForProxyConfigAndAPIHostPortChanges() + workertest.CleanKill(c, watcher) + c.Check(*called, jc.GreaterThan, 0) + c.Check(err, jc.ErrorIsNil) +} - s.ModelWatcherTests = apitesting.NewModelWatcherTests( - agentAPI, s.BackingState, apitesting.NoSecrets) +func (s *ProxyUpdaterSuite) TestProxyConfig(c *gc.C) { + conf := params.ProxyConfigResult{ + ProxySettings: params.ProxyConfig{ + HTTP: "http", + HTTPS: "https", + FTP: "ftp", + NoProxy: "NoProxy", + }, + APTProxySettings: params.ProxyConfig{ + HTTP: "http-apt", + HTTPS: "https-apt", + FTP: "ftp-apt", + NoProxy: "NoProxy-apt", + }, + } + expected := params.ProxyConfigResults{ + Results: []params.ProxyConfigResult{conf}, + } + + args := []apitesting.CheckArgs{{ + Facade: "ProxyUpdater", + Method: "ProxyConfig", + Results: expected, + }} + called, api := newAPI(c, args) + + proxySettings, APTProxySettings, err := api.ProxyConfig() + c.Assert(*called, gc.Equals, 1) + c.Assert(err, jc.ErrorIsNil) + c.Check(proxySettings, jc.DeepEquals, proxy.Settings{ + Http: "http", + Https: "https", + Ftp: "ftp", + NoProxy: "NoProxy", + }) + c.Check(APTProxySettings, jc.DeepEquals, proxy.Settings{ + Http: "http-apt", + Https: "https-apt", + Ftp: "ftp-apt", + NoProxy: "NoProxy-apt", + }) } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/service/client.go charm-2.2.0/src/github.com/juju/juju/api/service/client.go --- charm-2.1.1/src/github.com/juju/juju/api/service/client.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/service/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -15,6 +15,7 @@ "github.com/juju/juju/api" "github.com/juju/juju/api/base" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/charmstore" "github.com/juju/juju/constraints" "github.com/juju/juju/instance" "github.com/juju/juju/storage" @@ -61,8 +62,8 @@ // DeployArgs holds the arguments to be sent to Client.ServiceDeploy. type DeployArgs struct { - // CharmURL is the URL of the charm to deploy. - CharmURL string + // CharmID identifies the charm to deploy. + CharmID charmstore.CharmID // ServiceName is the name to give the service. ServiceName string // Series to be used for the machine. @@ -100,7 +101,8 @@ Services: []params.ServiceDeploy{{ ServiceName: args.ServiceName, Series: args.Series, - CharmUrl: args.CharmURL, + CharmUrl: args.CharmID.URL.String(), + Channel: string(args.CharmID.Channel), NumUnits: args.NumUnits, ConfigYAML: args.ConfigYAML, Constraints: args.Cons, @@ -140,8 +142,8 @@ type SetCharmConfig struct { // ServiceName is the name of the service to set the charm on. ServiceName string - // CharmUrl is the url for the charm. - CharmUrl string + // CharmID identifies the charm. + CharmID charmstore.CharmID // ForceSeries forces the use of the charm even if it doesn't match the // series of the unit. ForceSeries bool @@ -156,7 +158,8 @@ func (c *Client) SetCharm(cfg SetCharmConfig) error { args := params.ServiceSetCharm{ ServiceName: cfg.ServiceName, - CharmUrl: cfg.CharmUrl, + CharmUrl: cfg.CharmID.URL.String(), + Channel: string(cfg.CharmID.Channel), ForceSeries: cfg.ForceSeries, ForceUnits: cfg.ForceUnits, ResourceIDs: cfg.ResourceIDs, diff -Nru charm-2.1.1/src/github.com/juju/juju/api/service/client_test.go charm-2.2.0/src/github.com/juju/juju/api/service/client_test.go --- charm-2.1.1/src/github.com/juju/juju/api/service/client_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/service/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,6 +11,7 @@ "github.com/juju/juju/api/service" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/charmstore" "github.com/juju/juju/constraints" "github.com/juju/juju/instance" jujutesting "github.com/juju/juju/juju/testing" @@ -83,7 +84,7 @@ args, ok := a.(params.ServicesDeploy) c.Assert(ok, jc.IsTrue) c.Assert(args.Services, gc.HasLen, 1) - c.Assert(args.Services[0].CharmUrl, gc.Equals, "charmURL") + c.Assert(args.Services[0].CharmUrl, gc.Equals, "cs:trusty/a-charm-1") c.Assert(args.Services[0].ServiceName, gc.Equals, "serviceA") c.Assert(args.Services[0].Series, gc.Equals, "series") c.Assert(args.Services[0].NumUnits, gc.Equals, 2) @@ -100,7 +101,9 @@ }) args := service.DeployArgs{ - CharmURL: "charmURL", + CharmID: charmstore.CharmID{ + URL: charm.MustParseURL("trusty/a-charm-1"), + }, ServiceName: "serviceA", Series: "series", NumUnits: 2, @@ -144,14 +147,16 @@ args, ok := a.(params.ServiceSetCharm) c.Assert(ok, jc.IsTrue) c.Assert(args.ServiceName, gc.Equals, "service") - c.Assert(args.CharmUrl, gc.Equals, "charmURL") + c.Assert(args.CharmUrl, gc.Equals, "cs:trusty/service-1") c.Assert(args.ForceSeries, gc.Equals, true) c.Assert(args.ForceUnits, gc.Equals, true) return nil }) cfg := service.SetCharmConfig{ ServiceName: "service", - CharmUrl: "charmURL", + CharmID: charmstore.CharmID{ + URL: charm.MustParseURL("trusty/service-1"), + }, ForceSeries: true, ForceUnits: true, } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/servicescaler/api.go charm-2.2.0/src/github.com/juju/juju/api/servicescaler/api.go --- charm-2.1.1/src/github.com/juju/juju/api/servicescaler/api.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/servicescaler/api.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,79 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler + +import ( + "github.com/juju/errors" + "github.com/juju/loggo" + "github.com/juju/names" + + "github.com/juju/juju/api/base" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/watcher" +) + +var logger = loggo.GetLogger("juju.api.servicescaler") + +// NewWatcherFunc exists to let us test Watch properly. +type NewWatcherFunc func(base.APICaller, params.StringsWatchResult) watcher.StringsWatcher + +// API makes calls to the ServiceScaler facade. +type API struct { + caller base.FacadeCaller + newWatcher NewWatcherFunc +} + +// NewAPI returns a new API using the supplied caller. +func NewAPI(caller base.APICaller, newWatcher NewWatcherFunc) *API { + return &API{ + caller: base.NewFacadeCaller(caller, "ServiceScaler"), + newWatcher: newWatcher, + } +} + +// Watch returns a StringsWatcher that delivers the names of services +// that may need to be rescaled. +func (api *API) Watch() (watcher.StringsWatcher, error) { + var result params.StringsWatchResult + err := api.caller.FacadeCall("Watch", nil, &result) + if err != nil { + return nil, errors.Trace(err) + } + if result.Error != nil { + return nil, errors.Trace(result.Error) + } + w := api.newWatcher(api.caller.RawAPICaller(), result) + return w, nil +} + +// Rescale requests that all supplied service names be rescaled to +// their minimum configured sizes. It returns the first error it +// encounters. +func (api *API) Rescale(services []string) error { + args := params.Entities{ + Entities: make([]params.Entity, len(services)), + } + for i, service := range services { + if !names.IsValidService(service) { + return errors.NotValidf("service name %q", service) + } + tag := names.NewServiceTag(service) + args.Entities[i].Tag = tag.String() + } + var results params.ErrorResults + err := api.caller.FacadeCall("Rescale", args, &results) + if err != nil { + return errors.Trace(err) + } + for _, result := range results.Results { + if result.Error != nil { + if err == nil { + err = result.Error + } else { + logger.Errorf("additional rescale error: %v", err) + } + } + } + return errors.Trace(err) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/servicescaler/api_test.go charm-2.2.0/src/github.com/juju/juju/api/servicescaler/api_test.go --- charm-2.1.1/src/github.com/juju/juju/api/servicescaler/api_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/servicescaler/api_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,172 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler_test + +import ( + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/api/base" + apitesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/api/servicescaler" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/watcher" +) + +type APISuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&APISuite{}) + +func (s *APISuite) TestRescaleMethodName(c *gc.C) { + var called bool + caller := apiCaller(c, func(request string, _, _ interface{}) error { + called = true + c.Check(request, gc.Equals, "Rescale") + return nil + }) + api := servicescaler.NewAPI(caller, nil) + + api.Rescale(nil) + c.Check(called, jc.IsTrue) +} + +func (s *APISuite) TestRescaleBadArgs(c *gc.C) { + caller := apiCaller(c, func(_ string, _, _ interface{}) error { + panic("should not be called") + }) + api := servicescaler.NewAPI(caller, nil) + + err := api.Rescale([]string{"good-name", "bad/name"}) + c.Check(err, gc.ErrorMatches, `service name "bad/name" not valid`) + c.Check(err, jc.Satisfies, errors.IsNotValid) +} + +func (s *APISuite) TestRescaleConvertArgs(c *gc.C) { + var called bool + caller := apiCaller(c, func(_ string, arg, _ interface{}) error { + called = true + c.Check(arg, gc.DeepEquals, params.Entities{ + Entities: []params.Entity{{ + "service-foo", + }, { + "service-bar-baz", + }}, + }) + return nil + }) + api := servicescaler.NewAPI(caller, nil) + + api.Rescale([]string{"foo", "bar-baz"}) + c.Check(called, jc.IsTrue) +} + +func (s *APISuite) TestRescaleCallError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, _ interface{}) error { + return errors.New("snorble flip") + }) + api := servicescaler.NewAPI(caller, nil) + + err := api.Rescale(nil) + c.Check(err, gc.ErrorMatches, "snorble flip") +} + +func (s *APISuite) TestRescaleFirstError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, result interface{}) error { + resultPtr, ok := result.(*params.ErrorResults) + c.Assert(ok, jc.IsTrue) + *resultPtr = params.ErrorResults{Results: []params.ErrorResult{{ + nil, + }, { + ¶ms.Error{Message: "expect this error"}, + }, { + ¶ms.Error{Message: "not this one"}, + }, { + nil, + }}} + return nil + }) + api := servicescaler.NewAPI(caller, nil) + + err := api.Rescale(nil) + c.Check(err, gc.ErrorMatches, "expect this error") +} + +func (s *APISuite) TestRescaleNoError(c *gc.C) { + caller := apiCaller(c, func(_ string, _, _ interface{}) error { + return nil + }) + api := servicescaler.NewAPI(caller, nil) + + err := api.Rescale(nil) + c.Check(err, jc.ErrorIsNil) +} + +func (s *APISuite) TestWatchMethodName(c *gc.C) { + var called bool + caller := apiCaller(c, func(request string, _, _ interface{}) error { + called = true + c.Check(request, gc.Equals, "Watch") + return errors.New("irrelevant") + }) + api := servicescaler.NewAPI(caller, nil) + + api.Watch() + c.Check(called, jc.IsTrue) +} + +func (s *APISuite) TestWatchError(c *gc.C) { + var called bool + caller := apiCaller(c, func(request string, _, _ interface{}) error { + called = true + c.Check(request, gc.Equals, "Watch") + return errors.New("blam pow") + }) + api := servicescaler.NewAPI(caller, nil) + + watcher, err := api.Watch() + c.Check(watcher, gc.IsNil) + c.Check(err, gc.ErrorMatches, "blam pow") + c.Check(called, jc.IsTrue) +} + +func (s *APISuite) TestWatchSuccess(c *gc.C) { + expectResult := params.StringsWatchResult{ + StringsWatcherId: "123", + Changes: []string{"ping", "pong", "pung"}, + } + caller := apiCaller(c, func(_ string, _, result interface{}) error { + resultPtr, ok := result.(*params.StringsWatchResult) + c.Assert(ok, jc.IsTrue) + *resultPtr = expectResult + return nil + }) + expectWatcher := &stubWatcher{} + newWatcher := func(gotCaller base.APICaller, gotResult params.StringsWatchResult) watcher.StringsWatcher { + c.Check(gotCaller, gc.NotNil) // uncomparable + c.Check(gotResult, jc.DeepEquals, expectResult) + return expectWatcher + } + api := servicescaler.NewAPI(caller, newWatcher) + + watcher, err := api.Watch() + c.Check(watcher, gc.Equals, expectWatcher) + c.Check(err, jc.ErrorIsNil) +} + +func apiCaller(c *gc.C, check func(request string, arg, result interface{}) error) base.APICaller { + return apitesting.APICallerFunc(func(facade string, version int, id, request string, arg, result interface{}) error { + c.Check(facade, gc.Equals, "ServiceScaler") + c.Check(version, gc.Equals, 0) + c.Check(id, gc.Equals, "") + return check(request, arg, result) + }) +} + +type stubWatcher struct { + watcher.StringsWatcher +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/servicescaler/package_test.go charm-2.2.0/src/github.com/juju/juju/api/servicescaler/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/servicescaler/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/servicescaler/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/state.go charm-2.2.0/src/github.com/juju/juju/api/state.go --- charm-2.1.1/src/github.com/juju/juju/api/state.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/state.go 2016-04-28 06:03:34.000000000 +0000 @@ -9,20 +9,19 @@ "github.com/juju/errors" "github.com/juju/names" + "github.com/juju/version" "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" "github.com/juju/juju/api/addresser" - "github.com/juju/juju/api/agent" "github.com/juju/juju/api/base" "github.com/juju/juju/api/charmrevisionupdater" "github.com/juju/juju/api/cleaner" - "github.com/juju/juju/api/deployer" "github.com/juju/juju/api/discoverspaces" "github.com/juju/juju/api/firewaller" "github.com/juju/juju/api/imagemetadata" "github.com/juju/juju/api/instancepoller" "github.com/juju/juju/api/keyupdater" - "github.com/juju/juju/api/machiner" "github.com/juju/juju/api/provisioner" "github.com/juju/juju/api/reboot" "github.com/juju/juju/api/unitassigner" @@ -30,37 +29,40 @@ "github.com/juju/juju/api/upgrader" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/network" - "github.com/juju/juju/version" ) -// Login authenticates as the entity with the given name and password. -// Subsequent requests on the state will act as that entity. This -// method is usually called automatically by Open. The machine nonce +// Login authenticates as the entity with the given name and password +// or macaroons. Subsequent requests on the state will act as that entity. +// This method is usually called automatically by Open. The machine nonce // should be empty unless logging in as a machine agent. -func (st *state) Login(tag names.Tag, password, nonce string) error { - err := st.loginV3(tag, password, nonce) +func (st *state) Login(tag names.Tag, password, nonce string, ms []macaroon.Slice) error { + err := st.loginV3(tag, password, nonce, ms) return errors.Trace(err) } // loginV2 is retained for testing logins from older clients. -func (st *state) loginV2(tag names.Tag, password, nonce string) error { - return st.loginForVersion(tag, password, nonce, 2) +func (st *state) loginV2(tag names.Tag, password, nonce string, ms []macaroon.Slice) error { + return st.loginForVersion(tag, password, nonce, ms, 2) } -func (st *state) loginV3(tag names.Tag, password, nonce string) error { - return st.loginForVersion(tag, password, nonce, 3) +func (st *state) loginV3(tag names.Tag, password, nonce string, ms []macaroon.Slice) error { + return st.loginForVersion(tag, password, nonce, ms, 3) } -func (st *state) loginForVersion(tag names.Tag, password, nonce string, vers int) error { +func (st *state) loginForVersion(tag names.Tag, password, nonce string, macaroons []macaroon.Slice, vers int) error { var result params.LoginResultV1 request := ¶ms.LoginRequest{ AuthTag: tagToString(tag), Credentials: password, Nonce: nonce, + Macaroons: macaroons, } if tag == nil { - // Add any macaroons that might work for authenticating the login request. - request.Macaroons = httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL) + // Add any macaroons from the cookie jar that might work for + // authenticating the login request. + request.Macaroons = append(request.Macaroons, + httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL)..., + ) } err := st.APICall("Admin", vers, "", "Login", request, &result) if err != nil { @@ -180,12 +182,6 @@ return &Client{ClientFacade: frontend, facade: backend, st: st} } -// Machiner returns a version of the state that provides functionality -// required by the machiner worker. -func (st *state) Machiner() *machiner.State { - return machiner.NewState(st) -} - // UnitAssigner returns a version of the state that provides functionality // required by the unitassigner worker. func (st *state) UnitAssigner() unitassigner.API { @@ -214,12 +210,6 @@ return firewaller.NewState(st) } -// Agent returns a version of the state that provides -// functionality required by the agent code. -func (st *state) Agent() *agent.State { - return agent.NewState(st) -} - // Upgrader returns access to the Upgrader API func (st *state) Upgrader() *upgrader.State { return upgrader.NewState(st) @@ -235,11 +225,6 @@ } } -// Deployer returns access to the Deployer API -func (st *state) Deployer() *deployer.State { - return deployer.NewState(st) -} - // Addresser returns access to the Addresser API. func (st *state) Addresser() *addresser.API { return addresser.NewAPI(st) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/state_macaroon_test.go charm-2.2.0/src/github.com/juju/juju/api/state_macaroon_test.go --- charm-2.1.1/src/github.com/juju/juju/api/state_macaroon_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/state_macaroon_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -28,8 +28,7 @@ s.MacaroonSuite.SetUpTest(c) s.AddModelUser(c, testUserName) info := s.APIInfo(c) - // Don't log in. - info.UseMacaroons = false + info.SkipLogin = true s.client = s.OpenAPI(c, info, nil) } @@ -40,12 +39,12 @@ func (s *macaroonLoginSuite) TestSuccessfulLogin(c *gc.C) { s.DischargerLogin = func() string { return testUserName } - err := s.client.Login(nil, "", "") + err := s.client.Login(nil, "", "", nil) c.Assert(err, jc.ErrorIsNil) } func (s *macaroonLoginSuite) TestFailedToObtainDischargeLogin(c *gc.C) { - err := s.client.Login(nil, "", "") + err := s.client.Login(nil, "", "", nil) c.Assert(err, gc.ErrorMatches, `cannot get discharge from "https://.*": third party refused discharge: cannot discharge: login denied by discharger`) } @@ -53,7 +52,7 @@ s.DischargerLogin = func() string { return "testUnknown" } - err := s.client.Login(nil, "", "") + err := s.client.Login(nil, "", "", nil) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: "invalid entity name or password", Code: "unauthorized access", @@ -69,7 +68,7 @@ return testUserName } // First log into the regular API. - err := s.client.Login(nil, "", "") + err := s.client.Login(nil, "", "", nil) c.Assert(err, jc.ErrorIsNil) c.Assert(dischargeCount, gc.Equals, 1) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/state_test.go charm-2.2.0/src/github.com/juju/juju/api/state_test.go --- charm-2.1.1/src/github.com/juju/juju/api/state_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/state_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -9,8 +9,10 @@ "github.com/juju/names" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "gopkg.in/macaroon.v1" "github.com/juju/juju/api" + "github.com/juju/juju/api/usermanager" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/network" coretesting "github.com/juju/juju/testing" @@ -47,6 +49,8 @@ password := info.Password info.Tag = nil info.Password = "" + info.Macaroons = nil + info.SkipLogin = true apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, jc.ErrorIsNil) return apistate, tag, password @@ -82,7 +86,7 @@ modelTag, err := apistate.ModelTag() c.Check(err, gc.ErrorMatches, `"" is not a valid tag`) c.Check(modelTag, gc.Equals, names.ModelTag{}) - err = apistate.Login(tag, password, "") + err = apistate.Login(tag, password, "", nil) c.Assert(err, jc.ErrorIsNil) // Now that we've logged in, ModelTag should be updated correctly. modelTag, err = apistate.ModelTag() @@ -95,12 +99,41 @@ c.Check(controllerTag, gc.Equals, env.ModelTag()) } +func (s *stateSuite) TestLoginMacaroon(c *gc.C) { + apistate, tag, _ := s.OpenAPIWithoutLogin(c) + defer apistate.Close() + // Use s.APIState, because we can't get at UserManager without logging in. + mac, err := usermanager.NewClient(s.APIState).CreateLocalLoginMacaroon(tag.(names.UserTag)) + c.Assert(err, jc.ErrorIsNil) + err = apistate.Login(tag, "", "", []macaroon.Slice{{mac}}) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *stateSuite) TestLoginMacaroonInvalidId(c *gc.C) { + apistate, tag, _ := s.OpenAPIWithoutLogin(c) + defer apistate.Close() + mac, err := macaroon.New([]byte("root-key"), "id", "juju") + c.Assert(err, jc.ErrorIsNil) + err = apistate.Login(tag, "", "", []macaroon.Slice{{mac}}) + c.Assert(err, gc.ErrorMatches, "invalid entity name or password \\(unauthorized access\\)") +} + +func (s *stateSuite) TestLoginMacaroonInvalidUser(c *gc.C) { + apistate, tag, _ := s.OpenAPIWithoutLogin(c) + defer apistate.Close() + // Use s.APIState, because we can't get at UserManager without logging in. + mac, err := usermanager.NewClient(s.APIState).CreateLocalLoginMacaroon(tag.(names.UserTag)) + c.Assert(err, jc.ErrorIsNil) + err = apistate.Login(names.NewUserTag("bob@local"), "", "", []macaroon.Slice{{mac}}) + c.Assert(err, gc.ErrorMatches, "invalid entity name or password \\(unauthorized access\\)") +} + func (s *stateSuite) TestLoginTracksFacadeVersions(c *gc.C) { apistate, tag, password := s.OpenAPIWithoutLogin(c) defer apistate.Close() // We haven't called Login yet, so the Facade Versions should be empty c.Check(apistate.AllFacadeVersions(), gc.HasLen, 0) - err := apistate.Login(tag, password, "") + err := apistate.Login(tag, password, "", nil) c.Assert(err, jc.ErrorIsNil) // Now that we've logged in, AllFacadeVersions should be updated. allVersions := apistate.AllFacadeVersions() diff -Nru charm-2.1.1/src/github.com/juju/juju/api/testing/macaroonsuite.go charm-2.2.0/src/github.com/juju/juju/api/testing/macaroonsuite.go --- charm-2.1.1/src/github.com/juju/juju/api/testing/macaroonsuite.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/testing/macaroonsuite.go 2016-04-28 06:03:34.000000000 +0000 @@ -100,7 +100,6 @@ info := s.JujuConnSuite.APIInfo(c) info.Tag = nil info.Password = "" - info.UseMacaroons = true return info } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/undertaker/package_test.go charm-2.2.0/src/github.com/juju/juju/api/undertaker/package_test.go --- charm-2.1.1/src/github.com/juju/juju/api/undertaker/package_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/undertaker/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,16 +7,8 @@ "testing" gc "gopkg.in/check.v1" - - coretesting "github.com/juju/juju/testing" ) func TestAll(t *testing.T) { gc.TestingT(t) } - -type undertakerSuite struct { - coretesting.BaseSuite -} - -var _ = gc.Suite(&undertakerSuite{}) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/undertaker/undertaker.go charm-2.2.0/src/github.com/juju/juju/api/undertaker/undertaker.go --- charm-2.1.1/src/github.com/juju/juju/api/undertaker/undertaker.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/undertaker/undertaker.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,88 +5,70 @@ import ( "github.com/juju/errors" + "github.com/juju/names" "github.com/juju/juju/api/base" - apiwatcher "github.com/juju/juju/api/watcher" "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/environs/config" "github.com/juju/juju/watcher" ) +// NewWatcherFunc exists to let us test Watch properly. +type NewWatcherFunc func(base.APICaller, params.NotifyWatchResult) watcher.NotifyWatcher + // Client provides access to the undertaker API type Client struct { - base.ClientFacade - st base.APICallCloser - facade base.FacadeCaller -} - -// UndertakerClient defines the methods on the undertaker API end point. -type UndertakerClient interface { - ModelInfo() (params.UndertakerModelInfoResult, error) - ProcessDyingModel() error - RemoveModel() error - WatchModelResources() (watcher.NotifyWatcher, error) - ModelConfig() (*config.Config, error) + modelTag names.ModelTag + caller base.FacadeCaller + newWatcher NewWatcherFunc } // NewClient creates a new client for accessing the undertaker API. -func NewClient(st base.APICallCloser) *Client { - frontend, backend := base.NewClientFacade(st, "Undertaker") - return &Client{ClientFacade: frontend, st: st, facade: backend} +func NewClient(caller base.APICaller, newWatcher NewWatcherFunc) (*Client, error) { + modelTag, err := caller.ModelTag() + if err != nil { + return nil, errors.Trace(err) + } + return &Client{ + modelTag: modelTag, + caller: base.NewFacadeCaller(caller, "Undertaker"), + newWatcher: newWatcher, + }, nil } // ModelInfo returns information on the model needed by the undertaker worker. func (c *Client) ModelInfo() (params.UndertakerModelInfoResult, error) { result := params.UndertakerModelInfoResult{} - p, err := c.params() - if err != nil { - return params.UndertakerModelInfoResult{}, errors.Trace(err) - } - err = c.facade.FacadeCall("ModelInfo", p, &result) + err := c.facadeCall("ModelInfo", &result) return result, errors.Trace(err) } // ProcessDyingModel checks if a dying model has any machines or services. // If there are none, the model's life is changed from dying to dead. func (c *Client) ProcessDyingModel() error { - p, err := c.params() - if err != nil { - return errors.Trace(err) - } - - return c.facade.FacadeCall("ProcessDyingModel", p, nil) + return c.facadeCall("ProcessDyingModel", nil) } // RemoveModel removes any records of this model from Juju. func (c *Client) RemoveModel() error { - p, err := c.params() - if err != nil { - return errors.Trace(err) - } - return c.facade.FacadeCall("RemoveModel", p, nil) + return c.facadeCall("RemoveModel", nil) } -func (c *Client) params() (params.Entities, error) { - modelTag, err := c.st.ModelTag() - if err != nil { - return params.Entities{}, errors.Trace(err) +func (c *Client) facadeCall(name string, results interface{}) error { + args := params.Entities{ + Entities: []params.Entity{{c.modelTag.String()}}, } - return params.Entities{Entities: []params.Entity{{modelTag.String()}}}, nil + return c.caller.FacadeCall(name, args, results) } // WatchModelResources starts a watcher for changes to the model's // machines and services. func (c *Client) WatchModelResources() (watcher.NotifyWatcher, error) { var results params.NotifyWatchResults - - p, err := c.params() - if err != nil { - return nil, errors.Trace(err) - } - err = c.facade.FacadeCall("WatchModelResources", p, &results) + err := c.facadeCall("WatchModelResources", &results) if err != nil { return nil, err } + if len(results.Results) != 1 { return nil, errors.Errorf("expected 1 result, got %d", len(results.Results)) } @@ -94,25 +76,6 @@ if result.Error != nil { return nil, result.Error } - w := apiwatcher.NewNotifyWatcher(c.facade.RawAPICaller(), result) + w := c.newWatcher(c.caller.RawAPICaller(), result) return w, nil } - -// ModelConfig returns configuration information on the model needed -// by the undertaker worker. -func (c *Client) ModelConfig() (*config.Config, error) { - p, err := c.params() - if err != nil { - return nil, errors.Trace(err) - } - var result params.ModelConfigResult - err = c.facade.FacadeCall("ModelConfig", p, &result) - if err != nil { - return nil, err - } - conf, err := config.New(config.NoDefaults, result.Config) - if err != nil { - return nil, err - } - return conf, nil -} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/undertaker/undertaker_test.go charm-2.2.0/src/github.com/juju/juju/api/undertaker/undertaker_test.go --- charm-2.1.1/src/github.com/juju/juju/api/undertaker/undertaker_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/undertaker/undertaker_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,20 +4,25 @@ package undertaker_test import ( - "time" - + "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/api/base" basetesting "github.com/juju/juju/api/base/testing" "github.com/juju/juju/api/undertaker" "github.com/juju/juju/apiserver/params" coretesting "github.com/juju/juju/testing" + "github.com/juju/juju/watcher" ) -var _ undertaker.UndertakerClient = (*undertaker.Client)(nil) +type UndertakerSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&UndertakerSuite{}) -func (s *undertakerSuite) TestEnvironInfo(c *gc.C) { +func (s *UndertakerSuite) TestEnvironInfo(c *gc.C) { var called bool client := s.mockClient(c, "ModelInfo", func(response interface{}) { called = true @@ -31,7 +36,7 @@ c.Assert(result, gc.Equals, params.UndertakerModelInfoResult{}) } -func (s *undertakerSuite) TestProcessDyingEnviron(c *gc.C) { +func (s *UndertakerSuite) TestProcessDyingEnviron(c *gc.C) { var called bool client := s.mockClient(c, "ProcessDyingModel", func(response interface{}) { called = true @@ -42,7 +47,7 @@ c.Assert(called, jc.IsTrue) } -func (s *undertakerSuite) TestRemoveModel(c *gc.C) { +func (s *UndertakerSuite) TestRemoveModel(c *gc.C) { var called bool client := s.mockClient(c, "RemoveModel", func(response interface{}) { called = true @@ -54,72 +59,74 @@ c.Assert(called, jc.IsTrue) } -func (s *undertakerSuite) mockClient(c *gc.C, expectedRequest string, callback func(response interface{})) *undertaker.Client { - apiCaller := basetesting.APICallerFunc( - func(objType string, - version int, - id, request string, - args, response interface{}, - ) error { - c.Check(objType, gc.Equals, "Undertaker") - c.Check(id, gc.Equals, "") - c.Check(request, gc.Equals, expectedRequest) - - a, ok := args.(params.Entities) - c.Check(ok, jc.IsTrue) - c.Check(a.Entities, gc.DeepEquals, []params.Entity{{Tag: coretesting.ModelTag.String()}}) - - callback(response) - return nil - }) +func (s *UndertakerSuite) mockClient(c *gc.C, expectedRequest string, callback func(response interface{})) *undertaker.Client { + apiCaller := basetesting.APICallerFunc(func( + objType string, + version int, + id, request string, + args, response interface{}, + ) error { + c.Check(objType, gc.Equals, "Undertaker") + c.Check(id, gc.Equals, "") + c.Check(request, gc.Equals, expectedRequest) + + a, ok := args.(params.Entities) + c.Check(ok, jc.IsTrue) + c.Check(a.Entities, gc.DeepEquals, []params.Entity{{Tag: coretesting.ModelTag.String()}}) - return undertaker.NewClient(apiCaller) + callback(response) + return nil + }) + client, err := undertaker.NewClient(apiCaller, nil) + c.Assert(err, jc.ErrorIsNil) + return client } -func (s *undertakerSuite) TestWatchModelResourcesGetsChange(c *gc.C) { - apiCaller := basetesting.APICallerFunc( - func(objType string, - version int, - id, request string, - args, response interface{}, - ) error { - if resp, ok := response.(*params.NotifyWatchResults); ok { - c.Check(objType, gc.Equals, "Undertaker") - c.Check(id, gc.Equals, "") - c.Check(request, gc.Equals, "WatchModelResources") - - a, ok := args.(params.Entities) - c.Check(ok, jc.IsTrue) - c.Check(a.Entities, gc.DeepEquals, []params.Entity{{Tag: coretesting.ModelTag.String()}}) - - resp.Results = []params.NotifyWatchResult{{NotifyWatcherId: "1"}} - } else { - c.Check(objType, gc.Equals, "NotifyWatcher") - c.Check(id, gc.Equals, "1") - c.Check(request, gc.Equals, "Next") - } - return nil +func (s *UndertakerSuite) TestWatchModelResourcesCreatesWatcher(c *gc.C) { + apiCaller := basetesting.APICallerFunc(func( + objType string, + version int, + id, request string, + args, response interface{}, + ) error { + c.Check(objType, gc.Equals, "Undertaker") + c.Check(id, gc.Equals, "") + c.Check(request, gc.Equals, "WatchModelResources") + + a, ok := args.(params.Entities) + c.Check(ok, jc.IsTrue) + c.Check(a.Entities, gc.DeepEquals, []params.Entity{{Tag: coretesting.ModelTag.String()}}) + + resp, ok := response.(*params.NotifyWatchResults) + c.Assert(ok, jc.IsTrue) + resp.Results = []params.NotifyWatchResult{{ + NotifyWatcherId: "1001", + }} + return nil + }) + + expectWatcher := &fakeWatcher{} + newWatcher := func(apiCaller base.APICaller, result params.NotifyWatchResult) watcher.NotifyWatcher { + c.Check(apiCaller, gc.NotNil) // uncomparable + c.Check(result, gc.Equals, params.NotifyWatchResult{ + NotifyWatcherId: "1001", }) + return expectWatcher + } - client := undertaker.NewClient(apiCaller) + client, err := undertaker.NewClient(apiCaller, newWatcher) + c.Assert(err, jc.ErrorIsNil) w, err := client.WatchModelResources() c.Assert(err, jc.ErrorIsNil) - - select { - case <-w.Changes(): - case <-time.After(coretesting.ShortWait): - c.Fatalf("timed out waiting for change") - } + c.Check(w, gc.Equals, expectWatcher) } -func (s *undertakerSuite) TestWatchModelResourcesError(c *gc.C) { +func (s *UndertakerSuite) TestWatchModelResourcesError(c *gc.C) { var called bool - - // The undertaker feature tests ensure WatchModelResources is connected - // correctly end to end. This test just ensures that the API calls work. client := s.mockClient(c, "WatchModelResources", func(response interface{}) { called = true - c.Check(response, gc.DeepEquals, ¶ms.NotifyWatchResults{Results: []params.NotifyWatchResult(nil)}) + _, ok := response.(*params.NotifyWatchResults) + c.Check(ok, jc.IsTrue) }) w, err := client.WatchModelResources() @@ -128,18 +135,6 @@ c.Assert(called, jc.IsTrue) } -func (s *undertakerSuite) TestModelConfig(c *gc.C) { - var called bool - - // The undertaker feature tests ensure ModelConfig is connected - // correctly end to end. This test just ensures that the API calls work. - client := s.mockClient(c, "ModelConfig", func(response interface{}) { - called = true - c.Check(response, gc.DeepEquals, ¶ms.ModelConfigResult{Config: params.ModelConfig(nil)}) - }) - - // We intentionally don't test the error here. We are only interested that - // the ModelConfig endpoint was called. - client.ModelConfig() - c.Assert(called, jc.IsTrue) +type fakeWatcher struct { + watcher.NotifyWatcher } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/uniter/action_test.go charm-2.2.0/src/github.com/juju/juju/api/uniter/action_test.go --- charm-2.1.1/src/github.com/juju/juju/api/uniter/action_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/uniter/action_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -82,7 +82,7 @@ func (s *actionSuite) TestActionComplete(c *gc.C) { completed, err := s.uniterSuite.wordpressUnit.CompletedActions() c.Assert(err, jc.ErrorIsNil) - c.Assert(completed, gc.DeepEquals, ([]*state.Action)(nil)) + c.Assert(completed, gc.DeepEquals, ([]state.Action)(nil)) action, err := s.uniterSuite.wordpressUnit.AddAction("fakeaction", nil) c.Assert(err, jc.ErrorIsNil) @@ -104,7 +104,7 @@ func (s *actionSuite) TestActionFail(c *gc.C) { completed, err := s.uniterSuite.wordpressUnit.CompletedActions() c.Assert(err, jc.ErrorIsNil) - c.Assert(completed, gc.DeepEquals, ([]*state.Action)(nil)) + c.Assert(completed, gc.DeepEquals, ([]state.Action)(nil)) action, err := s.uniterSuite.wordpressUnit.AddAction("fakeaction", nil) c.Assert(err, jc.ErrorIsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/uniter/relationunit.go charm-2.2.0/src/github.com/juju/juju/api/uniter/relationunit.go --- charm-2.1.1/src/github.com/juju/juju/api/uniter/relationunit.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/uniter/relationunit.go 2016-04-28 06:03:34.000000000 +0000 @@ -167,30 +167,3 @@ func (ru *RelationUnit) Watch() (watcher.RelationUnitsWatcher, error) { return ru.st.WatchRelationUnits(ru.relation.tag, ru.unit.tag) } - -// NetworkConfig requests network config information from the server. -func (ru *RelationUnit) NetworkConfig() ([]params.NetworkConfig, error) { - var results params.UnitNetworkConfigResults - args := params.RelationUnits{ - RelationUnits: []params.RelationUnit{{ - Relation: ru.relation.tag.String(), - Unit: ru.unit.tag.String(), - }}, - } - - err := ru.st.facade.FacadeCall("NetworkConfig", args, &results) - if err != nil { - return nil, errors.Trace(err) - } - - if len(results.Results) != 1 { - return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) - } - - result := results.Results[0] - if result.Error != nil { - return nil, result.Error - } - - return result.Config, nil -} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/uniter/relationunit_test.go charm-2.2.0/src/github.com/juju/juju/api/uniter/relationunit_test.go --- charm-2.1.1/src/github.com/juju/juju/api/uniter/relationunit_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/uniter/relationunit_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -74,29 +74,6 @@ c.Assert(apiRel.String(), gc.Equals, "wordpress:db mysql:server") } -func (s *relationUnitSuite) TestNetworkConfig(c *gc.C) { - // Set some provider addresses bound to both "public" and "internal" - // spaces. - addresses := []network.Address{ - network.NewAddressOnSpace("public", "8.8.8.8"), - network.NewAddressOnSpace("", "8.8.4.4"), - network.NewAddressOnSpace("internal", "10.0.0.1"), - network.NewAddressOnSpace("internal", "10.0.0.2"), - network.NewAddressOnSpace("public", "fc00::1"), - } - err := s.wordpressMachine.SetProviderAddresses(addresses...) - c.Assert(err, jc.ErrorIsNil) - - _, apiRelUnit := s.getRelationUnits(c) - - netConfig, err := apiRelUnit.NetworkConfig() - c.Assert(err, jc.ErrorIsNil) - c.Assert(netConfig, jc.DeepEquals, []params.NetworkConfig{ - {Address: "10.0.0.1"}, - {Address: "10.0.0.2"}, - }) -} - func (s *relationUnitSuite) TestEndpoint(c *gc.C) { _, apiRelUnit := s.getRelationUnits(c) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/uniter/uniter.go charm-2.2.0/src/github.com/juju/juju/api/uniter/uniter.go --- charm-2.1.1/src/github.com/juju/juju/api/uniter/uniter.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/uniter/uniter.go 2016-04-28 06:03:34.000000000 +0000 @@ -115,8 +115,8 @@ } // getOneAction retrieves a single Action from the controller. -func (st *State) getOneAction(tag *names.ActionTag) (params.ActionsQueryResult, error) { - nothing := params.ActionsQueryResult{} +func (st *State) getOneAction(tag *names.ActionTag) (params.ActionResult, error) { + nothing := params.ActionResult{} args := params.Entities{ Entities: []params.Entity{ @@ -124,7 +124,7 @@ }, } - var results params.ActionsQueryResults + var results params.ActionResults err := st.facade.FacadeCall("Actions", args, &results) if err != nil { return nothing, err @@ -217,8 +217,8 @@ return nil, err } return &Action{ - name: result.Action.Action.Name, - params: result.Action.Action.Parameters, + name: result.Action.Name, + params: result.Action.Parameters, }, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/uniter/uniter_test.go charm-2.2.0/src/github.com/juju/juju/api/uniter/uniter_test.go --- charm-2.1.1/src/github.com/juju/juju/api/uniter/uniter_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/uniter/uniter_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -45,9 +45,11 @@ s.controllerMachine = testing.AddControllerMachine(c, s.State) } - // Bind wordpress:db to space "internal" + // Bind "db" relation of wordpress to space "internal", + // and the "admin-api" extra-binding to space "public". bindings := map[string]string{ - "db": "internal", + "db": "internal", + "admin-api": "public", } _, err := s.State.AddSpace("internal", "", nil, false) c.Assert(err, jc.ErrorIsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/uniter/unit.go charm-2.2.0/src/github.com/juju/juju/api/uniter/unit.go --- charm-2.1.1/src/github.com/juju/juju/api/uniter/unit.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/uniter/unit.go 2016-04-28 06:03:34.000000000 +0000 @@ -710,3 +710,31 @@ return results.Combine() } + +// NetworkConfig requests network config information for the unit and the given +// bindingName. +func (u *Unit) NetworkConfig(bindingName string) ([]params.NetworkConfig, error) { + var results params.UnitNetworkConfigResults + args := params.UnitsNetworkConfig{ + Args: []params.UnitNetworkConfig{{ + BindingName: bindingName, + UnitTag: u.tag.String(), + }}, + } + + err := u.st.facade.FacadeCall("NetworkConfig", args, &results) + if err != nil { + return nil, errors.Trace(err) + } + + if len(results.Results) != 1 { + return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) + } + + result := results.Results[0] + if result.Error != nil { + return nil, result.Error + } + + return result.Config, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/uniter/unit_test.go charm-2.2.0/src/github.com/juju/juju/api/uniter/unit_test.go --- charm-2.1.1/src/github.com/juju/juju/api/uniter/unit_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/uniter/unit_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -303,6 +303,44 @@ c.Assert(address, gc.Equals, "1.2.3.4") } +func (s *unitSuite) TestNetworkConfig(c *gc.C) { + c.Skip("dimitern: temporarily disabled to pass a CI run until it can be fixed like its apiserver/uniter counterpart") + + // Set some provider addresses bound to both "public" and "internal" + // spaces. + addresses := []network.Address{ + network.NewAddressOnSpace("public", "8.8.8.8"), + network.NewAddressOnSpace("", "8.8.4.4"), + network.NewAddressOnSpace("internal", "10.0.0.1"), + network.NewAddressOnSpace("internal", "10.0.0.2"), + network.NewAddressOnSpace("public", "fc00::1"), + } + err := s.wordpressMachine.SetProviderAddresses(addresses...) + c.Assert(err, jc.ErrorIsNil) + + netConfig, err := s.apiUnit.NetworkConfig("db") // relation name, bound to "internal" + c.Assert(err, jc.ErrorIsNil) + c.Assert(netConfig, jc.DeepEquals, []params.NetworkConfig{ + {Address: "10.0.0.1"}, + {Address: "10.0.0.2"}, + }) + + netConfig, err = s.apiUnit.NetworkConfig("admin-api") // extra-binding name, bound to "public" + c.Assert(err, jc.ErrorIsNil) + c.Assert(netConfig, jc.DeepEquals, []params.NetworkConfig{ + {Address: "8.8.8.8"}, + {Address: "fc00::1"}, + }) + + netConfig, err = s.apiUnit.NetworkConfig("unknown") + c.Assert(err, gc.ErrorMatches, `binding name "unknown" not defined by the unit's charm`) + c.Assert(netConfig, gc.IsNil) + + netConfig, err = s.apiUnit.NetworkConfig("") + c.Assert(err, gc.ErrorMatches, "binding name cannot be empty") + c.Assert(netConfig, gc.IsNil) +} + func (s *unitSuite) TestAvailabilityZone(c *gc.C) { uniter.PatchUnitResponse(s, s.apiUnit, "AvailabilityZone", func(result interface{}) error { diff -Nru charm-2.1.1/src/github.com/juju/juju/api/upgrader/unitupgrader_test.go charm-2.2.0/src/github.com/juju/juju/api/upgrader/unitupgrader_test.go --- charm-2.1.1/src/github.com/juju/juju/api/upgrader/unitupgrader_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/upgrader/unitupgrader_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -9,6 +9,7 @@ "github.com/juju/utils" "github.com/juju/utils/arch" "github.com/juju/utils/series" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/api" @@ -17,7 +18,7 @@ jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" "github.com/juju/juju/tools" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" "github.com/juju/juju/watcher/watchertest" ) @@ -37,7 +38,7 @@ var _ = gc.Suite(&unitUpgraderSuite{}) var current = version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/upgrader/upgrader.go charm-2.2.0/src/github.com/juju/juju/api/upgrader/upgrader.go --- charm-2.1.1/src/github.com/juju/juju/api/upgrader/upgrader.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/upgrader/upgrader.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,11 +6,12 @@ import ( "fmt" + "github.com/juju/version" + "github.com/juju/juju/api/base" apiwatcher "github.com/juju/juju/api/watcher" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/tools" - "github.com/juju/juju/version" "github.com/juju/juju/watcher" ) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/upgrader/upgrader_test.go charm-2.2.0/src/github.com/juju/juju/api/upgrader/upgrader_test.go --- charm-2.1.1/src/github.com/juju/juju/api/upgrader/upgrader_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/upgrader/upgrader_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -8,6 +8,7 @@ "github.com/juju/errors" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/api" @@ -18,7 +19,6 @@ statetesting "github.com/juju/juju/state/testing" coretesting "github.com/juju/juju/testing" "github.com/juju/juju/tools" - "github.com/juju/juju/version" "github.com/juju/juju/watcher/watchertest" ) diff -Nru charm-2.1.1/src/github.com/juju/juju/api/usermanager/client.go charm-2.2.0/src/github.com/juju/juju/api/usermanager/client.go --- charm-2.1.1/src/github.com/juju/juju/api/usermanager/client.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/usermanager/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,11 +7,14 @@ "fmt" "strings" + "gopkg.in/macaroon.v1" + "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" "github.com/juju/juju/api/base" + "github.com/juju/juju/api/modelmanager" "github.com/juju/juju/apiserver/params" ) @@ -33,7 +36,7 @@ // AddUser creates a new local user in the controller, sharing with that user any specified models. func (c *Client) AddUser( - username, displayName, password string, modelUUIDs ...string, + username, displayName, password, access string, modelUUIDs ...string, ) (_ names.UserTag, secretKey []byte, _ error) { if !names.IsValidUser(username) { return names.UserTag{}, nil, fmt.Errorf("invalid user name %q", username) @@ -42,15 +45,26 @@ for i, uuid := range modelUUIDs { modelTags[i] = names.NewModelTag(uuid).String() } + + var accessPermission params.ModelAccessPermission + var err error + if len(modelTags) > 0 { + accessPermission, err = modelmanager.ParseModelAccess(access) + if err != nil { + return names.UserTag{}, nil, errors.Trace(err) + } + } + userArgs := params.AddUsers{ Users: []params.AddUser{{ Username: username, DisplayName: displayName, Password: password, - SharedModelTags: modelTags}}, + SharedModelTags: modelTags, + ModelAccess: accessPermission}}, } var results params.AddUserResults - err := c.facade.FacadeCall("AddUser", userArgs, &results) + err = c.facade.FacadeCall("AddUser", userArgs, &results) if err != nil { return names.UserTag{}, nil, errors.Trace(err) } @@ -175,3 +189,22 @@ } return results.OneError() } + +// CreateLocalLoginMacaroon creates a local login macaroon for the +// authenticated user. +func (c *Client) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) { + args := params.Entities{Entities: []params.Entity{{tag.String()}}} + var results params.MacaroonResults + if err := c.facade.FacadeCall("CreateLocalLoginMacaroon", args, &results); err != nil { + return nil, errors.Trace(err) + } + if n := len(results.Results); n != 1 { + logger.Errorf("expected 1 result, got %#v", results) + return nil, errors.Errorf("expected 1 result, got %d", n) + } + result := results.Results[0] + if result.Error != nil { + return nil, errors.Trace(result.Error) + } + return result.Result, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/usermanager/client_test.go charm-2.2.0/src/github.com/juju/juju/api/usermanager/client_test.go --- charm-2.1.1/src/github.com/juju/juju/api/usermanager/client_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/usermanager/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -30,7 +30,7 @@ } func (s *usermanagerSuite) TestAddUser(c *gc.C) { - tag, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password") + tag, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password", "") c.Assert(err, jc.ErrorIsNil) user, err := s.State.User(tag) @@ -40,11 +40,14 @@ c.Assert(user.PasswordValid("password"), jc.IsTrue) } -func (s *usermanagerSuite) TestAddUserWithSharedModel(c *gc.C) { +func (s *usermanagerSuite) TestAddUserWithModelAccess(c *gc.C) { sharedModelState := s.Factory.MakeModel(c, nil) defer sharedModelState.Close() - tag, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password", sharedModelState.ModelUUID()) + foobarTag, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password", "read", sharedModelState.ModelUUID()) + c.Assert(err, jc.ErrorIsNil) + + altAdminTag, _, err := s.usermanager.AddUser("altadmin", "Alt Admin", "password", "write", sharedModelState.ModelUUID()) c.Assert(err, jc.ErrorIsNil) // Check model is shared with expected users. @@ -52,13 +55,19 @@ c.Assert(err, jc.ErrorIsNil) users, err := sharedModel.Users() c.Assert(err, jc.ErrorIsNil) - c.Assert(users, gc.HasLen, 2) + c.Assert(users, gc.HasLen, 3) var modelUserTags = make([]names.UserTag, len(users)) for i, u := range users { modelUserTags[i] = u.UserTag() + if u.UserTag().Name() == "foobar" { + c.Assert(u.ReadOnly(), jc.IsTrue) + } else { + c.Assert(u.ReadOnly(), jc.IsFalse) + } } c.Assert(modelUserTags, jc.SameContents, []names.UserTag{ - tag, + foobarTag, + altAdminTag, names.NewLocalUserTag("admin"), }) } @@ -66,7 +75,7 @@ func (s *usermanagerSuite) TestAddExistingUser(c *gc.C) { s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar"}) - _, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password") + _, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password", "read") c.Assert(err, gc.ErrorMatches, "failed to create user: user already exists") } @@ -76,7 +85,7 @@ return errors.New("call error") }, ) - _, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password") + _, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password", "write") c.Assert(err, gc.ErrorMatches, "call error") } @@ -90,7 +99,7 @@ return errors.New("wrong result type") }, ) - _, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password") + _, _, err := s.usermanager.AddUser("foobar", "Foo Bar", "password", "read") c.Assert(err, gc.ErrorMatches, "expected 1 result, got 2") } diff -Nru charm-2.1.1/src/github.com/juju/juju/api/watcher/watcher.go charm-2.2.0/src/github.com/juju/juju/api/watcher/watcher.go --- charm-2.1.1/src/github.com/juju/juju/api/watcher/watcher.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/watcher/watcher.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,11 +6,13 @@ import ( "sync" + "github.com/juju/errors" "github.com/juju/loggo" "launchpad.net/tomb" "github.com/juju/juju/api/base" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" "github.com/juju/juju/watcher" ) @@ -438,3 +440,75 @@ func (w *entitiesWatcher) Changes() watcher.StringsChannel { return w.out } + +// NewMigrationStatusWatcher takes the NotifyWatcherId returns by the +// MigrationSlave.Watch API and returns a watcher which will report +// status changes for any migration of the model associated with the +// API connection. +func NewMigrationStatusWatcher(caller base.APICaller, watcherId string) watcher.MigrationStatusWatcher { + w := &migrationStatusWatcher{ + caller: caller, + id: watcherId, + out: make(chan watcher.MigrationStatus), + } + go func() { + defer w.tomb.Done() + w.tomb.Kill(w.loop()) + }() + return w +} + +type migrationStatusWatcher struct { + commonWatcher + caller base.APICaller + id string + out chan watcher.MigrationStatus +} + +func (w *migrationStatusWatcher) loop() error { + w.newResult = func() interface{} { return new(params.MigrationStatus) } + w.call = makeWatcherAPICaller(w.caller, "MigrationStatusWatcher", w.id) + w.commonWatcher.init() + go w.commonLoop() + + for { + var data interface{} + var ok bool + + select { + case data, ok = <-w.in: + if !ok { + // The tomb is already killed with the correct error + // at this point, so just return. + return nil + } + case <-w.tomb.Dying(): + return nil + } + + inStatus := *data.(*params.MigrationStatus) + phase, ok := migration.ParsePhase(inStatus.Phase) + if !ok { + return errors.Errorf("invalid phase %q", inStatus.Phase) + } + outStatus := watcher.MigrationStatus{ + Attempt: inStatus.Attempt, + Phase: phase, + SourceAPIAddrs: inStatus.SourceAPIAddrs, + SourceCACert: inStatus.SourceCACert, + TargetAPIAddrs: inStatus.TargetAPIAddrs, + TargetCACert: inStatus.TargetCACert, + } + select { + case w.out <- outStatus: + case <-w.tomb.Dying(): + return nil + } + } +} + +// Changes returns a channel that reports the latest status of the +// migration of a model. +func (w *migrationStatusWatcher) Changes() <-chan watcher.MigrationStatus { + return w.out +} diff -Nru charm-2.1.1/src/github.com/juju/juju/api/watcher/watcher_test.go charm-2.2.0/src/github.com/juju/juju/api/watcher/watcher_test.go --- charm-2.1.1/src/github.com/juju/juju/api/watcher/watcher_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/api/watcher/watcher_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,12 +6,16 @@ import ( "time" + "github.com/juju/names" jc "github.com/juju/testing/checkers" + "github.com/juju/utils" gc "gopkg.in/check.v1" "github.com/juju/juju/api" + "github.com/juju/juju/api/migrationminion" "github.com/juju/juju/api/watcher" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" "github.com/juju/juju/storage" @@ -21,6 +25,7 @@ "github.com/juju/juju/testing/factory" corewatcher "github.com/juju/juju/watcher" "github.com/juju/juju/watcher/watchertest" + "github.com/juju/juju/worker" ) type watcherSuite struct { @@ -256,3 +261,98 @@ case <-time.After(coretesting.ShortWait): } } + +type migrationSuite struct { + testing.JujuConnSuite +} + +var _ = gc.Suite(&migrationSuite{}) + +func (s *migrationSuite) startSync(c *gc.C, st *state.State) { + backingSt, err := s.BackingStatePool.Get(st.ModelUUID()) + c.Assert(err, jc.ErrorIsNil) + backingSt.StartSync() +} + +func (s *migrationSuite) TestMigrationStatusWatcher(c *gc.C) { + const nonce = "noncey" + + // Create a model to migrate. + hostedState := s.Factory.MakeModel(c, &factory.ModelParams{Prepare: true}) + defer hostedState.Close() + hostedFactory := factory.NewFactory(hostedState) + + // Create a machine in the hosted model to connect as. + m, password := hostedFactory.MakeMachineReturningPassword(c, &factory.MachineParams{ + Nonce: nonce, + }) + + // Connect as the machine to watch for migration status. + apiInfo := s.APIInfo(c) + apiInfo.Tag = m.Tag() + apiInfo.Password = password + apiInfo.ModelTag = hostedState.ModelTag() + apiInfo.Nonce = nonce + + apiConn, err := api.Open(apiInfo, api.DialOpts{}) + c.Assert(err, jc.ErrorIsNil) + defer apiConn.Close() + + // Start watching for a migration. + client := migrationminion.NewClient(apiConn) + w, err := client.Watch() + c.Assert(err, jc.ErrorIsNil) + defer func() { + c.Assert(worker.Stop(w), jc.ErrorIsNil) + }() + + assertNoChange := func() { + s.startSync(c, hostedState) + select { + case _, ok := <-w.Changes(): + c.Fatalf("watcher sent unexpected change: (_, %v)", ok) + case <-time.After(coretesting.ShortWait): + } + } + + assertChange := func(phase migration.Phase) { + s.startSync(c, hostedState) + select { + case status, ok := <-w.Changes(): + c.Assert(ok, jc.IsTrue) + c.Assert(status.Phase, gc.Equals, phase) + case <-time.After(coretesting.LongWait): + c.Fatalf("watcher didn't emit an event") + } + assertNoChange() + } + + // Initial event with no migration in progress. + assertChange(migration.NONE) + + // Now create a migration, should trigger watcher. + spec := state.ModelMigrationSpec{ + InitiatedBy: names.NewUserTag("someone"), + TargetInfo: migration.TargetInfo{ + ControllerTag: names.NewModelTag(utils.MustNewUUID().String()), + Addrs: []string{"1.2.3.4:5"}, + CACert: "cert", + AuthTag: names.NewUserTag("dog"), + Password: "sekret", + }, + } + mig, err := hostedState.CreateModelMigration(spec) + c.Assert(err, jc.ErrorIsNil) + assertChange(migration.QUIESCE) + + // Now abort the migration, this should be reported too. + c.Assert(mig.SetPhase(migration.ABORT), jc.ErrorIsNil) + assertChange(migration.ABORT) + c.Assert(mig.SetPhase(migration.ABORTDONE), jc.ErrorIsNil) + assertChange(migration.ABORTDONE) + + // Start a new migration, this should also trigger. + _, err = hostedState.CreateModelMigration(spec) + c.Assert(err, jc.ErrorIsNil) + assertChange(migration.QUIESCE) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/action/action.go charm-2.2.0/src/github.com/juju/juju/apiserver/action/action.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/action/action.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/action/action.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,6 +4,7 @@ package action import ( + "github.com/juju/errors" "github.com/juju/names" "github.com/juju/juju/apiserver/common" @@ -20,6 +21,7 @@ state *state.State resources *common.Resources authorizer common.Authorizer + check *common.BlockChecker } // NewActionAPI returns an initialized ActionAPI @@ -32,6 +34,7 @@ state: st, resources: resources, authorizer: authorizer, + check: common.NewBlockChecker(st), }, nil } @@ -61,7 +64,7 @@ currentResult.Error = common.ServerError(err) continue } - response.Results[i] = makeActionResult(receiverTag, action) + response.Results[i] = common.MakeActionResult(receiverTag, action) } return response, nil } @@ -86,10 +89,15 @@ // enqueued Action, or an error if there was a problem enqueueing the // Action. func (a *ActionAPI) Enqueue(arg params.Actions) (params.ActionResults, error) { + if err := a.check.ChangeAllowed(); err != nil { + return params.ActionResults{}, errors.Trace(err) + } + + tagToActionReceiver := common.TagToActionReceiverFn(a.state.FindEntity) response := params.ActionResults{Results: make([]params.ActionResult, len(arg.Actions))} for i, action := range arg.Actions { currentResult := &response.Results[i] - receiver, err := tagToActionReceiver(a.state, action.Receiver) + receiver, err := tagToActionReceiver(action.Receiver) if err != nil { currentResult.Error = common.ServerError(err) continue @@ -100,7 +108,7 @@ continue } - response.Results[i] = makeActionResult(receiver.Tag(), enqueued) + response.Results[i] = common.MakeActionResult(receiver.Tag(), enqueued) } return response, nil } @@ -135,6 +143,10 @@ // Cancel attempts to cancel enqueued Actions from running. func (a *ActionAPI) Cancel(arg params.Entities) (params.ActionResults, error) { + if err := a.check.ChangeAllowed(); err != nil { + return params.ActionResults{}, errors.Trace(err) + } + response := params.ActionResults{Results: make([]params.ActionResult, len(arg.Entities))} for i, entity := range arg.Entities { currentResult := &response.Results[i] @@ -164,7 +176,7 @@ continue } - response.Results[i] = makeActionResult(receiverTag, result) + response.Results[i] = common.MakeActionResult(receiverTag, result) } return response, nil } @@ -200,10 +212,11 @@ // and returns all of the Actions the extractorFn can get out of the // ActionReceiver. func (a *ActionAPI) internalList(arg params.Entities, fn extractorFn) (params.ActionsByReceivers, error) { + tagToActionReceiver := common.TagToActionReceiverFn(a.state.FindEntity) response := params.ActionsByReceivers{Actions: make([]params.ActionsByReceiver, len(arg.Entities))} for i, entity := range arg.Entities { currentResult := &response.Actions[i] - receiver, err := tagToActionReceiver(a.state, entity.Tag) + receiver, err := tagToActionReceiver(entity.Tag) if err != nil { currentResult.Error = common.ServerError(common.ErrBadId) continue @@ -220,24 +233,6 @@ return response, nil } -// tagToActionReceiver takes a tag string and tries to convert it to an -// ActionReceiver. -func tagToActionReceiver(st *state.State, tag string) (state.ActionReceiver, error) { - receiverTag, err := names.ParseTag(tag) - if err != nil { - return nil, common.ErrBadId - } - entity, err := st.FindEntity(receiverTag) - if err != nil { - return nil, common.ErrBadId - } - receiver, ok := entity.(state.ActionReceiver) - if !ok { - return nil, common.ErrBadId - } - return receiver, nil -} - // extractorFn is the generic signature for functions that extract // state.Actions from an ActionReceiver, and return them as a slice of // params.ActionResult. @@ -262,60 +257,18 @@ // pendingActions iterates through the Actions() enqueued for an // ActionReceiver, and converts them to a slice of params.ActionResult. func pendingActions(ar state.ActionReceiver) ([]params.ActionResult, error) { - return convertActions(ar, ar.PendingActions) + return common.ConvertActions(ar, ar.PendingActions) } // runningActions iterates through the Actions() running on an // ActionReceiver, and converts them to a slice of params.ActionResult. func runningActions(ar state.ActionReceiver) ([]params.ActionResult, error) { - return convertActions(ar, ar.RunningActions) + return common.ConvertActions(ar, ar.RunningActions) } // completedActions iterates through the Actions() that have run to // completion for an ActionReceiver, and converts them to a slice of // params.ActionResult. func completedActions(ar state.ActionReceiver) ([]params.ActionResult, error) { - return convertActions(ar, ar.CompletedActions) -} - -// getActionsFn declares the function type that returns a slice of -// *state.Action and error, used to curry specific list functions. -type getActionsFn func() ([]*state.Action, error) - -// convertActions takes a generic getActionsFn to obtain a slice -// of *state.Action and then converts them to the API slice of -// params.ActionResult. -func convertActions(ar state.ActionReceiver, fn getActionsFn) ([]params.ActionResult, error) { - items := []params.ActionResult{} - actions, err := fn() - if err != nil { - return items, err - } - for _, action := range actions { - if action == nil { - continue - } - items = append(items, makeActionResult(ar.Tag(), action)) - } - return items, nil -} - -// makeActionResult does the actual type conversion from *state.Action -// to params.ActionResult. -func makeActionResult(actionReceiverTag names.Tag, action *state.Action) params.ActionResult { - output, message := action.Results() - return params.ActionResult{ - Action: ¶ms.Action{ - Receiver: actionReceiverTag.String(), - Tag: action.ActionTag().String(), - Name: action.Name(), - Parameters: action.Parameters(), - }, - Status: string(action.Status()), - Message: message, - Output: output, - Enqueued: action.Enqueued(), - Started: action.Started(), - Completed: action.Completed(), - } + return common.ConvertActions(ar, ar.CompletedActions) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/action/action_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/action/action_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/action/action_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/action/action_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,6 +7,7 @@ "fmt" "testing" + "github.com/juju/errors" "github.com/juju/names" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -14,6 +15,7 @@ "github.com/juju/juju/apiserver/action" "github.com/juju/juju/apiserver/common" + commontesting "github.com/juju/juju/apiserver/common/testing" "github.com/juju/juju/apiserver/params" apiservertesting "github.com/juju/juju/apiserver/testing" jujutesting "github.com/juju/juju/juju/testing" @@ -28,6 +30,7 @@ type actionSuite struct { jujutesting.JujuConnSuite + commontesting.BlockHelper action *action.ActionAPI authorizer apiservertesting.FakeAuthorizer @@ -47,6 +50,8 @@ func (s *actionSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) + s.BlockHelper = commontesting.NewBlockHelper(s.APIState) + s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) s.authorizer = apiservertesting.FakeAuthorizer{ Tag: s.AdminUserTag(c), @@ -102,6 +107,28 @@ s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() }) } +func (s *actionSuite) AssertBlocked(c *gc.C, err error, msg string) { + c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err)) + c.Assert(errors.Cause(err), gc.DeepEquals, ¶ms.Error{ + Message: msg, + Code: "operation is blocked", + }) +} + +func (s *actionSuite) TestBlockEnqueue(c *gc.C) { + // block all changes + s.BlockAllChanges(c, "Enqueue") + _, err := s.action.Enqueue(params.Actions{}) + s.AssertBlocked(c, err, "Enqueue") +} + +func (s *actionSuite) TestBlockCancel(c *gc.C) { + // block all changes + s.BlockAllChanges(c, "Cancel") + _, err := s.action.Cancel(params.Entities{}) + s.AssertBlocked(c, err, "Cancel") +} + func (s *actionSuite) TestActions(c *gc.C) { arg := params.Actions{ Actions: []params.Action{ diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/action/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/action/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/action/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/action/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,9 @@ +// Copyright 2012, 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action + +var ( + GetAllUnitNames = getAllUnitNames + QueueActions = &queueActions +) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/action/run.go charm-2.2.0/src/github.com/juju/juju/apiserver/action/run.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/action/run.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/action/run.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,107 @@ +// Copyright 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action + +import ( + "time" + + "github.com/juju/errors" + "github.com/juju/names" + "github.com/juju/utils/set" + + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/actions" + "github.com/juju/juju/state" +) + +// getAllUnitNames returns a sequence of valid Unit objects from state. If any +// of the service names or unit names are not found, an error is returned. +func getAllUnitNames(st *state.State, units, services []string) (result []names.Tag, err error) { + unitsSet := set.NewStrings(units...) + for _, name := range services { + service, err := st.Service(name) + if err != nil { + return nil, err + } + units, err := service.AllUnits() + if err != nil { + return nil, err + } + for _, unit := range units { + unitsSet.Add(unit.Name()) + } + } + for _, unitName := range unitsSet.SortedValues() { + result = append(result, names.NewUnitTag(unitName)) + } + return result, nil +} + +// Run the commands specified on the machines identified through the +// list of machines, units and services. +func (a *ActionAPI) Run(run params.RunParams) (results params.ActionResults, err error) { + if err := a.check.ChangeAllowed(); err != nil { + return results, errors.Trace(err) + } + + units, err := getAllUnitNames(a.state, run.Units, run.Services) + if err != nil { + return results, errors.Trace(err) + } + + machines := make([]names.Tag, len(run.Machines)) + for i, machineId := range run.Machines { + if !names.IsValidMachine(machineId) { + return results, errors.Errorf("invalid machine id %q", machineId) + } + machines[i] = names.NewMachineTag(machineId) + } + + actionParams := a.createActionsParams(append(units, machines...), run.Commands, run.Timeout) + + return queueActions(a, actionParams) +} + +// RunOnAllMachines attempts to run the specified command on all the machines. +func (a *ActionAPI) RunOnAllMachines(run params.RunParams) (results params.ActionResults, err error) { + if err := a.check.ChangeAllowed(); err != nil { + return results, errors.Trace(err) + } + + machines, err := a.state.AllMachines() + if err != nil { + return results, err + } + machineTags := make([]names.Tag, len(machines)) + for i, machine := range machines { + machineTags[i] = machine.Tag() + } + + actionParams := a.createActionsParams(machineTags, run.Commands, run.Timeout) + + return queueActions(a, actionParams) +} + +func (a *ActionAPI) createActionsParams(actionReceiverTags []names.Tag, quotedCommands string, timeout time.Duration) params.Actions { + + apiActionParams := params.Actions{Actions: []params.Action{}} + + actionParams := map[string]interface{}{} + actionParams["command"] = quotedCommands + actionParams["timeout"] = timeout.Nanoseconds() + + for _, tag := range actionReceiverTags { + apiActionParams.Actions = append(apiActionParams.Actions, params.Action{ + Receiver: tag.String(), + Name: actions.JujuRunActionName, + Parameters: actionParams, + }) + } + + return apiActionParams +} + +var queueActions = func(a *ActionAPI, args params.Actions) (results params.ActionResults, err error) { + return a.Enqueue(args) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/action/run_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/action/run_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/action/run_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/action/run_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,236 @@ +// Copyright 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action_test + +import ( + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/action" + commontesting "github.com/juju/juju/apiserver/common/testing" + "github.com/juju/juju/apiserver/params" + apiservertesting "github.com/juju/juju/apiserver/testing" + jujutesting "github.com/juju/juju/juju/testing" + "github.com/juju/juju/state" + "github.com/juju/juju/testing" +) + +type runSuite struct { + jujutesting.JujuConnSuite + commontesting.BlockHelper + + client *action.ActionAPI +} + +var _ = gc.Suite(&runSuite{}) + +func (s *runSuite) SetUpTest(c *gc.C) { + s.JujuConnSuite.SetUpTest(c) + s.BlockHelper = commontesting.NewBlockHelper(s.APIState) + s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) + + var err error + auth := apiservertesting.FakeAuthorizer{ + Tag: s.AdminUserTag(c), + } + s.client, err = action.NewActionAPI(s.State, nil, auth) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *runSuite) addMachine(c *gc.C) *state.Machine { + machine, err := s.State.AddMachine("quantal", state.JobHostUnits) + c.Assert(err, jc.ErrorIsNil) + return machine +} + +func (s *runSuite) addUnit(c *gc.C, service *state.Service) *state.Unit { + unit, err := service.AddUnit() + c.Assert(err, jc.ErrorIsNil) + err = unit.AssignToNewMachine() + c.Assert(err, jc.ErrorIsNil) + return unit +} + +func (s *runSuite) TestGetAllUnitNames(c *gc.C) { + charm := s.AddTestingCharm(c, "dummy") + owner := s.AdminUserTag(c) + magic, err := s.State.AddService(state.AddServiceArgs{Name: "magic", Owner: owner.String(), Charm: charm}) + s.addUnit(c, magic) + s.addUnit(c, magic) + + notAssigned, err := s.State.AddService(state.AddServiceArgs{Name: "not-assigned", Owner: owner.String(), Charm: charm}) + c.Assert(err, jc.ErrorIsNil) + _, err = notAssigned.AddUnit() + c.Assert(err, jc.ErrorIsNil) + + _, err = s.State.AddService(state.AddServiceArgs{Name: "no-units", Owner: owner.String(), Charm: charm}) + c.Assert(err, jc.ErrorIsNil) + + wordpress, err := s.State.AddService(state.AddServiceArgs{Name: "wordpress", Owner: owner.String(), Charm: s.AddTestingCharm(c, "wordpress")}) + c.Assert(err, jc.ErrorIsNil) + wordpress0 := s.addUnit(c, wordpress) + _, err = s.State.AddService(state.AddServiceArgs{Name: "logging", Owner: owner.String(), Charm: s.AddTestingCharm(c, "logging")}) + c.Assert(err, jc.ErrorIsNil) + + eps, err := s.State.InferEndpoints("logging", "wordpress") + c.Assert(err, jc.ErrorIsNil) + rel, err := s.State.AddRelation(eps...) + c.Assert(err, jc.ErrorIsNil) + ru, err := rel.Unit(wordpress0) + c.Assert(err, jc.ErrorIsNil) + err = ru.EnterScope(nil) + c.Assert(err, jc.ErrorIsNil) + + for i, test := range []struct { + message string + expected []string + units []string + services []string + error string + }{{ + message: "no units, expected nil slice", + }, { + message: "asking for a service that isn't there", + services: []string{"foo"}, + error: `service "foo" not found`, + }, { + message: "service with no units is not really an error", + services: []string{"no-units"}, + }, { + message: "A service with units", + services: []string{"magic"}, + expected: []string{"magic/0", "magic/1"}, + }, { + message: "Asking for just a unit", + units: []string{"magic/0"}, + expected: []string{"magic/0"}, + }, { + message: "Asking for just a subordinate unit", + units: []string{"logging/0"}, + expected: []string{"logging/0"}, + }, { + message: "Asking for a unit, and the service", + services: []string{"magic"}, + units: []string{"magic/0"}, + expected: []string{"magic/0", "magic/1"}, + }} { + c.Logf("%v: %s", i, test.message) + result, err := action.GetAllUnitNames(s.State, test.units, test.services) + if test.error == "" { + c.Check(err, jc.ErrorIsNil) + var units []string + for _, unit := range result { + units = append(units, unit.Id()) + } + c.Check(units, jc.SameContents, test.expected) + } else { + c.Check(err, gc.ErrorMatches, test.error) + } + } +} + +func (s *runSuite) AssertBlocked(c *gc.C, err error, msg string) { + c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err)) + c.Assert(errors.Cause(err), gc.DeepEquals, ¶ms.Error{ + Message: msg, + Code: "operation is blocked", + }) +} + +func (s *runSuite) TestBlockRunOnAllMachines(c *gc.C) { + // block all changes + s.BlockAllChanges(c, "TestBlockRunOnAllMachines") + _, err := s.client.RunOnAllMachines( + params.RunParams{ + Commands: "hostname", + Timeout: testing.LongWait, + }) + s.AssertBlocked(c, err, "TestBlockRunOnAllMachines") +} + +func (s *runSuite) TestBlockRunMachineAndService(c *gc.C) { + // block all changes + s.BlockAllChanges(c, "TestBlockRunMachineAndService") + _, err := s.client.Run( + params.RunParams{ + Commands: "hostname", + Timeout: testing.LongWait, + Machines: []string{"0"}, + Services: []string{"magic"}, + }) + s.AssertBlocked(c, err, "TestBlockRunMachineAndService") +} + +func (s *runSuite) TestRunMachineAndService(c *gc.C) { + // We only test that we create the actions correctly + // There is no need to test anything else at this level. + expectedPayload := map[string]interface{}{ + "command": "hostname", + "timeout": int64(0), + } + expectedArgs := params.Actions{ + Actions: []params.Action{ + {Receiver: "unit-magic-0", Name: "juju-run", Parameters: expectedPayload}, + {Receiver: "unit-magic-1", Name: "juju-run", Parameters: expectedPayload}, + {Receiver: "machine-0", Name: "juju-run", Parameters: expectedPayload}, + }, + } + called := false + s.PatchValue(action.QueueActions, func(client *action.ActionAPI, args params.Actions) (params.ActionResults, error) { + called = true + c.Assert(args, jc.DeepEquals, expectedArgs) + return params.ActionResults{}, nil + }) + + s.addMachine(c) + + charm := s.AddTestingCharm(c, "dummy") + owner := s.Factory.MakeUser(c, nil).Tag() + magic, err := s.State.AddService(state.AddServiceArgs{Name: "magic", Owner: owner.String(), Charm: charm}) + c.Assert(err, jc.ErrorIsNil) + s.addUnit(c, magic) + s.addUnit(c, magic) + + s.client.Run( + params.RunParams{ + Commands: "hostname", + Machines: []string{"0"}, + Services: []string{"magic"}, + }) + c.Assert(called, jc.IsTrue) +} + +func (s *runSuite) TestRunOnAllMachines(c *gc.C) { + // We only test that we create the actions correctly + // There is no need to test anything else at this level. + expectedPayload := map[string]interface{}{ + "command": "hostname", + "timeout": testing.LongWait.Nanoseconds(), + } + expectedArgs := params.Actions{ + Actions: []params.Action{ + {Receiver: "machine-0", Name: "juju-run", Parameters: expectedPayload}, + {Receiver: "machine-1", Name: "juju-run", Parameters: expectedPayload}, + {Receiver: "machine-2", Name: "juju-run", Parameters: expectedPayload}, + }, + } + called := false + s.PatchValue(action.QueueActions, func(client *action.ActionAPI, args params.Actions) (params.ActionResults, error) { + called = true + c.Assert(args, jc.DeepEquals, expectedArgs) + return params.ActionResults{}, nil + }) + // Make three machines. + s.addMachine(c) + s.addMachine(c) + s.addMachine(c) + + s.client.RunOnAllMachines( + params.RunParams{ + Commands: "hostname", + Timeout: testing.LongWait, + }) + c.Assert(called, jc.IsTrue) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/addresser/addresser_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/addresser/addresser_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/addresser/addresser_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/addresser/addresser_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -16,7 +16,6 @@ "github.com/juju/juju/cmd/modelcmd" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" - "github.com/juju/juju/environs/configstore" "github.com/juju/juju/feature" "github.com/juju/juju/instance" "github.com/juju/juju/jujuclient/jujuclienttesting" @@ -59,11 +58,7 @@ var err error s.api, err = addresser.NewAddresserAPI(nil, s.resources, s.authoriser) c.Assert(err, jc.ErrorIsNil) -} - -func (s *AddresserSuite) TearDownTest(c *gc.C) { - dummy.Reset() - s.BaseSuite.TearDownTest(c) + s.AddCleanup(dummy.Reset) } func (s *AddresserSuite) TestCanDeallocateAddressesEnabled(c *gc.C) { @@ -274,12 +269,14 @@ // testingEnvConfig prepares an environment configuration using // the dummy provider. func testingEnvConfig(c *gc.C) *config.Config { - cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) - c.Assert(err, jc.ErrorIsNil) env, err := environs.Prepare( - modelcmd.BootstrapContext(coretesting.Context(c)), configstore.NewMem(), + modelcmd.BootstrapContext(coretesting.Context(c)), jujuclienttesting.NewMemStore(), - "dummycontroller", environs.PrepareForBootstrapParams{Config: cfg}, + environs.PrepareParams{ + ControllerName: "dummycontroller", + BaseConfig: dummy.SampleConfig(), + CloudName: "dummy", + }, ) c.Assert(err, jc.ErrorIsNil) return env.Config() @@ -299,12 +296,14 @@ // mockTestingEnvConfig prepares an environment configuration using // the mock provider which does not support networking. func mockTestingEnvConfig(c *gc.C) *config.Config { - cfg, err := config.New(config.NoDefaults, mockConfig()) - c.Assert(err, jc.ErrorIsNil) env, err := environs.Prepare( - modelcmd.BootstrapContext(coretesting.Context(c)), configstore.NewMem(), + modelcmd.BootstrapContext(coretesting.Context(c)), jujuclienttesting.NewMemStore(), - "dummycontroller", environs.PrepareForBootstrapParams{Config: cfg}, + environs.PrepareParams{ + ControllerName: "dummycontroller", + BaseConfig: mockConfig(), + CloudName: "dummy", + }, ) c.Assert(err, jc.ErrorIsNil) return env.Config() diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/addresser/mock_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/addresser/mock_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/addresser/mock_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/addresser/mock_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -390,7 +390,11 @@ environs.EnvironProvider } -func (p mockEnvironProvider) PrepareForBootstrap(environs.BootstrapContext, environs.PrepareForBootstrapParams) (environs.Environ, error) { +func (p mockEnvironProvider) BootstrapConfig(args environs.BootstrapConfigParams) (*config.Config, error) { + return args.Config, nil +} + +func (p mockEnvironProvider) PrepareForBootstrap(environs.BootstrapContext, *config.Config) (environs.Environ, error) { return &mockEnviron{}, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/admin.go charm-2.2.0/src/github.com/juju/juju/apiserver/admin.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/admin.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/admin.go 2016-04-28 06:03:34.000000000 +0000 @@ -18,7 +18,7 @@ "github.com/juju/juju/rpc/rpcreflect" "github.com/juju/juju/state" "github.com/juju/juju/state/presence" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type adminApiFactory func(srv *Server, root *apiHandler, reqNotifier *requestNotifier) interface{} @@ -176,7 +176,7 @@ ControllerTag: environ.ControllerTag().String(), Facades: DescribeFacades(), UserInfo: maybeUserInfo, - ServerVersion: version.Current.String(), + ServerVersion: jujuversion.Current.String(), } // For sufficiently modern login versions, stop serving the diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/admin_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/admin_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/admin_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/admin_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -18,6 +18,7 @@ gc "gopkg.in/check.v1" "github.com/juju/juju/api" + apimachiner "github.com/juju/juju/api/machiner" apitesting "github.com/juju/juju/api/testing" "github.com/juju/juju/apiserver" "github.com/juju/juju/apiserver/params" @@ -79,8 +80,8 @@ info := s.APIInfo(c) info.Tag = nil info.Password = "" - st, err := api.Open(info, api.DialOpts{}) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) + defer st.Close() request := ¶ms.LoginRequest{ AuthTag: "bar", @@ -88,7 +89,7 @@ } var response params.LoginResult - err = st.APICall("Admin", 3, "", "Login", request, &response) + err := st.APICall("Admin", 3, "", "Login", request, &response) c.Assert(err, gc.ErrorMatches, `.*"bar" is not a valid tag.*`) } @@ -124,29 +125,24 @@ code: params.CodeUnauthorized, }} { c.Logf("test %d; entity %q; password %q", i, t.tag, t.password) - // Note that Open does not log in if the tag and password - // are empty. This allows us to test operations on the connection - // before calling Login, which we could not do if Open - // always logged in. - info.Tag = nil - info.Password = "" func() { - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + // Open the API without logging in, so we can perform + // operations on the connection before calling Login. + st := s.openAPIWithoutLogin(c, info) defer st.Close() - _, err = st.Machiner().Machine(names.NewMachineTag("0")) + _, err := apimachiner.NewState(st).Machine(names.NewMachineTag("0")) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `unknown object type "Machiner"`, Code: "not implemented", }) // Since these are user login tests, the nonce is empty. - err = st.Login(t.tag, t.password, "") + err = st.Login(t.tag, t.password, "", nil) c.Assert(errors.Cause(err), gc.DeepEquals, t.err) c.Assert(params.ErrCode(err), gc.Equals, t.code) - _, err = st.Machiner().Machine(names.NewMachineTag("0")) + _, err = apimachiner.NewState(st).Machine(names.NewMachineTag("0")) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `unknown object type "Machiner"`, Code: "not implemented", @@ -159,22 +155,19 @@ info, cleanup := s.setupServerWithValidator(c, nil) defer cleanup() - info.Tag = nil - info.Password = "" - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() password := "password" u := s.Factory.MakeUser(c, &factory.UserParams{Password: password, Disabled: true}) - _, err = st.Client().Status([]string{}) + _, err := st.Client().Status([]string{}) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `unknown object type "Client"`, Code: "not implemented", }) // Since these are user login tests, the nonce is empty. - err = st.Login(u.Tag(), password, "") + err = st.Login(u.Tag(), password, "", nil) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: "invalid entity name or password", Code: "unauthorized access", @@ -202,7 +195,7 @@ c.Assert(err, jc.ErrorIsNil) defer apiConn.Close() - apiMachine, err := apiConn.Machiner().Machine(machine.MachineTag()) + apiMachine, err := apimachiner.NewState(apiConn).Machine(machine.MachineTag()) c.Assert(err, jc.ErrorIsNil) c.Assert(apiMachine.Tag(), gc.Equals, machine.Tag()) } @@ -542,7 +535,7 @@ checkLogin := func(tag names.Tag) { st := s.openAPIWithoutLogin(c, info) defer st.Close() - err := st.Login(tag, "dummy-secret", "nonce") + err := st.Login(tag, "dummy-secret", "nonce", nil) c.Assert(err, gc.ErrorMatches, "something") } checkLogin(names.NewUserTag("definitelywontexist")) @@ -559,7 +552,7 @@ defer st.Close() // Ensure not already logged in. - _, err := st.Machiner().Machine(names.NewMachineTag("0")) + _, err := apimachiner.NewState(st).Machine(names.NewMachineTag("0")) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `unknown object type "Machiner"`, Code: "not implemented", @@ -567,7 +560,7 @@ adminUser := s.AdminUserTag(c) // Since these are user login tests, the nonce is empty. - err = st.Login(adminUser, "dummy-secret", "") + err = st.Login(adminUser, "dummy-secret", "", nil) checker(c, err, st) } @@ -611,6 +604,7 @@ func (s *baseLoginSuite) openAPIWithoutLogin(c *gc.C, info *api.Info) api.Connection { info.Tag = nil info.Password = "" + info.SkipLogin = true st, err := api.Open(info, fastDialOpts) c.Assert(err, jc.ErrorIsNil) return st @@ -621,12 +615,11 @@ defer cleanup() c.Assert(info.ModelTag, gc.Equals, s.State.ModelTag()) - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() adminUser := s.AdminUserTag(c) - err = st.Login(adminUser, "dummy-secret", "") + err := st.Login(adminUser, "dummy-secret", "", nil) c.Assert(err, jc.ErrorIsNil) s.assertRemoteEnvironment(c, st, s.State.ModelTag()) @@ -637,12 +630,11 @@ defer cleanup() c.Assert(info.ModelTag, gc.Equals, s.State.ModelTag()) - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() adminUser := s.AdminUserTag(c) - err = st.Login(adminUser, "bad-password", "") + err := st.Login(adminUser, "bad-password", "", nil) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `invalid entity name or password`, Code: "unauthorized access", @@ -656,12 +648,11 @@ uuid, err := utils.NewUUID() c.Assert(err, jc.ErrorIsNil) info.ModelTag = names.NewModelTag(uuid.String()) - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() adminUser := s.AdminUserTag(c) - err = st.Login(adminUser, "dummy-secret", "") + err = st.Login(adminUser, "dummy-secret", "", nil) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: fmt.Sprintf("unknown model: %q", uuid), Code: "not found", @@ -673,12 +664,11 @@ defer cleanup() info.ModelTag = names.NewModelTag("rubbish") - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() adminUser := s.AdminUserTag(c) - err = st.Login(adminUser, "dummy-secret", "") + err := st.Login(adminUser, "dummy-secret", "", nil) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `unknown model: "rubbish"`, Code: "not found", @@ -695,11 +685,10 @@ }) defer envState.Close() info.ModelTag = envState.ModelTag() - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() - err = st.Login(envOwner.UserTag(), "password", "") + err := st.Login(envOwner.UserTag(), "password", "", nil) c.Assert(err, jc.ErrorIsNil) s.assertRemoteEnvironment(c, st, envState.ModelTag()) } @@ -718,7 +707,6 @@ ConfigAttrs: map[string]interface{}{ "controller": false, }, - Prepare: true, }) defer envState.Close() @@ -728,11 +716,10 @@ }) info.ModelTag = envState.ModelTag() - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() - err = st.Login(machine.Tag(), password, "nonce") + err := st.Login(machine.Tag(), password, "nonce", nil) c.Assert(err, jc.ErrorIsNil) } @@ -747,11 +734,10 @@ envState := s.Factory.MakeModel(c, nil) defer envState.Close() info.ModelTag = envState.ModelTag() - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() - err = st.Login(machine.Tag(), password, "nonce") + err := st.Login(machine.Tag(), password, "nonce", nil) c.Assert(err, jc.ErrorIsNil) } @@ -764,11 +750,10 @@ envState := s.Factory.MakeModel(c, nil) defer envState.Close() info.ModelTag = envState.ModelTag() - st, err := api.Open(info, fastDialOpts) - c.Assert(err, jc.ErrorIsNil) + st := s.openAPIWithoutLogin(c, info) defer st.Close() - err = st.Login(machine.Tag(), password, "nonce") + err := st.Login(machine.Tag(), password, "nonce", nil) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: "invalid entity name or password", Code: "unauthorized access", diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/agenttools/agenttools.go charm-2.2.0/src/github.com/juju/juju/apiserver/agenttools/agenttools.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/agenttools/agenttools.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/agenttools/agenttools.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "github.com/juju/errors" "github.com/juju/loggo" + "github.com/juju/version" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/environs" @@ -13,7 +14,6 @@ "github.com/juju/juju/environs/tools" "github.com/juju/juju/state" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) func init() { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/agenttools/agenttools_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/agenttools/agenttools_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/agenttools/agenttools_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/agenttools/agenttools_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "github.com/juju/errors" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/environs" @@ -13,7 +14,6 @@ "github.com/juju/juju/state" coretesting "github.com/juju/juju/testing" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) var _ = gc.Suite(&AgentToolsSuite{}) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/allfacades.go charm-2.2.0/src/github.com/juju/juju/apiserver/allfacades.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/allfacades.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/allfacades.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,9 @@ // This file imports all of the facades so they get registered at runtime. // When adding a new facade implementation, import it here so that its init() // function will get called to register it. +// +// TODO(fwereade): this is silly. We should be declaring our full API in *one* +// place, not scattering it across packages and depending on magic import lists. import ( _ "github.com/juju/juju/apiserver/action" _ "github.com/juju/juju/apiserver/addresser" @@ -29,13 +32,19 @@ _ "github.com/juju/juju/apiserver/instancepoller" _ "github.com/juju/juju/apiserver/keymanager" _ "github.com/juju/juju/apiserver/keyupdater" + _ "github.com/juju/juju/apiserver/lifeflag" _ "github.com/juju/juju/apiserver/logger" _ "github.com/juju/juju/apiserver/machine" + _ "github.com/juju/juju/apiserver/machineactions" _ "github.com/juju/juju/apiserver/machinemanager" _ "github.com/juju/juju/apiserver/meterstatus" _ "github.com/juju/juju/apiserver/metricsadder" _ "github.com/juju/juju/apiserver/metricsdebug" _ "github.com/juju/juju/apiserver/metricsmanager" + _ "github.com/juju/juju/apiserver/migrationflag" + _ "github.com/juju/juju/apiserver/migrationmaster" + _ "github.com/juju/juju/apiserver/migrationminion" + _ "github.com/juju/juju/apiserver/migrationtarget" _ "github.com/juju/juju/apiserver/modelmanager" _ "github.com/juju/juju/apiserver/provisioner" _ "github.com/juju/juju/apiserver/proxyupdater" @@ -43,6 +52,8 @@ _ "github.com/juju/juju/apiserver/resumer" _ "github.com/juju/juju/apiserver/retrystrategy" _ "github.com/juju/juju/apiserver/service" + _ "github.com/juju/juju/apiserver/servicescaler" + _ "github.com/juju/juju/apiserver/singular" _ "github.com/juju/juju/apiserver/spaces" _ "github.com/juju/juju/apiserver/statushistory" _ "github.com/juju/juju/apiserver/storage" diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/apiserver.go charm-2.2.0/src/github.com/juju/juju/apiserver/apiserver.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/apiserver.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/apiserver.go 2016-04-28 06:03:34.000000000 +0000 @@ -22,6 +22,7 @@ "launchpad.net/tomb" "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/common/apihttp" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/rpc" "github.com/juju/juju/rpc/jsoncodec" @@ -67,6 +68,9 @@ LogDir string Validator LoginValidator CertChanged chan params.StateServingInfo + + // This field only exists to support testing. + StatePool *state.StatePool } // changeCertListener wraps a TLS net.Listener. @@ -188,9 +192,15 @@ Certificates: []tls.Certificate{tlsCert}, MinVersion: tls.VersionTLS10, } + + stPool := cfg.StatePool + if stPool == nil { + stPool = state.NewStatePool(s) + } + srv := &Server{ state: s, - statePool: state.NewStatePool(s), + statePool: stPool, lis: newChangeCertListener(lis, cfg.CertChanged, tlsConfig), tag: cfg.Tag, dataDir: cfg.DataDir, @@ -201,7 +211,10 @@ 3: newAdminApiV3, }, } - srv.authCtxt = newAuthContext(srv) + srv.authCtxt, err = newAuthContext(s) + if err != nil { + return nil, errors.Trace(err) + } go srv.run() return srv, nil } @@ -244,8 +257,9 @@ func newRequestNotifier(count *int32) *requestNotifier { return &requestNotifier{ - id: atomic.AddInt64(&globalCounter, 1), - tag_: "", + id: atomic.AddInt64(&globalCounter, 1), + tag_: "", + // TODO(fwereade): 2016-03-17 lp:1558657 start: time.Now(), count: count, } @@ -308,40 +322,6 @@ func (n *requestNotifier) ClientReply(req rpc.Request, hdr *rpc.Header, body interface{}) { } -// trackRequests wraps a http.Handler, incrementing and decrementing -// the apiserver's WaitGroup and blocking request when the apiserver -// is shutting down. -// -// Note: It is only safe to use trackRequests with API handlers which -// are interruptible (i.e. they pay attention to the apiserver tomb) -// or are guaranteed to be short-lived. If it's used with long running -// API handlers which don't watch the apiserver's tomb, apiserver -// shutdown will be blocked until the API handler returns. -func (srv *Server) trackRequests(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - srv.wg.Add(1) - defer srv.wg.Done() - // If we've got to this stage and the tomb is still - // alive, we know that any tomb.Kill must occur after we - // have called wg.Add, so we avoid the possibility of a - // handler goroutine running after Stop has returned. - if srv.tomb.Err() != tomb.ErrStillAlive { - return - } - - handler.ServeHTTP(w, r) - }) -} - -func handleAll(mux *pat.PatternServeMux, pattern string, handler http.Handler) { - mux.Get(pattern, handler) - mux.Post(pattern, handler) - mux.Head(pattern, handler) - mux.Put(pattern, handler) - mux.Del(pattern, handler) - mux.Options(pattern, handler) -} - func (srv *Server) run() { logger.Infof("listening on %q", srv.lis.Addr()) @@ -373,38 +353,63 @@ // registered, first match wins. So more specific ones have to be // registered first. mux := pat.New() + for _, endpoint := range srv.endpoints() { + registerEndpoint(endpoint, mux) + } + go func() { + addr := srv.lis.Addr() // not valid after addr closed + logger.Debugf("Starting API http server on address %q", addr) + err := http.Serve(srv.lis, mux) + // normally logging an error at debug level would be grounds for a beating, + // however in this case the error is *expected* to be non nil, and does not + // affect the operation of the apiserver, but for completeness log it anyway. + logger.Debugf("API http server exited, final error was: %v", err) + }() + + <-srv.tomb.Dying() +} + +func (srv *Server) endpoints() []apihttp.Endpoint { httpCtxt := httpContext{ srv: srv, } + endpoints := common.ResolveAPIEndpoints(srv.newHandlerArgs) + + // TODO(ericsnow) Add the following to the registry instead. + + add := func(pattern string, handler http.Handler) { + // TODO: We can switch from all methods to specific ones for entries + // where we only want to support specific request methods. However, our + // tests currently assert that errors come back as application/json and + // pat only does "text/plain" responses. + for _, method := range common.DefaultHTTPMethods { + endpoints = append(endpoints, apihttp.Endpoint{ + Pattern: pattern, + Method: method, + Handler: handler, + }) + } + } + mainAPIHandler := srv.trackRequests(http.HandlerFunc(srv.apiHandler)) logSinkHandler := srv.trackRequests(newLogSinkHandler(httpCtxt, srv.logDir)) debugLogHandler := srv.trackRequests(newDebugLogDBHandler(httpCtxt)) - handleAll(mux, "/model/:modeluuid/services/:service/resources/:resource", - newResourceHandler(httpCtxt), - ) - handleAll(mux, "/model/:modeluuid/units/:unit/resources/:resource", - newUnitResourceHandler(httpCtxt), - ) - handleAll(mux, "/model/:modeluuid/logsink", logSinkHandler) - handleAll(mux, "/model/:modeluuid/log", debugLogHandler) - handleAll(mux, "/model/:modeluuid/charms", + add("/model/:modeluuid/logsink", logSinkHandler) + add("/model/:modeluuid/log", debugLogHandler) + add("/model/:modeluuid/charms", &charmsHandler{ ctxt: httpCtxt, dataDir: srv.dataDir}, ) - // TODO: We can switch from handleAll to mux.Post/Get/etc for entries - // where we only want to support specific request methods. However, our - // tests currently assert that errors come back as application/json and - // pat only does "text/plain" responses. - handleAll(mux, "/model/:modeluuid/tools", + add("/model/:modeluuid/tools", &toolsUploadHandler{ ctxt: httpCtxt, }, ) - handleAll(mux, "/model/:modeluuid/tools/:version", + add("/model/:modeluuid/tools/:version", &toolsDownloadHandler{ ctxt: httpCtxt, }, @@ -412,14 +417,14 @@ strictCtxt := httpCtxt strictCtxt.strictValidation = true strictCtxt.controllerModelOnly = true - handleAll(mux, "/model/:modeluuid/backups", + add("/model/:modeluuid/backups", &backupHandler{ ctxt: strictCtxt, }, ) - handleAll(mux, "/model/:modeluuid/api", mainAPIHandler) + add("/model/:modeluuid/api", mainAPIHandler) - handleAll(mux, "/model/:modeluuid/images/:kind/:series/:arch/:filename", + add("/model/:modeluuid/images/:kind/:series/:arch/:filename", &imagesDownloadHandler{ ctxt: httpCtxt, dataDir: srv.dataDir, @@ -427,45 +432,112 @@ }, ) - handleGUI(mux, "/gui/:modeluuid/", srv.dataDir, httpCtxt) + endpoints = append(endpoints, guiEndpoints("/gui/:modeluuid/", srv.dataDir, httpCtxt)...) + add("/gui-archive", &guiArchiveHandler{ + ctxt: httpCtxt, + }) + add("/gui-version", &guiVersionHandler{ + ctxt: httpCtxt, + }) // For backwards compatibility we register all the old paths - handleAll(mux, "/log", debugLogHandler) + add("/log", debugLogHandler) - handleAll(mux, "/charms", + add("/charms", &charmsHandler{ ctxt: httpCtxt, dataDir: srv.dataDir, }, ) - handleAll(mux, "/tools", + add("/tools", &toolsUploadHandler{ ctxt: httpCtxt, }, ) - handleAll(mux, "/tools/:version", + add("/tools/:version", &toolsDownloadHandler{ ctxt: httpCtxt, }, ) - handleAll(mux, "/register", + add("/register", ®isterUserHandler{ ctxt: httpCtxt, }, ) - handleAll(mux, "/", mainAPIHandler) + add("/", mainAPIHandler) - go func() { - addr := srv.lis.Addr() // not valid after addr closed - logger.Debugf("Starting API http server on address %q", addr) - err := http.Serve(srv.lis, mux) - // normally logging an error at debug level would be grounds for a beating, - // however in this case the error is *expected* to be non nil, and does not - // affect the operation of the apiserver, but for completeness log it anyway. - logger.Debugf("API http server exited, final error was: %v", err) - }() + return endpoints +} - <-srv.tomb.Dying() +func (srv *Server) newHandlerArgs(spec apihttp.HandlerConstraints) apihttp.NewHandlerArgs { + ctxt := httpContext{ + srv: srv, + strictValidation: spec.StrictValidation, + controllerModelOnly: spec.ControllerModelOnly, + } + + var args apihttp.NewHandlerArgs + switch spec.AuthKind { + case names.UserTagKind: + args.Connect = ctxt.stateForRequestAuthenticatedUser + case names.UnitTagKind: + args.Connect = ctxt.stateForRequestAuthenticatedAgent + case "": + logger.Tracef(`no access level specified; proceeding with "unauthenticated"`) + args.Connect = func(req *http.Request) (*state.State, state.Entity, error) { + st, err := ctxt.stateForRequestUnauthenticated(req) + return st, nil, err + } + default: + logger.Warningf(`unrecognized access level %q; proceeding with "unauthenticated"`, spec.AuthKind) + args.Connect = func(req *http.Request) (*state.State, state.Entity, error) { + st, err := ctxt.stateForRequestUnauthenticated(req) + return st, nil, err + } + } + return args +} + +// trackRequests wraps a http.Handler, incrementing and decrementing +// the apiserver's WaitGroup and blocking request when the apiserver +// is shutting down. +// +// Note: It is only safe to use trackRequests with API handlers which +// are interruptible (i.e. they pay attention to the apiserver tomb) +// or are guaranteed to be short-lived. If it's used with long running +// API handlers which don't watch the apiserver's tomb, apiserver +// shutdown will be blocked until the API handler returns. +func (srv *Server) trackRequests(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv.wg.Add(1) + defer srv.wg.Done() + // If we've got to this stage and the tomb is still + // alive, we know that any tomb.Kill must occur after we + // have called wg.Add, so we avoid the possibility of a + // handler goroutine running after Stop has returned. + if srv.tomb.Err() != tomb.ErrStillAlive { + return + } + + handler.ServeHTTP(w, r) + }) +} + +func registerEndpoint(ep apihttp.Endpoint, mux *pat.PatternServeMux) { + switch ep.Method { + case "GET": + mux.Get(ep.Pattern, ep.Handler) + case "POST": + mux.Post(ep.Pattern, ep.Handler) + case "HEAD": + mux.Head(ep.Pattern, ep.Handler) + case "PUT": + mux.Put(ep.Pattern, ep.Handler) + case "DEL": + mux.Del(ep.Pattern, ep.Handler) + case "OPTIONS": + mux.Options(ep.Pattern, ep.Handler) + } } func (srv *Server) apiHandler(w http.ResponseWriter, req *http.Request) { @@ -534,6 +606,7 @@ } func (srv *Server) mongoPinger() error { + // TODO(fwereade): 2016-03-17 lp:1558657 timer := time.NewTimer(0) session := srv.state.MongoSession().Copy() defer session.Close() diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/authcontext.go charm-2.2.0/src/github.com/juju/juju/apiserver/authcontext.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/authcontext.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/authcontext.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "net/http" "sync" + "time" "github.com/juju/errors" "github.com/juju/names" @@ -16,27 +17,41 @@ "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/state" + "github.com/juju/juju/state/bakerystorage" ) // authContext holds authentication context shared // between all API endpoints. type authContext struct { - srv *Server + st *state.State agentAuth authentication.AgentAuthenticator userAuth authentication.UserAuthenticator // macaroonAuthOnce guards the fields below it. macaroonAuthOnce sync.Once - _macaroonAuth *authentication.MacaroonAuthenticator + _macaroonAuth *authentication.ExternalMacaroonAuthenticator _macaroonAuthError error } -// newAuthContext creates a new authentication context for srv. -func newAuthContext(srv *Server) *authContext { - return &authContext{ - srv: srv, +// newAuthContext creates a new authentication context for st. +func newAuthContext(st *state.State) (*authContext, error) { + ctxt := &authContext{st: st} + store, err := st.NewBakeryStorage() + if err != nil { + return nil, errors.Trace(err) + } + // We use a non-nil, but empty key, because we don't use the + // key, and don't want to incur the overhead of generating one + // each time we create a service. + bakeryService, key, err := newBakeryService(st, store, nil) + if err != nil { + return nil, errors.Trace(err) } + ctxt.userAuth.Service = &expirableStorageBakeryService{bakeryService, key, store, nil} + // TODO(fwereade): 2016-03-17 lp:1558657 + ctxt.userAuth.Clock = state.GetClock() + return ctxt, nil } // Authenticate implements authentication.EntityAuthenticator @@ -78,7 +93,7 @@ // logins. If it fails once, it will always fail. func (ctxt *authContext) macaroonAuth() (authentication.EntityAuthenticator, error) { ctxt.macaroonAuthOnce.Do(func() { - ctxt._macaroonAuth, ctxt._macaroonAuthError = newMacaroonAuth(ctxt.srv.statePool.SystemState()) + ctxt._macaroonAuth, ctxt._macaroonAuthError = newExternalMacaroonAuth(ctxt.st) }) if ctxt._macaroonAuth == nil { return nil, errors.Trace(ctxt._macaroonAuthError) @@ -88,9 +103,10 @@ var errMacaroonAuthNotConfigured = errors.New("macaroon authentication is not configured") -// newMacaroonAuth returns an authenticator that can authenticate -// macaroon-based logins. This is just a helper function for authCtxt.macaroonAuth. -func newMacaroonAuth(st *state.State) (*authentication.MacaroonAuthenticator, error) { +// newExternalMacaroonAuth returns an authenticator that can authenticate +// macaroon-based logins for external users. This is just a helper function +// for authCtxt.macaroonAuth. +func newExternalMacaroonAuth(st *state.State) (*authentication.ExternalMacaroonAuthenticator, error) { envCfg, err := st.ModelConfig() if err != nil { return nil, errors.Annotate(err, "cannot get model config") @@ -109,18 +125,18 @@ return nil, errors.Annotate(err, "cannot get identity public key") } } - svc, err := bakery.NewService( - bakery.NewServiceParams{ - Location: "juju model " + st.ModelUUID(), - Locator: bakery.PublicKeyLocatorMap{ - idURL: idPK, - }, - }, - ) + // We pass in nil for the storage, which leads to in-memory storage + // being used. We only use in-memory storage for now, since we never + // expire the keys, and don't want garbage to accumulate. + // + // TODO(axw) we should store the key in mongo, so that multiple servers + // can authenticate. That will require that we encode the server's ID + // in the macaroon ID so that servers don't overwrite each others' keys. + svc, _, err := newBakeryService(st, nil, bakery.PublicKeyLocatorMap{idURL: idPK}) if err != nil { return nil, errors.Annotate(err, "cannot make bakery service") } - var auth authentication.MacaroonAuthenticator + var auth authentication.ExternalMacaroonAuthenticator auth.Service = svc auth.Macaroon, err = svc.NewMacaroon("api-login", nil, nil) if err != nil { @@ -129,3 +145,48 @@ auth.IdentityLocation = idURL return &auth, nil } + +// newBakeryService creates a new bakery.Service. +func newBakeryService( + st *state.State, + store bakerystorage.ExpirableStorage, + locator bakery.PublicKeyLocator, +) (*bakery.Service, *bakery.KeyPair, error) { + key, err := bakery.GenerateKey() + if err != nil { + return nil, nil, errors.Annotate(err, "generating key for bakery service") + } + service, err := bakery.NewService(bakery.NewServiceParams{ + Location: "juju model " + st.ModelUUID(), + Store: store, + Key: key, + Locator: locator, + }) + if err != nil { + return nil, nil, errors.Trace(err) + } + return service, key, nil +} + +// expirableStorageBakeryService wraps bakery.Service, adding the ExpireStorageAt method. +type expirableStorageBakeryService struct { + *bakery.Service + key *bakery.KeyPair + store bakerystorage.ExpirableStorage + locator bakery.PublicKeyLocator +} + +// ExpireStorageAt implements authentication.ExpirableStorageBakeryService. +func (s *expirableStorageBakeryService) ExpireStorageAt(t time.Time) (authentication.ExpirableStorageBakeryService, error) { + store := s.store.ExpireAt(t) + service, err := bakery.NewService(bakery.NewServiceParams{ + Location: s.Location(), + Store: store, + Key: s.key, + Locator: s.locator, + }) + if err != nil { + return nil, errors.Trace(err) + } + return &expirableStorageBakeryService{service, s.key, store, s.locator}, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/authentication/user.go charm-2.2.0/src/github.com/juju/juju/apiserver/authentication/user.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/authentication/user.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/authentication/user.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,7 +7,9 @@ "time" "github.com/juju/errors" + "github.com/juju/loggo" "github.com/juju/names" + "github.com/juju/utils/clock" "gopkg.in/macaroon-bakery.v1/bakery" "gopkg.in/macaroon-bakery.v1/bakery/checkers" "gopkg.in/macaroon.v1" @@ -17,32 +19,116 @@ "github.com/juju/juju/state" ) -// UserAuthenticator performs password based authentication for users. +var logger = loggo.GetLogger("juju.apiserver.authentication") + +// UserAuthenticator performs authentication for local users. If a password type UserAuthenticator struct { AgentAuthenticator + + // Service holds the service that is used to mint and verify macaroons. + Service ExpirableStorageBakeryService + + // Clock is used to calculate the expiry time for macaroons. + Clock clock.Clock } -const usernameKey = "username" +const ( + usernameKey = "username" + + // TODO(axw) make this configurable via model config. + localLoginExpiryTime = 24 * time.Hour + + // TODO(axw) check with cmars about this time limit. Seems a bit + // too low. Are we prompting the user every hour, or just refreshing + // the token every hour until the external IdM requires prompting + // the user? + externalLoginExpiryTime = 1 * time.Hour +) var _ EntityAuthenticator = (*UserAuthenticator)(nil) -// Authenticate authenticates the provided entity and returns an error on authentication failure. -func (u *UserAuthenticator) Authenticate(entityFinder EntityFinder, tag names.Tag, req params.LoginRequest) (state.Entity, error) { - if tag.Kind() != names.UserTagKind { +// Authenticate authenticates the entity with the specified tag, and returns an +// error on authentication failure. +// +// If and only if no password is supplied, then Authenticate will check for any +// valid macaroons. Otherwise, password authentication will be performed. +func (u *UserAuthenticator) Authenticate( + entityFinder EntityFinder, tag names.Tag, req params.LoginRequest, +) (state.Entity, error) { + userTag, ok := tag.(names.UserTag) + if !ok { return nil, errors.Errorf("invalid request") } + if req.Credentials == "" && userTag.IsLocal() { + return u.authenticateMacaroons(entityFinder, userTag, req) + } return u.AgentAuthenticator.Authenticate(entityFinder, tag, req) } -// MacaroonAuthenticator performs authentication for users using macaroons. -// If the authentication fails because provided macaroons are invalid, -// and macaroon authentiction is enabled, it will return a -// *common.DischargeRequiredError holding a macaroon to be -// discharged. -type MacaroonAuthenticator struct { +// CreateLocalLoginMacaroon creates a time-limited macaroon for a local user +// to log into the controller with. The macaroon will be valid for use with +// UserAuthenticator.Authenticate until the time limit expires, or the Juju +// controller agent restarts. +// +// NOTE(axw) this method will generate a key for a previously unseen user, +// and store it in the bakery.Service's storage. Callers should first ensure +// the user is valid before calling this, to avoid filling storage with keys +// for invalid users. +func (u *UserAuthenticator) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) { + + expiryTime := u.Clock.Now().Add(localLoginExpiryTime) + + // Ensure that the private key that we generate and store will be + // removed from storage once the expiry time has elapsed. + bakeryService, err := u.Service.ExpireStorageAt(expiryTime) + if err != nil { + return nil, errors.Trace(err) + } + + // We create the macaroon with a random ID and random root key, which + // enables multiple clients to login as the same user and obtain separate + // macaroons without having them use the same root key. + m, err := bakeryService.NewMacaroon("", nil, []checkers.Caveat{ + // The macaroon may only be used to log in as the user + // specified by the tag passed to CreateLocalUserMacaroon. + checkers.DeclaredCaveat(usernameKey, tag.Canonical()), + }) + if err != nil { + return nil, errors.Annotate(err, "cannot create macaroon") + } + if err := addMacaroonTimeBeforeCaveat(bakeryService, m, expiryTime); err != nil { + return nil, errors.Trace(err) + } + return m, nil +} + +func (u *UserAuthenticator) authenticateMacaroons( + entityFinder EntityFinder, tag names.UserTag, req params.LoginRequest, +) (state.Entity, error) { + // Check for a valid request macaroon. + assert := map[string]string{usernameKey: tag.Canonical()} + _, err := u.Service.CheckAny(req.Macaroons, assert, checkers.New(checkers.TimeBefore)) + if err != nil { + logger.Debugf("local-login macaroon authentication failed: %v", err) + return nil, errors.Trace(common.ErrBadCreds) + } + entity, err := entityFinder.FindEntity(tag) + if errors.IsNotFound(err) { + return nil, errors.Trace(common.ErrBadCreds) + } else if err != nil { + return nil, errors.Trace(err) + } + return entity, nil +} + +// ExternalMacaroonAuthenticator performs authentication for external users using +// macaroons. If the authentication fails because provided macaroons are invalid, +// and macaroon authentiction is enabled, it will return a *common.DischargeRequiredError +// holding a macaroon to be discharged. +type ExternalMacaroonAuthenticator struct { // Service holds the service that is // used to verify macaroon authorization. - Service *bakery.Service + Service BakeryService // Macaroon guards macaroon-authentication-based access // to the APIs. Appropriate caveats will be added before @@ -55,18 +141,19 @@ IdentityLocation string } -var _ EntityAuthenticator = (*MacaroonAuthenticator)(nil) +var _ EntityAuthenticator = (*ExternalMacaroonAuthenticator)(nil) -func (m *MacaroonAuthenticator) newDischargeRequiredError(cause error) error { +func (m *ExternalMacaroonAuthenticator) newDischargeRequiredError(cause error) error { if m.Service == nil || m.Macaroon == nil { return errors.Trace(cause) } mac := m.Macaroon.Clone() - err := m.Service.AddCaveat(mac, checkers.TimeBeforeCaveat(time.Now().Add(time.Hour))) - if err != nil { + // TODO(fwereade): 2016-03-17 lp:1558657 + expiryTime := time.Now().Add(externalLoginExpiryTime) + if err := addMacaroonTimeBeforeCaveat(m.Service, mac, expiryTime); err != nil { return errors.Annotatef(err, "cannot create macaroon") } - err = m.Service.AddCaveat(mac, checkers.NeedDeclaredCaveat( + err := m.Service.AddCaveat(mac, checkers.NeedDeclaredCaveat( checkers.Caveat{ Location: m.IdentityLocation, Condition: "is-authenticated-user", @@ -84,7 +171,7 @@ // Authenticate authenticates the provided entity. If there is no macaroon provided, it will // return a *DischargeRequiredError containing a macaroon that can be used to grant access. -func (m *MacaroonAuthenticator) Authenticate(entityFinder EntityFinder, _ names.Tag, req params.LoginRequest) (state.Entity, error) { +func (m *ExternalMacaroonAuthenticator) Authenticate(entityFinder EntityFinder, _ names.Tag, req params.LoginRequest) (state.Entity, error) { declared, err := m.Service.CheckAny(req.Macaroons, nil, checkers.New(checkers.TimeBefore)) if _, ok := errors.Cause(err).(*bakery.VerificationError); ok { return nil, m.newDischargeRequiredError(err) @@ -121,3 +208,26 @@ } return entity, nil } + +func addMacaroonTimeBeforeCaveat(svc BakeryService, m *macaroon.Macaroon, t time.Time) error { + return svc.AddCaveat(m, checkers.TimeBeforeCaveat(t)) +} + +// BakeryService defines the subset of bakery.Service +// that we require for authentication. +type BakeryService interface { + AddCaveat(*macaroon.Macaroon, checkers.Caveat) error + CheckAny([]macaroon.Slice, map[string]string, checkers.Checker) (map[string]string, error) + NewMacaroon(string, []byte, []checkers.Caveat) (*macaroon.Macaroon, error) +} + +// ExpirableStorageBakeryService extends BakeryService +// with the ExpireStorageAt method so that root keys are +// removed from storage at that time. +type ExpirableStorageBakeryService interface { + BakeryService + + // ExpireStorageAt returns a new ExpirableStorageBakeryService with + // a store that will expire items added to it at the specified time. + ExpireStorageAt(time.Time) (ExpirableStorageBakeryService, error) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/authentication/user_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/authentication/user_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/authentication/user_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/authentication/user_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,10 +5,12 @@ import ( "net/http" + "time" "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" + "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/utils" gc "gopkg.in/check.v1" @@ -23,6 +25,7 @@ "github.com/juju/juju/apiserver/params" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" + coretesting "github.com/juju/juju/testing" "github.com/juju/juju/testing/factory" ) @@ -140,9 +143,81 @@ } +func (s *userAuthenticatorSuite) TestValidMacaroonUserLogin(c *gc.C) { + user := s.Factory.MakeUser(c, &factory.UserParams{ + Name: "bobbrown", + }) + macaroons := []macaroon.Slice{{&macaroon.Macaroon{}}} + service := mockBakeryService{} + + // User login + authenticator := &authentication.UserAuthenticator{Service: &service} + _, err := authenticator.Authenticate(s.State, user.Tag(), params.LoginRequest{ + Credentials: "", + Nonce: "", + Macaroons: macaroons, + }) + c.Assert(err, jc.ErrorIsNil) + + service.CheckCallNames(c, "CheckAny") + call := service.Calls()[0] + c.Assert(call.Args, gc.HasLen, 3) + c.Assert(call.Args[0], jc.DeepEquals, macaroons) + c.Assert(call.Args[1], jc.DeepEquals, map[string]string{"username": "bobbrown@local"}) + // no check for checker function, can't compare functions +} + +func (s *userAuthenticatorSuite) TestCreateLocalLoginMacaroon(c *gc.C) { + service := mockBakeryService{} + clock := coretesting.NewClock(time.Time{}) + authenticator := &authentication.UserAuthenticator{ + Service: &service, + Clock: clock, + } + + _, err := authenticator.CreateLocalLoginMacaroon(names.NewUserTag("bobbrown")) + c.Assert(err, jc.ErrorIsNil) + + service.CheckCallNames(c, "ExpireStorageAt", "NewMacaroon", "AddCaveat") + calls := service.Calls() + c.Assert(calls[0].Args, jc.DeepEquals, []interface{}{clock.Now().Add(24 * time.Hour)}) + c.Assert(calls[1].Args, jc.DeepEquals, []interface{}{ + "", []byte(nil), []checkers.Caveat{ + checkers.DeclaredCaveat("username", "bobbrown@local"), + }, + }) + c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{ + &macaroon.Macaroon{}, + checkers.TimeBeforeCaveat(clock.Now().Add(24 * time.Hour)), + }) +} + +type mockBakeryService struct { + testing.Stub +} + +func (s *mockBakeryService) AddCaveat(m *macaroon.Macaroon, caveat checkers.Caveat) error { + s.MethodCall(s, "AddCaveat", m, caveat) + return s.NextErr() +} + +func (s *mockBakeryService) CheckAny(ms []macaroon.Slice, assert map[string]string, checker checkers.Checker) (map[string]string, error) { + s.MethodCall(s, "CheckAny", ms, assert, checker) + return nil, s.NextErr() +} + +func (s *mockBakeryService) NewMacaroon(id string, key []byte, caveats []checkers.Caveat) (*macaroon.Macaroon, error) { + s.MethodCall(s, "NewMacaroon", id, key, caveats) + return &macaroon.Macaroon{}, s.NextErr() +} + +func (s *mockBakeryService) ExpireStorageAt(t time.Time) (authentication.ExpirableStorageBakeryService, error) { + s.MethodCall(s, "ExpireStorageAt", t) + return s, s.NextErr() +} + type macaroonAuthenticatorSuite struct { jujutesting.JujuConnSuite - discharger *bakerytest.Discharger // username holds the username that will be // declared in the discharger's caveats. username string @@ -150,14 +225,6 @@ var _ = gc.Suite(&macaroonAuthenticatorSuite{}) -func (s *macaroonAuthenticatorSuite) SetUpTest(c *gc.C) { - s.discharger = bakerytest.NewDischarger(nil, s.Checker) -} - -func (s *macaroonAuthenticatorSuite) TearDownTest(c *gc.C) { - s.discharger.Close() -} - func (s *macaroonAuthenticatorSuite) Checker(req *http.Request, cond, arg string) ([]checkers.Caveat, error) { return []checkers.Caveat{checkers.DeclaredCaveat("username", s.username)}, nil } @@ -207,19 +274,21 @@ }} func (s *macaroonAuthenticatorSuite) TestMacaroonAuthentication(c *gc.C) { + discharger := bakerytest.NewDischarger(nil, s.Checker) + defer discharger.Close() for i, test := range authenticateSuccessTests { c.Logf("\ntest %d; %s", i, test.about) s.username = test.dischargedUsername svc, err := bakery.NewService(bakery.NewServiceParams{ - Locator: s.discharger, + Locator: discharger, }) c.Assert(err, jc.ErrorIsNil) mac, err := svc.NewMacaroon("", nil, nil) c.Assert(err, jc.ErrorIsNil) - authenticator := &authentication.MacaroonAuthenticator{ + authenticator := &authentication.ExternalMacaroonAuthenticator{ Service: svc, - IdentityLocation: s.discharger.Location(), + IdentityLocation: discharger.Location(), Macaroon: mac, } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/backups/backups.go charm-2.2.0/src/github.com/juju/juju/apiserver/backups/backups.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/backups/backups.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/backups/backups.go 2016-04-28 06:03:34.000000000 +0000 @@ -111,6 +111,15 @@ result.Hostname = meta.Origin.Hostname result.Version = meta.Origin.Version + // TODO(wallyworld) - remove these ASAP + // These are only used by the restore CLI when re-bootstrapping. + // We will use a better solution but the way restore currently + // works, we need them and they are no longer available via + // bootstrap config. We will need to ifx how re-bootstrap deals + // with these keys to address the issue. + result.CACert = meta.CACert + result.CAPrivateKey = meta.CAPrivateKey + return result } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/handlers.go charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/handlers.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/handlers.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/handlers.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,49 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charmrevisionupdater + +import ( + "fmt" + + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/charmstore" + "github.com/juju/juju/state" +) + +// LatestCharmHandler exposes the functionality needed to deal with +// the latest info (from the store) for a charm. +type LatestCharmHandler interface { + // HandleLatest deals with the given charm info, treating it as the + // most up-to-date information for the charms most recent revision. + HandleLatest(names.ServiceTag, charmstore.CharmInfo) error +} + +type newHandlerFunc func(*state.State) (LatestCharmHandler, error) + +var registeredHandlers = map[string]newHandlerFunc{} + +// RegisterLatestCharmHandler adds the factory func for the identified +// handler to the handler registry. +func RegisterLatestCharmHandler(name string, newHandler newHandlerFunc) error { + if _, ok := registeredHandlers[name]; ok { + msg := fmt.Sprintf(`"latest charm" handler %q already registered`, name) + return errors.NewAlreadyExists(nil, msg) + } + registeredHandlers[name] = newHandler + return nil +} + +func createHandlers(st *state.State) ([]LatestCharmHandler, error) { + var handlers []LatestCharmHandler + for _, newHandler := range registeredHandlers { + handler, err := newHandler(st) + if err != nil { + return nil, errors.Trace(err) + } + handlers = append(handlers, handler) + } + return handlers, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/package_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -1,7 +1,7 @@ // Copyright 2013 Canonical Ltd. // Licensed under the AGPLv3, see LICENCE file for details. -package charmrevisionupdater_test +package charmrevisionupdater import ( stdtesting "testing" diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/testing/suite.go charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/testing/suite.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/testing/suite.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/testing/suite.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "fmt" "net/http/httptest" + "net/url" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -15,6 +16,7 @@ "gopkg.in/juju/charmstore.v5-unstable" "github.com/juju/juju/apiserver/charmrevisionupdater" + jujucharmstore "github.com/juju/juju/charmstore" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" "github.com/juju/juju/testcharms" @@ -44,7 +46,7 @@ AuthUsername: "test-user", AuthPassword: "test-password", } - handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4) + handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5) c.Assert(err, jc.ErrorIsNil) s.Handler = handler s.Server = httptest.NewServer(handler) @@ -66,9 +68,10 @@ s.jcSuite.PatchValue(&charmrepo.CacheDir, c.MkDir()) // Patch the charm repo initializer function: it is replaced with a charm // store repo pointing to the testing server. - s.jcSuite.PatchValue(&charmrevisionupdater.NewCharmStore, func(p charmrepo.NewCharmStoreParams) *charmrepo.CharmStore { - p.URL = s.Server.URL - return charmrepo.NewCharmStore(p) + s.jcSuite.PatchValue(&charmrevisionupdater.NewCharmStoreClient, func(st *state.State) (jujucharmstore.Client, error) { + csURL, err := url.Parse(s.Server.URL) + c.Assert(err, jc.ErrorIsNil) + return jujucharmstore.NewCachingClient(state.MacaroonCache{st}, csURL) }) s.charms = make(map[string]*state.Charm) } @@ -98,7 +101,13 @@ ch := testcharms.Repo.CharmDir(charmName) name := ch.Meta().Name curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, rev)) - dummy, err := s.jcSuite.State.AddCharm(ch, curl, "dummy-path", fmt.Sprintf("%s-%d-sha256", name, rev)) + info := state.CharmInfo{ + Charm: ch, + ID: curl, + StoragePath: "dummy-path", + SHA256: fmt.Sprintf("%s-%d-sha256", name, rev), + } + dummy, err := s.jcSuite.State.AddCharm(info) c.Assert(err, jc.ErrorIsNil) s.charms[name] = dummy return dummy diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,11 +6,10 @@ import ( "github.com/juju/errors" "github.com/juju/loggo" - "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/juju/charmrepo.v2-unstable" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/charmstore" "github.com/juju/juju/state" ) @@ -51,87 +50,110 @@ // UpdateLatestRevisions retrieves the latest revision information from the charm store for all deployed charms // and records this information in state. func (api *CharmRevisionUpdaterAPI) UpdateLatestRevisions() (params.ErrorResult, error) { - // First get the uuid for the environment to use when querying the charm store. - env, err := api.state.Model() - if err != nil { + if err := api.updateLatestRevisions(); err != nil { return params.ErrorResult{Error: common.ServerError(err)}, nil } - uuid := env.UUID() + return params.ErrorResult{}, nil +} - deployedCharms, err := fetchAllDeployedCharms(api.state) +func (api *CharmRevisionUpdaterAPI) updateLatestRevisions() error { + // Get the handlers to use. + handlers, err := createHandlers(api.state) if err != nil { - return params.ErrorResult{Error: common.ServerError(err)}, nil + return err } - // Look up the revision information for all the deployed charms. - curls, err := retrieveLatestCharmInfo(deployedCharms, uuid) + + // Look up the information for all the deployed charms. This is the + // "expensive" part. + latest, err := retrieveLatestCharmInfo(api.state) if err != nil { - return params.ErrorResult{Error: common.ServerError(err)}, nil + return err } - // Add the charms and latest revision info to state as charm placeholders. - for _, curl := range curls { - if err = api.state.AddStoreCharmPlaceholder(curl); err != nil { - return params.ErrorResult{Error: common.ServerError(err)}, nil + + // Process the resulting info for each charm. + for _, info := range latest { + // First, add a charm placeholder to the model for each. + if err = api.state.AddStoreCharmPlaceholder(info.LatestURL()); err != nil { + return err + } + + // Then run through the handlers. + serviceID := info.service.ServiceTag() + for _, handler := range handlers { + if err := handler.HandleLatest(serviceID, info.CharmInfo); err != nil { + return err + } } } - return params.ErrorResult{}, nil + + return nil +} + +// NewCharmStoreClient instantiates a new charm store repository. Exported so +// we can change it during testing. +var NewCharmStoreClient = func(st *state.State) (charmstore.Client, error) { + return charmstore.NewCachingClient(state.MacaroonCache{st}, nil) +} + +type latestCharmInfo struct { + charmstore.CharmInfo + service *state.Service } -// fetchAllDeployedCharms returns a map from service name to service -// and a map from service name to unit name to unit. -func fetchAllDeployedCharms(st *state.State) (map[string]*charm.URL, error) { - deployedCharms := make(map[string]*charm.URL) +// retrieveLatestCharmInfo looks up the charm store to return the charm URLs for the +// latest revision of the deployed charms. +func retrieveLatestCharmInfo(st *state.State) ([]latestCharmInfo, error) { + // First get the uuid for the environment to use when querying the charm store. + env, err := st.Model() + if err != nil { + return nil, err + } + services, err := st.AllServices() if err != nil { return nil, err } - for _, s := range services { - url, _ := s.CharmURL() - // Record the basic charm information so it can be bulk processed later to - // get the available revision numbers from the repo. - baseCharm := url.WithRevision(-1) - deployedCharms[baseCharm.String()] = baseCharm - } - return deployedCharms, nil -} -// NewCharmStore instantiates a new charm store repository. -// It is defined at top level for testing purposes. -var NewCharmStore = charmrepo.NewCharmStore + client, err := NewCharmStoreClient(st) + if err != nil { + return nil, errors.Trace(err) + } -// retrieveLatestCharmInfo looks up the charm store to return the charm URLs for the -// latest revision of the deployed charms. -func retrieveLatestCharmInfo(deployedCharms map[string]*charm.URL, uuid string) ([]*charm.URL, error) { - var curls []*charm.URL - for _, curl := range deployedCharms { + var charms []charmstore.CharmID + var resultsIndexedServices []*state.Service + for _, service := range services { + curl, _ := service.CharmURL() if curl.Schema == "local" { // Version checking for charms from local repositories is not // currently supported, since we don't yet support passing in // a path to the local repo. This may change if the need arises. continue } - curls = append(curls, curl) + + cid := charmstore.CharmID{ + URL: curl, + Channel: service.Channel(), + } + charms = append(charms, cid) + resultsIndexedServices = append(resultsIndexedServices, service) } - // Do a bulk call to get the revision info for all charms. - logger.Infof("retrieving revision information for %d charms", len(curls)) - repo := NewCharmStore(charmrepo.NewCharmStoreParams{}) - repo = repo.WithJujuAttrs(map[string]string{ - "environment_uuid": uuid, - }) - revInfo, err := repo.Latest(curls...) + results, err := charmstore.LatestCharmInfo(client, charms, env.UUID()) if err != nil { - err = errors.Annotate(err, "finding charm revision info") - logger.Infof(err.Error()) return nil, err } - var latestCurls []*charm.URL - for i, info := range revInfo { - curl := curls[i] - if info.Err == nil { - latestCurls = append(latestCurls, curl.WithRevision(info.Revision)) - } else { - logger.Errorf("retrieving charm info for %s: %v", curl, info.Err) + + var latest []latestCharmInfo + for i, result := range results { + if result.Error != nil { + logger.Errorf("retrieving charm info for %s: %v", charms[i].URL, result.Error) + continue } + service := resultsIndexedServices[i] + latest = append(latest, latestCharmInfo{ + CharmInfo: result.CharmInfo, + service: service, + }) } - return latestCurls, nil + return latest, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" + "net/url" "github.com/juju/errors" jc "github.com/juju/testing/checkers" @@ -17,6 +18,7 @@ "github.com/juju/juju/apiserver/charmrevisionupdater/testing" "github.com/juju/juju/apiserver/common" apiservertesting "github.com/juju/juju/apiserver/testing" + "github.com/juju/juju/charmstore" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" ) @@ -157,16 +159,22 @@ // Set up a charm store server that stores the request header. var header http.Header + received := false srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - header = r.Header + // the first request is the one with the UUID. + if !received { + header = r.Header + received = true + } s.Handler.ServeHTTP(w, r) })) defer srv.Close() // Point the charm repo initializer to the testing server. - s.PatchValue(&charmrevisionupdater.NewCharmStore, func(p charmrepo.NewCharmStoreParams) *charmrepo.CharmStore { - p.URL = srv.URL - return charmrepo.NewCharmStore(p) + s.PatchValue(&charmrevisionupdater.NewCharmStoreClient, func(st *state.State) (charmstore.Client, error) { + csURL, err := url.Parse(srv.URL) + c.Assert(err, jc.ErrorIsNil) + return charmstore.NewCachingClient(state.MacaroonCache{st}, csURL) }) result, err := s.charmrevisionupdater.UpdateLatestRevisions() diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/charms.go charm-2.2.0/src/github.com/juju/juju/apiserver/charms.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/charms.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/charms.go 2016-04-28 06:03:34.000000000 +0000 @@ -376,15 +376,15 @@ } bundleSHA256 := hex.EncodeToString(hash.Sum(nil)) + info := service.CharmArchive{ + ID: curl, + Charm: archive, + Data: &repackagedArchive, + Size: int64(repackagedArchive.Len()), + SHA256: bundleSHA256, + } // Store the charm archive in environment storage. - return service.StoreCharmArchive( - st, - curl, - archive, - &repackagedArchive, - int64(repackagedArchive.Len()), - bundleSHA256, - ) + return service.StoreCharmArchive(st, info) } // processGet handles a charm file GET request after authentication. @@ -412,7 +412,12 @@ // Prepare the bundle directories. name := charm.Quote(curlString) - charmArchivePath := filepath.Join(h.dataDir, "charm-get-cache", name+".zip") + charmArchivePath := filepath.Join( + h.dataDir, + "charm-get-cache", + st.ModelUUID(), + name+".zip", + ) // Check if the charm archive is already in the cache. if _, err := os.Stat(charmArchivePath); os.IsNotExist(err) { @@ -447,23 +452,23 @@ if err != nil { return errors.Annotate(err, "cannot create charm archive temp file") } - defer tempCharmArchive.Close() + defer cleanupFile(tempCharmArchive) // Use the storage to retrieve and save the charm archive. reader, _, err := storage.Get(ch.StoragePath()) if err != nil { - defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "cannot get charm from model storage") } defer reader.Close() - if _, err = io.Copy(tempCharmArchive, reader); err != nil { - defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "error processing charm archive download") } tempCharmArchive.Close() + + // Note that os.Rename won't fail if the target already exists; + // there's no problem if there's concurrent get requests for the + // same charm. if err = os.Rename(tempCharmArchive.Name(), charmArchivePath); err != nil { - defer cleanupFile(tempCharmArchive) return errors.Annotate(err, "error renaming the charm archive") } return nil @@ -473,6 +478,8 @@ // If this poses an active problem somewhere else it will be refactored in // utils and used everywhere. func cleanupFile(file *os.File) { + // Errors are ignored because it is ok for this to be called when + // the file is already closed or has been moved. file.Close() os.Remove(file.Name()) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/charms_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/charms_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/charms_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/charms_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -172,7 +172,13 @@ curl := charm.MustParseURL( fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()), ) - _, err := s.State.AddCharm(ch, curl, "dummy-storage-path", "dummy-1-sha256") + info := state.CharmInfo{ + Charm: ch, + ID: curl, + StoragePath: "dummy-storage-path", + SHA256: "dummy-1-sha256", + } + _, err := s.State.AddCharm(info) c.Assert(err, jc.ErrorIsNil) // Now try uploading the same revision and verify it gets bumped, @@ -470,7 +476,7 @@ func (s *charmsSuite) TestGetUsesCache(c *gc.C) { // Add a fake charm archive in the cache directory. - cacheDir := filepath.Join(s.DataDir(), "charm-get-cache") + cacheDir := filepath.Join(s.DataDir(), "charm-get-cache", s.State.ModelUUID()) err := os.MkdirAll(cacheDir, 0755) c.Assert(err, jc.ErrorIsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/api_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/api_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/api_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/api_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -154,7 +154,7 @@ // but this behavior is already tested in cmd/juju/status_test.go and // also tested live and it works. var scenarioStatus = ¶ms.FullStatus{ - ModelName: "dummymodel", + ModelName: "admin", Machines: map[string]params.MachineStatus{ "0": { Id: "0", diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/client.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/client.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/client.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,7 +5,6 @@ import ( "fmt" - "time" "github.com/juju/errors" "github.com/juju/loggo" @@ -16,14 +15,13 @@ "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/apiserver/service" - "github.com/juju/juju/apiserver/usermanager" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/manual" "github.com/juju/juju/instance" "github.com/juju/juju/network" "github.com/juju/juju/state" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) func init() { @@ -278,7 +276,9 @@ var result params.ProvisioningScriptResult icfg, err := InstanceConfig(c.api.state(), args.MachineId, args.Nonce, args.DataDir) if err != nil { - return result, err + return result, common.ServerError(errors.Annotate( + err, "getting instance config", + )) } // Until DisablePackageCommands is retired, for backwards @@ -291,14 +291,21 @@ icfg.EnableOSRefreshUpdate = false icfg.EnableOSUpgrade = false } else if cfg, err := c.api.stateAccessor.ModelConfig(); err != nil { - return result, err + return result, common.ServerError(errors.Annotate( + err, "getting model config", + )) } else { icfg.EnableOSUpgrade = cfg.EnableOSUpgrade() icfg.EnableOSRefreshUpdate = cfg.EnableOSRefreshUpdate() } result.Script, err = manual.ProvisioningScript(icfg) - return result, err + if err != nil { + return result, common.ServerError(errors.Annotate( + err, "getting provisioning script", + )) + } + return result, nil } // DestroyMachines removes a given set of machines. @@ -353,34 +360,6 @@ return info, nil } -// ShareModel manages allowing and denying the given user(s) access to the model. -func (c *Client) ShareModel(args params.ModifyModelUsers) (result params.ErrorResults, err error) { - var createdBy names.UserTag - var ok bool - if createdBy, ok = c.api.auth.GetAuthTag().(names.UserTag); !ok { - return result, errors.Errorf("api connection is not through a user") - } - - result = params.ErrorResults{ - Results: make([]params.ErrorResult, len(args.Changes)), - } - if len(args.Changes) == 0 { - return result, nil - } - - for i, arg := range args.Changes { - userTagString := arg.UserTag - user, err := names.ParseUserTag(userTagString) - if err != nil { - result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not share model")) - continue - } - result.Results[i].Error = common.ServerError( - usermanager.ShareModelAction(c.api.stateAccessor, c.api.stateAccessor.ModelTag(), createdBy, user, arg.Action)) - } - return result, nil -} - // ModelUserInfo returns information on all users in the model. func (c *Client) ModelUserInfo() (params.ModelUserInfoResults, error) { var results params.ModelUserInfoResults @@ -394,31 +373,21 @@ } for _, user := range users { - var lastConn *time.Time - userLastConn, err := user.LastConnection() + var result params.ModelUserInfoResult + userInfo, err := common.ModelUserInfo(user) if err != nil { - if !state.IsNeverConnectedError(err) { - return results, errors.Trace(err) - } + result.Error = common.ServerError(err) } else { - lastConn = &userLastConn + result.Result = &userInfo } - results.Results = append(results.Results, params.ModelUserInfoResult{ - Result: ¶ms.ModelUserInfo{ - UserName: user.UserName(), - DisplayName: user.DisplayName(), - CreatedBy: user.CreatedBy(), - DateCreated: user.DateCreated(), - LastConnection: lastConn, - }, - }) + results.Results = append(results.Results, result) } return results, nil } // AgentVersion returns the current version that the API server is running. func (c *Client) AgentVersion() (params.AgentVersionResult, error) { - return params.AgentVersionResult{Version: version.Current}, nil + return params.AgentVersionResult{Version: jujuversion.Current}, nil } // ModelGet implements the server-side part of the @@ -513,9 +482,10 @@ return c.api.toolsFinder.FindTools(args) } -func (c *Client) AddCharm(args params.CharmURL) error { +func (c *Client) AddCharm(args params.AddCharm) error { return service.AddCharmWithAuthorization(c.api.state(), params.AddCharmWithAuthorization{ - URL: args.URL, + URL: args.URL, + Channel: args.Channel, }) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/client_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/client_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/client_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,7 +5,6 @@ import ( "fmt" - "regexp" "sort" "strconv" "strings" @@ -14,6 +13,7 @@ "github.com/juju/errors" "github.com/juju/names" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" "gopkg.in/juju/charm.v6-unstable" @@ -37,7 +37,7 @@ "github.com/juju/juju/status" coretesting "github.com/juju/juju/testing" "github.com/juju/juju/testing/factory" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type Killer interface { @@ -96,35 +96,38 @@ ¶ms.ModelUserInfo{ UserName: owner.UserName(), DisplayName: owner.DisplayName(), + Access: "write", }, }, { localUser1, ¶ms.ModelUserInfo{ UserName: "ralphdoe@local", DisplayName: "Ralph Doe", + Access: "write", }, }, { localUser2, ¶ms.ModelUserInfo{ UserName: "samsmith@local", DisplayName: "Sam Smith", + Access: "write", }, }, { remoteUser1, ¶ms.ModelUserInfo{ UserName: "bobjohns@ubuntuone", DisplayName: "Bob Johns", + Access: "write", }, }, { remoteUser2, ¶ms.ModelUserInfo{ UserName: "nicshaw@idprovider", DisplayName: "Nic Shaw", + Access: "write", }, }, } { - r.info.CreatedBy = owner.UserName() - r.info.DateCreated = r.user.DateCreated() r.info.LastConnection = lastConnPointer(c, r.user) expected.Results = append(expected.Results, params.ModelUserInfoResult{Result: r.info}) } @@ -161,229 +164,6 @@ return modelUser } -func (s *serverSuite) TestShareModelAddMissingLocalFails(c *gc.C) { - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: names.NewLocalUserTag("foobar").String(), - Action: params.AddModelUser, - }}} - - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - expectedErr := `could not share model: user "foobar" does not exist locally: user "foobar" not found` - c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) -} - -func (s *serverSuite) TestUnshareModel(c *gc.C) { - user := s.Factory.MakeModelUser(c, nil) - _, err := s.State.ModelUser(user.UserTag()) - c.Assert(err, jc.ErrorIsNil) - - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: user.UserTag().String(), - Action: params.RemoveModelUser, - }}} - - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - c.Assert(result.OneError(), gc.IsNil) - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.IsNil) - - _, err = s.State.ModelUser(user.UserTag()) - c.Assert(errors.IsNotFound(err), jc.IsTrue) -} - -func (s *serverSuite) TestUnshareModelMissingUser(c *gc.C) { - user := names.NewUserTag("bob") - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: user.String(), - Action: params.RemoveModelUser, - }}} - - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - c.Assert(result.OneError(), gc.ErrorMatches, `could not unshare model: env user "bob@local" does not exist: transaction aborted`) - - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.NotNil) - - _, err = s.State.ModelUser(user) - c.Assert(errors.IsNotFound(err), jc.IsTrue) -} - -func (s *serverSuite) TestShareModelAddLocalUser(c *gc.C) { - user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar", NoModelUser: true}) - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: user.Tag().String(), - Action: params.AddModelUser, - }}} - - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - c.Assert(result.OneError(), gc.IsNil) - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.IsNil) - - modelUser, err := s.State.ModelUser(user.UserTag()) - c.Assert(err, jc.ErrorIsNil) - c.Assert(modelUser.UserName(), gc.Equals, user.UserTag().Canonical()) - c.Assert(modelUser.CreatedBy(), gc.Equals, "admin@local") - lastConn, err := modelUser.LastConnection() - c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) - c.Assert(lastConn, gc.Equals, time.Time{}) -} - -func (s *serverSuite) TestShareModelAddRemoteUser(c *gc.C) { - user := names.NewUserTag("foobar@ubuntuone") - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: user.String(), - Action: params.AddModelUser, - }}} - - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - c.Assert(result.OneError(), gc.IsNil) - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.IsNil) - - modelUser, err := s.State.ModelUser(user) - c.Assert(err, jc.ErrorIsNil) - c.Assert(modelUser.UserName(), gc.Equals, user.Canonical()) - c.Assert(modelUser.CreatedBy(), gc.Equals, "admin@local") - lastConn, err := modelUser.LastConnection() - c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) - c.Assert(lastConn.IsZero(), jc.IsTrue) -} - -func (s *serverSuite) TestShareModelAddUserTwice(c *gc.C) { - user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar"}) - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: user.Tag().String(), - Action: params.AddModelUser, - }}} - - _, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - c.Assert(result.OneError(), gc.ErrorMatches, "could not share model: model user \"foobar@local\" already exists") - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.ErrorMatches, "could not share model: model user \"foobar@local\" already exists") - c.Assert(result.Results[0].Error.Code, gc.Matches, params.CodeAlreadyExists) - - modelUser, err := s.State.ModelUser(user.UserTag()) - c.Assert(err, jc.ErrorIsNil) - c.Assert(modelUser.UserName(), gc.Equals, user.UserTag().Canonical()) -} - -func (s *serverSuite) TestShareModelInvalidTags(c *gc.C) { - for _, testParam := range []struct { - tag string - validTag bool - }{{ - tag: "unit-foo/0", - validTag: true, - }, { - tag: "service-foo", - validTag: true, - }, { - tag: "relation-wordpress:db mysql:db", - validTag: true, - }, { - tag: "machine-0", - validTag: true, - }, { - tag: "user@local", - validTag: false, - }, { - tag: "user-Mua^h^h^h^arh", - validTag: true, - }, { - tag: "user@", - validTag: false, - }, { - tag: "user@ubuntuone", - validTag: false, - }, { - tag: "user@ubuntuone", - validTag: false, - }, { - tag: "@ubuntuone", - validTag: false, - }, { - tag: "in^valid.", - validTag: false, - }, { - tag: "", - validTag: false, - }, - } { - var expectedErr string - errPart := `could not share model: "` + regexp.QuoteMeta(testParam.tag) + `" is not a valid ` - - if testParam.validTag { - - // The string is a valid tag, but not a user tag. - expectedErr = errPart + `user tag` - } else { - - // The string is not a valid tag of any kind. - expectedErr = errPart + `tag` - } - - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: testParam.tag, - Action: params.AddModelUser, - }}} - - _, err := s.client.ShareModel(args) - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) - } -} - -func (s *serverSuite) TestShareModelZeroArgs(c *gc.C) { - args := params.ModifyModelUsers{Changes: []params.ModifyModelUser{{}}} - - _, err := s.client.ShareModel(args) - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - expectedErr := `could not share model: "" is not a valid tag` - c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) -} - -func (s *serverSuite) TestShareModelInvalidAction(c *gc.C) { - var dance params.ModelAction = "dance" - args := params.ModifyModelUsers{ - Changes: []params.ModifyModelUser{{ - UserTag: "user-user@local", - Action: dance, - }}} - - _, err := s.client.ShareModel(args) - result, err := s.client.ShareModel(args) - c.Assert(err, jc.ErrorIsNil) - expectedErr := `unknown action "dance"` - c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) - c.Assert(result.Results, gc.HasLen, 1) - c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) -} - func (s *serverSuite) TestSetEnvironAgentVersion(c *gc.C) { args := params.SetModelAgentVersion{ Version: version.MustParse("9.8.7"), @@ -1313,8 +1093,8 @@ apiParams[0].Placement = instance.MustParsePlacement("lxc") apiParams[1].Placement = instance.MustParsePlacement("lxc:0") apiParams[1].ContainerType = instance.LXC - apiParams[2].Placement = instance.MustParsePlacement("dummymodel:invalid") - apiParams[3].Placement = instance.MustParsePlacement("dummymodel:valid") + apiParams[2].Placement = instance.MustParsePlacement("admin:invalid") + apiParams[3].Placement = instance.MustParsePlacement("admin:valid") machines, err := s.APIState.Client().AddMachines(apiParams) c.Assert(err, jc.ErrorIsNil) c.Assert(len(machines), gc.Equals, 4) @@ -1703,7 +1483,7 @@ func (s *clientSuite) TestClientAgentVersion(c *gc.C) { current := version.MustParse("1.2.0") - s.PatchValue(&version.Current, current) + s.PatchValue(&jujuversion.Current, current) result, err := s.APIState.Client().AgentVersion() c.Assert(err, jc.ErrorIsNil) c.Assert(result, gc.Equals, current) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/export_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,11 +5,6 @@ import "github.com/juju/juju/state" -var ( - RemoteParamsForMachine = remoteParamsForMachine - GetAllUnitNames = getAllUnitNames -) - // Filtering exports var ( MatchPortRanges = matchPortRanges @@ -25,8 +20,7 @@ type MachineAndContainers machineAndContainers var ( - StartSerialWaitParallel = startSerialWaitParallel - GetEnvironment = &getEnvironment + GetEnvironment = &getEnvironment ) type StateInterface stateInterface diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/instanceconfig.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/instanceconfig.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/instanceconfig.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/instanceconfig.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,14 +4,16 @@ package client import ( - "errors" "fmt" + "github.com/juju/errors" + "github.com/juju/utils/set" + + "github.com/juju/juju/api" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/cloudconfig/instancecfg" "github.com/juju/juju/controller/authentication" - "github.com/juju/juju/environs" "github.com/juju/juju/state" ) @@ -22,7 +24,7 @@ func InstanceConfig(st *state.State, machineId, nonce, dataDir string) (*instancecfg.InstanceConfig, error) { environConfig, err := st.ModelConfig() if err != nil { - return nil, err + return nil, errors.Annotate(err, "getting model config") } // Get the machine so we can get its series and arch. @@ -30,11 +32,11 @@ // an error is returned. machine, err := st.Machine(machineId) if err != nil { - return nil, err + return nil, errors.Annotate(err, "getting machine") } hc, err := machine.HardwareCharacteristics() if err != nil { - return nil, err + return nil, errors.Annotate(err, "getting machine hardware characteristics") } if hc.Arch == nil { return nil, fmt.Errorf("arch is not set for %q", machine.Tag()) @@ -47,7 +49,7 @@ } environment, err := st.Model() if err != nil { - return nil, err + return nil, errors.Annotate(err, "getting state model") } urlGetter := common.NewToolsURLGetter(environment.UUID(), st) toolsFinder := common.NewToolsFinder(st, st, urlGetter) @@ -59,46 +61,53 @@ Arch: *hc.Arch, }) if err != nil { - return nil, err + return nil, errors.Annotate(err, "finding tools") } if findToolsResult.Error != nil { - return nil, findToolsResult.Error + return nil, errors.Annotate(findToolsResult.Error, "finding tools") } tools := findToolsResult.List[0] - // Find the API endpoints. - env, err := environs.New(environConfig) + // Get the API connection info; attempt all API addresses. + apiHostPorts, err := st.APIHostPorts() if err != nil { - return nil, err + return nil, errors.Annotate(err, "getting API addresses") } - apiInfo, err := environs.APIInfo(env) - if err != nil { - return nil, err + apiAddrs := make(set.Strings) + for _, hostPorts := range apiHostPorts { + for _, hp := range hostPorts { + apiAddrs.Add(hp.NetAddr()) + } + } + apiInfo := &api.Info{ + Addrs: apiAddrs.SortedValues(), + CACert: st.CACert(), + ModelTag: st.ModelTag(), } auth := authentication.NewAuthenticator(st.MongoConnectionInfo(), apiInfo) mongoInfo, apiInfo, err := auth.SetupAuthentication(machine) if err != nil { - return nil, err + return nil, errors.Annotate(err, "setting up machine authentication") } // Find requested networks. networks, err := machine.RequestedNetworks() if err != nil { - return nil, err + return nil, errors.Annotate(err, "getting requested networks for machine") } // Figure out if secure connections are supported. info, err := st.StateServingInfo() if err != nil { - return nil, err + return nil, errors.Annotate(err, "getting state serving info") } secureServerConnection := info.CAPrivateKey != "" - icfg, err := instancecfg.NewInstanceConfig(machineId, nonce, env.Config().ImageStream(), machine.Series(), "", + icfg, err := instancecfg.NewInstanceConfig(machineId, nonce, environConfig.ImageStream(), machine.Series(), "", secureServerConnection, networks, mongoInfo, apiInfo, ) if err != nil { - return nil, err + return nil, errors.Annotate(err, "initializing instance config") } if dataDir != "" { icfg.DataDir = dataDir @@ -106,7 +115,7 @@ icfg.Tools = tools err = instancecfg.FinishInstanceConfig(icfg, environConfig) if err != nil { - return nil, err + return nil, errors.Annotate(err, "finishing instance config") } return icfg, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/instanceconfig_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/instanceconfig_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/instanceconfig_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/instanceconfig_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -116,5 +116,5 @@ machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams}) c.Assert(err, jc.ErrorIsNil) _, err = client.InstanceConfig(s.State, machines[0].Machine, apiParams.Nonce, "") - c.Assert(err, gc.ErrorMatches, coretools.ErrNoMatches.Error()) + c.Assert(err, gc.ErrorMatches, "finding tools: "+coretools.ErrNoMatches.Error()) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/perm_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/perm_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/perm_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/perm_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -9,6 +9,7 @@ "github.com/juju/errors" "github.com/juju/names" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" "gopkg.in/juju/charm.v6-unstable" @@ -16,10 +17,11 @@ "github.com/juju/juju/api/annotations" "github.com/juju/juju/api/service" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/charmstore" "github.com/juju/juju/constraints" "github.com/juju/juju/rpc" "github.com/juju/juju/state" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type permSuite struct { @@ -349,7 +351,9 @@ func opClientServiceSetCharm(c *gc.C, st api.Connection, mst *state.State) (func(), error) { cfg := service.SetCharmConfig{ ServiceName: "nosuch", - CharmUrl: "local:quantal/wordpress", + CharmID: charmstore.CharmID{ + URL: charm.MustParseURL("local:quantal/wordpress"), + }, } err := service.NewClient(st).SetCharm(cfg) if params.IsCodeNotFound(err) { @@ -430,7 +434,7 @@ if err != nil { return func() {}, err } - err = st.Client().SetModelAgentVersion(version.Current) + err = st.Client().SetModelAgentVersion(jujuversion.Current) if err != nil { return func() {}, err } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/run.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/run.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/run.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/run.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,238 +0,0 @@ -// Copyright 2013 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package client - -import ( - "fmt" - "path/filepath" - "sort" - "sync" - "time" - - "github.com/juju/errors" - "github.com/juju/utils" - "github.com/juju/utils/clock" - "github.com/juju/utils/set" - "github.com/juju/utils/ssh" - - "github.com/juju/juju/agent" - "github.com/juju/juju/apiserver/common" - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/network" - "github.com/juju/juju/state" -) - -// remoteParamsForMachine returns a filled in RemoteExec instance -// based on the machine, command and timeout params. If the machine -// does not have an internal address, the Host is empty. This is caught -// by the function that actually tries to execute the command. -func remoteParamsForMachine(machine *state.Machine, command string, timeout time.Duration) *RemoteExec { - // magic boolean parameters are bad :-( - address, ok := network.SelectInternalAddress(machine.Addresses(), false) - execParams := &RemoteExec{ - ExecParams: ssh.ExecParams{ - Command: command, - Timeout: timeout, - }, - MachineId: machine.Id(), - } - if ok { - execParams.Host = fmt.Sprintf("ubuntu@%s", address.Value) - } - return execParams -} - -// getAllUnitNames returns a sequence of valid Unit objects from state. If any -// of the service names or unit names are not found, an error is returned. -func getAllUnitNames(st *state.State, units, services []string) (result []*state.Unit, err error) { - unitsSet := set.NewStrings(units...) - for _, name := range services { - service, err := st.Service(name) - if err != nil { - return nil, err - } - units, err := service.AllUnits() - if err != nil { - return nil, err - } - for _, unit := range units { - unitsSet.Add(unit.Name()) - } - } - for _, unitName := range unitsSet.Values() { - unit, err := st.Unit(unitName) - if err != nil { - return nil, err - } - // We only operate on units that have an assigned machine. - if _, err := unit.AssignedMachineId(); err != nil { - return nil, err - } - result = append(result, unit) - } - return result, nil -} - -func (c *Client) getDataDir() string { - dataResource, ok := c.api.resources.Get("dataDir").(common.StringResource) - if !ok { - return "" - } - return dataResource.String() -} - -// Run the commands specified on the machines identified through the -// list of machines, units and services. -func (c *Client) Run(run params.RunParams) (results params.RunResults, err error) { - if err := c.check.ChangeAllowed(); err != nil { - return params.RunResults{}, errors.Trace(err) - } - units, err := getAllUnitNames(c.api.state(), run.Units, run.Services) - if err != nil { - return results, err - } - // We want to create a RemoteExec for each unit and each machine. - // If we have both a unit and a machine request, we run it twice, - // once for the unit inside the exec context using juju-run, and - // the other outside the context just using bash. - var params []*RemoteExec - var quotedCommands = utils.ShQuote(run.Commands) - for _, unit := range units { - // We know that the unit is both a principal unit, and that it has an - // assigned machine. - machineId, _ := unit.AssignedMachineId() - machine, err := c.api.stateAccessor.Machine(machineId) - if err != nil { - return results, err - } - command := fmt.Sprintf("juju-run %s %s", unit.Name(), quotedCommands) - execParam := remoteParamsForMachine(machine, command, run.Timeout) - execParam.UnitId = unit.Name() - params = append(params, execParam) - } - for _, machineId := range run.Machines { - machine, err := c.api.stateAccessor.Machine(machineId) - if err != nil { - return results, err - } - command := fmt.Sprintf("juju-run --no-context %s", quotedCommands) - execParam := remoteParamsForMachine(machine, command, run.Timeout) - params = append(params, execParam) - } - return ParallelExecute(c.getDataDir(), params), nil -} - -// RunOnAllMachines attempts to run the specified command on all the machines. -func (c *Client) RunOnAllMachines(run params.RunParams) (params.RunResults, error) { - if err := c.check.ChangeAllowed(); err != nil { - return params.RunResults{}, errors.Trace(err) - } - machines, err := c.api.stateAccessor.AllMachines() - if err != nil { - return params.RunResults{}, err - } - var params []*RemoteExec - quotedCommands := utils.ShQuote(run.Commands) - command := fmt.Sprintf("juju-run --no-context %s", quotedCommands) - for _, machine := range machines { - params = append(params, remoteParamsForMachine(machine, command, run.Timeout)) - } - return ParallelExecute(c.getDataDir(), params), nil -} - -// RemoteExec extends the standard ssh.ExecParams by providing the machine and -// perhaps the unit ids. These are then returned in the params.RunResult return -// values. -type RemoteExec struct { - ssh.ExecParams - MachineId string - UnitId string -} - -// ParallelExecute executes all of the requests defined in the params, -// using the system identity stored in the dataDir. -func ParallelExecute(dataDir string, args []*RemoteExec) params.RunResults { - var results params.RunResults - results.Results = make([]params.RunResult, len(args), len(args)) - - identity := filepath.Join(dataDir, agent.SystemIdentity) - for i, arg := range args { - arg.ExecParams.IdentityFile = identity - - results.Results[i] = params.RunResult{ - MachineId: arg.MachineId, - UnitId: arg.UnitId, - } - } - - startSerialWaitParallel(args, &results, ssh.StartCommandOnMachine, waitOnCommand) - - // TODO(ericsnow) lp:1517076 - // Why do we sort these? Shouldn't we keep them - // in the same order that they were requested? - sort.Sort(MachineOrder(results.Results)) - return results -} - -// startSerialWaitParallel start a command for each RemoteExec, one at -// a time and then waits for all the results asynchronously. -// -// We do this because ssh.StartCommandOnMachine() relies on os/exec.Cmd, -// which in turn relies on fork+exec. That means every copy of the -// command we run will require a separate fork. This can be a problem -// for controllers with low resources or in environments with many -// machines. -// -// Note that when the start operation completes, the memory of the -// forked process will have already been replaced with that of the -// exec'ed command. This is a relatively quick operation relative to -// the wait operation. That is why start-serially-and-wait-in-parallel -// is a viable approach. -func startSerialWaitParallel( - args []*RemoteExec, - results *params.RunResults, - start func(params ssh.ExecParams) (*ssh.RunningCmd, error), - wait func(wg *sync.WaitGroup, cmd *ssh.RunningCmd, result *params.RunResult, cancel <-chan struct{}), -) { - var wg sync.WaitGroup - for i, arg := range args { - logger.Debugf("exec on %s: %#v", arg.MachineId, *arg) - - cancel := make(chan struct{}) - go func(d time.Duration) { - <-clock.WallClock.After(d) - close(cancel) - }(arg.Timeout) - - // Start the commands serially... - cmd, err := start(arg.ExecParams) - if err != nil { - results.Results[i].Error = err.Error() - continue - } - - wg.Add(1) - // ...but wait for them in parallel. - go wait(&wg, cmd, &results.Results[i], cancel) - } - wg.Wait() -} - -func waitOnCommand(wg *sync.WaitGroup, cmd *ssh.RunningCmd, result *params.RunResult, cancel <-chan struct{}) { - defer wg.Done() - response, err := cmd.WaitWithCancel(cancel) - logger.Debugf("response from %s: %#v (err:%v)", result.MachineId, response, err) - result.ExecResponse = response - if err != nil { - result.Error = err.Error() - } -} - -// MachineOrder is used to provide the api to sort the results by the machine -// id. -type MachineOrder []params.RunResult - -func (a MachineOrder) Len() int { return len(a) } -func (a MachineOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a MachineOrder) Less(i, j int) bool { return a[i].MachineId < a[j].MachineId } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/run_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/run_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/run_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/run_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,512 +0,0 @@ -// Copyright 2013 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package client_test - -import ( - "fmt" - "sync" - "time" - - "github.com/juju/errors" - gitjujutesting "github.com/juju/testing" - jc "github.com/juju/testing/checkers" - "github.com/juju/utils/exec" - "github.com/juju/utils/ssh" - gc "gopkg.in/check.v1" - - "github.com/juju/juju/apiserver/client" - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/network" - "github.com/juju/juju/rpc" - "github.com/juju/juju/state" - "github.com/juju/juju/testing" -) - -type runSuite struct { - baseSuite -} - -var _ = gc.Suite(&runSuite{}) - -func (s *runSuite) addMachine(c *gc.C) *state.Machine { - machine, err := s.State.AddMachine("quantal", state.JobHostUnits) - c.Assert(err, jc.ErrorIsNil) - return machine -} - -func (s *runSuite) addMachineWithAddress(c *gc.C, address string) *state.Machine { - machine := s.addMachine(c) - machine.SetProviderAddresses(network.NewAddress(address)) - return machine -} - -func (s *runSuite) TestRemoteParamsForMachinePopulates(c *gc.C) { - machine := s.addMachine(c) - result := client.RemoteParamsForMachine(machine, "command", time.Minute) - c.Assert(result.Command, gc.Equals, "command") - c.Assert(result.Timeout, gc.Equals, time.Minute) - c.Assert(result.MachineId, gc.Equals, machine.Id()) - // Now an empty host isn't particularly useful, but the machine doesn't - // have an address to use. - c.Assert(machine.Addresses(), gc.HasLen, 0) - c.Assert(result.Host, gc.Equals, "") -} - -func (s *runSuite) TestRemoteParamsForMachinePopulatesWithAddress(c *gc.C) { - machine := s.addMachineWithAddress(c, "10.3.2.1") - - result := client.RemoteParamsForMachine(machine, "command", time.Minute) - c.Assert(result.Command, gc.Equals, "command") - c.Assert(result.Timeout, gc.Equals, time.Minute) - c.Assert(result.MachineId, gc.Equals, machine.Id()) - c.Assert(result.Host, gc.Equals, "ubuntu@10.3.2.1") -} - -func (s *runSuite) addUnit(c *gc.C, service *state.Service) *state.Unit { - unit, err := service.AddUnit() - c.Assert(err, jc.ErrorIsNil) - err = unit.AssignToNewMachine() - c.Assert(err, jc.ErrorIsNil) - mId, err := unit.AssignedMachineId() - c.Assert(err, jc.ErrorIsNil) - machine, err := s.State.Machine(mId) - c.Assert(err, jc.ErrorIsNil) - machine.SetProviderAddresses(network.NewAddress("10.3.2.1")) - return unit -} - -func (s *runSuite) TestGetAllUnitNames(c *gc.C) { - charm := s.AddTestingCharm(c, "dummy") - owner := s.AdminUserTag(c) - magic, err := s.State.AddService(state.AddServiceArgs{Name: "magic", Owner: owner.String(), Charm: charm}) - s.addUnit(c, magic) - s.addUnit(c, magic) - - notAssigned, err := s.State.AddService(state.AddServiceArgs{Name: "not-assigned", Owner: owner.String(), Charm: charm}) - c.Assert(err, jc.ErrorIsNil) - _, err = notAssigned.AddUnit() - c.Assert(err, jc.ErrorIsNil) - - _, err = s.State.AddService(state.AddServiceArgs{Name: "no-units", Owner: owner.String(), Charm: charm}) - c.Assert(err, jc.ErrorIsNil) - - wordpress, err := s.State.AddService(state.AddServiceArgs{Name: "wordpress", Owner: owner.String(), Charm: s.AddTestingCharm(c, "wordpress")}) - c.Assert(err, jc.ErrorIsNil) - wordpress0 := s.addUnit(c, wordpress) - _, err = s.State.AddService(state.AddServiceArgs{Name: "logging", Owner: owner.String(), Charm: s.AddTestingCharm(c, "logging")}) - c.Assert(err, jc.ErrorIsNil) - - eps, err := s.State.InferEndpoints("logging", "wordpress") - c.Assert(err, jc.ErrorIsNil) - rel, err := s.State.AddRelation(eps...) - c.Assert(err, jc.ErrorIsNil) - ru, err := rel.Unit(wordpress0) - c.Assert(err, jc.ErrorIsNil) - err = ru.EnterScope(nil) - c.Assert(err, jc.ErrorIsNil) - - for i, test := range []struct { - message string - expected []string - units []string - services []string - error string - }{{ - message: "no units, expected nil slice", - }, { - message: "asking for a unit that isn't there", - units: []string{"foo/0"}, - error: `unit "foo/0" not found`, - }, { - message: "asking for a service that isn't there", - services: []string{"foo"}, - error: `service "foo" not found`, - }, { - message: "service with no units is not really an error", - services: []string{"no-units"}, - }, { - message: "A service with units not assigned is an error", - services: []string{"not-assigned"}, - error: `unit "not-assigned/0" is not assigned to a machine`, - }, { - message: "A service with units", - services: []string{"magic"}, - expected: []string{"magic/0", "magic/1"}, - }, { - message: "Asking for just a unit", - units: []string{"magic/0"}, - expected: []string{"magic/0"}, - }, { - message: "Asking for just a subordinate unit", - units: []string{"logging/0"}, - expected: []string{"logging/0"}, - }, { - message: "Asking for a unit, and the service", - services: []string{"magic"}, - units: []string{"magic/0"}, - expected: []string{"magic/0", "magic/1"}, - }} { - c.Logf("%v: %s", i, test.message) - result, err := client.GetAllUnitNames(s.State, test.units, test.services) - if test.error == "" { - c.Check(err, jc.ErrorIsNil) - var units []string - for _, unit := range result { - units = append(units, unit.Name()) - } - c.Check(units, jc.SameContents, test.expected) - } else { - c.Check(err, gc.ErrorMatches, test.error) - } - } -} - -func (s *runSuite) mockSSH(c *gc.C, cmd string) { - gitjujutesting.PatchExecutable(c, s, "ssh", cmd) - gitjujutesting.PatchExecutable(c, s, "scp", cmd) - client, _ := ssh.NewOpenSSHClient() - s.PatchValue(&ssh.DefaultClient, client) -} - -func (s *runSuite) TestParallelExecuteErrorsOnBlankHost(c *gc.C) { - s.mockSSH(c, echoInputShowArgs) - - params := []*client.RemoteExec{ - { - ExecParams: ssh.ExecParams{ - Command: "foo", - Timeout: testing.LongWait, - }, - }, - } - - runResults := client.ParallelExecute("/some/dir", params) - c.Assert(runResults.Results, gc.HasLen, 1) - result := runResults.Results[0] - c.Assert(result.Error, gc.Equals, "missing host address") -} - -func (s *runSuite) TestParallelExecuteAddsIdentity(c *gc.C) { - s.mockSSH(c, echoInputShowArgs) - - params := []*client.RemoteExec{ - { - ExecParams: ssh.ExecParams{ - Host: "localhost", - Command: "foo", - Timeout: testing.LongWait, - }, - }, - } - - runResults := client.ParallelExecute("/some/dir", params) - c.Assert(runResults.Results, gc.HasLen, 1) - result := runResults.Results[0] - c.Assert(result.Error, gc.Equals, "") - c.Assert(string(result.Stderr), jc.Contains, "system-identity") -} - -func (s *runSuite) TestParallelExecuteCopiesAcrossMachineAndUnit(c *gc.C) { - s.mockSSH(c, echoInputShowArgs) - - params := []*client.RemoteExec{ - { - ExecParams: ssh.ExecParams{ - Host: "localhost", - Command: "foo", - Timeout: testing.LongWait, - }, - MachineId: "machine-id", - UnitId: "unit-id", - }, - } - - runResults := client.ParallelExecute("/some/dir", params) - c.Assert(runResults.Results, gc.HasLen, 1) - result := runResults.Results[0] - c.Assert(result.Error, gc.Equals, "") - c.Assert(result.MachineId, gc.Equals, "machine-id") - c.Assert(result.UnitId, gc.Equals, "unit-id") -} - -func (s *runSuite) TestRunOnAllMachines(c *gc.C) { - // Make three machines. - s.addMachineWithAddress(c, "10.3.2.1") - s.addMachineWithAddress(c, "10.3.2.2") - s.addMachineWithAddress(c, "10.3.2.3") - - s.mockSSH(c, echoInput) - - // hmm... this seems to be going through the api client, and from there - // through to the apiserver implementation. Not ideal, but it is how the - // other client tests are written. - client := s.APIState.Client() - results, err := client.RunOnAllMachines("hostname", testing.LongWait) - c.Assert(err, jc.ErrorIsNil) - c.Assert(results, gc.HasLen, 3) - - var expectedResults []params.RunResult - for i := 0; i < 3; i++ { - expectedResults = append(expectedResults, - params.RunResult{ - ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[0])}, - - MachineId: fmt.Sprint(i), - }) - } - - c.Check(results, jc.DeepEquals, expectedResults) - c.Check(string(results[0].Stdout), gc.Equals, expectedCommand[0]) - c.Check(string(results[1].Stdout), gc.Equals, expectedCommand[0]) - c.Check(string(results[2].Stdout), gc.Equals, expectedCommand[0]) -} - -func (s *runSuite) AssertBlocked(c *gc.C, err error, msg string) { - c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err)) - c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ - Message: msg, - Code: "operation is blocked", - }) -} - -func (s *runSuite) TestBlockRunOnAllMachines(c *gc.C) { - // Make three machines. - s.addMachineWithAddress(c, "10.3.2.1") - s.addMachineWithAddress(c, "10.3.2.2") - s.addMachineWithAddress(c, "10.3.2.3") - - s.mockSSH(c, echoInput) - - // block all changes - s.BlockAllChanges(c, "TestBlockRunOnAllMachines") - _, err := s.APIState.Client().RunOnAllMachines("hostname", testing.LongWait) - s.AssertBlocked(c, err, "TestBlockRunOnAllMachines") -} - -func (s *runSuite) TestRunMachineAndService(c *gc.C) { - // Make three machines. - s.addMachineWithAddress(c, "10.3.2.1") - - charm := s.AddTestingCharm(c, "dummy") - owner := s.Factory.MakeUser(c, nil).Tag() - magic, err := s.State.AddService(state.AddServiceArgs{Name: "magic", Owner: owner.String(), Charm: charm}) - c.Assert(err, jc.ErrorIsNil) - s.addUnit(c, magic) - s.addUnit(c, magic) - - s.mockSSH(c, echoInput) - - // hmm... this seems to be going through the api client, and from there - // through to the apiserver implementation. Not ideal, but it is how the - // other client tests are written. - client := s.APIState.Client() - results, err := client.Run( - params.RunParams{ - Commands: "hostname", - Timeout: testing.LongWait, - Machines: []string{"0"}, - Services: []string{"magic"}, - }) - c.Assert(err, jc.ErrorIsNil) - c.Assert(results, gc.HasLen, 3) - - expectedResults := []params.RunResult{ - { - ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[0])}, - MachineId: "0", - }, - { - ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[1])}, - MachineId: "1", - UnitId: "magic/0", - }, - { - ExecResponse: exec.ExecResponse{Stdout: []byte(expectedCommand[2])}, - MachineId: "2", - UnitId: "magic/1", - }, - } - - c.Assert(results, jc.DeepEquals, expectedResults) -} - -func (s *runSuite) TestBlockRunMachineAndService(c *gc.C) { - // Make three machines. - s.addMachineWithAddress(c, "10.3.2.1") - - charm := s.AddTestingCharm(c, "dummy") - owner := s.Factory.MakeUser(c, nil).Tag() - magic, err := s.State.AddService(state.AddServiceArgs{Name: "magic", Owner: owner.String(), Charm: charm}) - c.Assert(err, jc.ErrorIsNil) - s.addUnit(c, magic) - s.addUnit(c, magic) - - s.mockSSH(c, echoInput) - - // hmm... this seems to be going through the api client, and from there - // through to the apiserver implementation. Not ideal, but it is how the - // other client tests are written. - client := s.APIState.Client() - - // block all changes - s.BlockAllChanges(c, "TestBlockRunMachineAndService") - _, err = client.Run( - params.RunParams{ - Commands: "hostname", - Timeout: testing.LongWait, - Machines: []string{"0"}, - Services: []string{"magic"}, - }) - s.AssertBlocked(c, err, "TestBlockRunMachineAndService") -} - -func (s *runSuite) TestStartSerialWaitParallel(c *gc.C) { - st := starter{serialChecker{c: c, block: make(chan struct{})}} - w := waiter{concurrentChecker{c: c, block: make(chan struct{})}} - count := 4 - w.started.Add(count) - st.finished.Add(count) - args := make([]*client.RemoteExec, count) - for i := range args { - args[i] = &client.RemoteExec{ - ExecParams: ssh.ExecParams{Timeout: testing.LongWait}, - } - } - - results := ¶ms.RunResults{} - results.Results = make([]params.RunResult, count) - - // run this in a goroutine so we can futz with things asynchronously. - go client.StartSerialWaitParallel(args, results, st.start, w.wait) - - // ok, so we give the function some time to run... if we are running start - // in goroutines, this would give them time to do their thing. - <-time.After(testing.ShortWait) - - // if start is being run synchronously, then only one of the start functions - // should have been called by now. - c.Assert(st.count, gc.Equals, 1) - - // good, now let's unblock start and let'em fly - st.unblock() - - // wait for the start functions to complete - select { - case <-st.waitFinish(): - // good, all start functions called. - case <-time.After(testing.ShortWait): - c.Fatalf("timed out waiting for start functions to be called.") - } - - // wait for the wait functions to run their startups - select { - case <-w.waitStarted(): - // good, all start functions called. - case <-time.After(testing.ShortWait): - c.Fatalf("Timed out waiting for start functions to be called. Start functions probably not called as goroutines.") - } - w.unblock() -} - -type starter struct { - serialChecker -} - -func (s *starter) start(_ ssh.ExecParams) (*ssh.RunningCmd, error) { - s.called() - return nil, nil -} - -type waiter struct { - concurrentChecker -} - -func (w *waiter) wait(wg *sync.WaitGroup, _ *ssh.RunningCmd, _ *params.RunResult, _ <-chan struct{}) { - defer wg.Done() - w.called() -} - -// serialChecker is a type that lets us check that a function is called -// serially, not concurrently. -type serialChecker struct { - c *gc.C - mu sync.Mutex - block chan struct{} - count int - finished sync.WaitGroup -} - -// unblock unblocks the called method. -func (s *serialChecker) unblock() { - close(s.block) -} - -// called registers tha this function has been called. It will block until -// unblock is called, or will timeout after a LongWait. -func (s *serialChecker) called() { - defer s.finished.Done() - // make sure we serialize access to the struct - s.mu.Lock() - // log that we've been called - s.count++ - s.mu.Unlock() - select { - case <-s.block: - case <-time.After(testing.LongWait): - s.c.Fatalf("time out waiting for unblock") - } -} - -// waitFinish waits on the finished waitgroup, and closes the returned channel -// when it is. -func (s *serialChecker) waitFinish() <-chan struct{} { - done := make(chan struct{}) - go func() { - s.finished.Wait() - close(done) - }() - return done -} - -// concurrentChecker is a type to allow us to check that the a function is being -// called concurrently, not serially. -type concurrentChecker struct { - c *gc.C - block chan struct{} - started sync.WaitGroup -} - -// unblock unblocks the functions currently blocked by this type. -func (c *concurrentChecker) unblock() { - close(c.block) -} - -// waitStarted waits on the started waitgroup, and closes the returned channel -// when it is. -func (c *concurrentChecker) waitStarted() <-chan struct{} { - done := make(chan struct{}) - go func() { - c.started.Wait() - close(done) - }() - return done -} - -// wait is the function we pass to startSerialWaitParallel. It gives each -// goroutine an index, so we can keep track of which ones were run when, and -// blocks until released by the unblock function. -func (c *concurrentChecker) called() { - // signal that we've started - c.started.Done() - - // now we block on the block channel, waiting to be unblocked. This way, if - // we don't get the started waitgroup finished, we know these functions - // weren't run concurrently. - select { - case <-c.block: - // good, all functions were run and we got unblocked. - case <-time.After(testing.LongWait): - c.c.Fatalf("timed out waiting to unblock") - } -} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/run_unix_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/run_unix_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/run_unix_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/run_unix_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -// Copyright 2013 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -// +build !windows - -package client_test - -var expectedCommand = []string{ - "juju-run --no-context 'hostname'\n", - "juju-run magic/0 'hostname'\n", - "juju-run magic/1 'hostname'\n", -} - -var echoInputShowArgs = `#!/bin/bash -# Write the args to stderr -echo "$*" >&2 -# And echo stdin to stdout -while read line -do echo $line -done <&0 -` - -var echoInput = `#!/bin/bash -# And echo stdin to stdout -while read line -do echo $line -done <&0 -` diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/run_windows_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/run_windows_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/run_windows_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/run_windows_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -// Copyright 2013 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -// +build windows - -package client_test - -var expectedCommand = []string{ - "juju-run --no-context 'hostname'\r\n", - "juju-run magic/0 'hostname'\r\n", - "juju-run magic/1 'hostname'\r\n", -} - -var echoInputShowArgs = `@echo off -echo %* 1>&2 - -setlocal -for /F "tokens=*" %%a in ('more') do ( - echo %%a -) -` - -var echoInput = ` -@echo off -setlocal -for /F "tokens=*" %%a in ('more') do ( - echo %%a -) -` diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/state.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/state.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/state.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/state.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,6 +5,7 @@ import ( "github.com/juju/names" + "github.com/juju/version" "gopkg.in/juju/charm.v6-unstable" "github.com/juju/juju/constraints" @@ -13,7 +14,6 @@ "github.com/juju/juju/network" "github.com/juju/juju/state" "github.com/juju/juju/status" - "github.com/juju/juju/version" ) // Unit represents a state.Unit. diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/status.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/status.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/status.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/status.go 2016-04-28 06:03:34.000000000 +0000 @@ -300,23 +300,6 @@ return "", nil } -// Status is a stub version of FullStatus that was introduced in 1.16 -func (c *Client) Status() (params.LegacyStatus, error) { - var legacyStatus params.LegacyStatus - status, err := c.FullStatus(params.StatusParams{}) - if err != nil { - return legacyStatus, err - } - - legacyStatus.Machines = make(map[string]params.LegacyMachineStatus) - for machineName, machineStatus := range status.Machines { - legacyStatus.Machines[machineName] = params.LegacyMachineStatus{ - InstanceId: string(machineStatus.InstanceId), - } - } - return legacyStatus, nil -} - type statusContext struct { // machines: top-level machine id -> list of machines nested in // this machine. @@ -326,7 +309,7 @@ relations map[string][]*state.Relation units map[string]map[string]*state.Unit networks map[string]*state.Network - latestCharms map[charm.URL]string + latestCharms map[charm.URL]*state.Charm } // fetchMachines returns a map from top level machine id to machines, where machines[0] is the host @@ -366,11 +349,11 @@ func fetchAllServicesAndUnits( st stateInterface, matchAny bool, -) (map[string]*state.Service, map[string]map[string]*state.Unit, map[charm.URL]string, error) { +) (map[string]*state.Service, map[string]map[string]*state.Unit, map[charm.URL]*state.Charm, error) { svcMap := make(map[string]*state.Service) unitMap := make(map[string]map[string]*state.Unit) - latestCharms := make(map[charm.URL]string) + latestCharms := make(map[charm.URL]*state.Charm) services, err := st.AllServices() if err != nil { return nil, nil, nil, err @@ -391,7 +374,7 @@ // the latest store revision can be looked up. charmURL, _ := s.CharmURL() if charmURL.Schema == "cs" { - latestCharms[*charmURL.WithRevision(-1)] = "" + latestCharms[*charmURL.WithRevision(-1)] = nil } } } @@ -403,8 +386,9 @@ if err != nil { return nil, nil, nil, err } - latestCharms[baseURL] = ch.String() + latestCharms[baseURL] = ch } + return svcMap, unitMap, latestCharms, nil } @@ -614,26 +598,30 @@ return servicesMap } -func (context *statusContext) processService(service *state.Service) (processedStatus params.ServiceStatus) { +func (context *statusContext) processService(service *state.Service) params.ServiceStatus { serviceCharmURL, _ := service.CharmURL() - processedStatus.Charm = serviceCharmURL.String() - processedStatus.Exposed = service.IsExposed() - processedStatus.Life = processLife(service) - - latestCharm, ok := context.latestCharms[*serviceCharmURL.WithRevision(-1)] - if ok && latestCharm != serviceCharmURL.String() { - processedStatus.CanUpgradeTo = latestCharm + var processedStatus = params.ServiceStatus{ + Charm: serviceCharmURL.String(), + Exposed: service.IsExposed(), + Life: processLife(service), + } + + if latestCharm, ok := context.latestCharms[*serviceCharmURL.WithRevision(-1)]; ok && latestCharm != nil { + if latestCharm.Revision() > serviceCharmURL.Revision { + processedStatus.CanUpgradeTo = latestCharm.String() + } } + var err error processedStatus.Relations, processedStatus.SubordinateTo, err = context.processServiceRelations(service) if err != nil { processedStatus.Err = err - return + return processedStatus } networks, err := service.Networks() if err != nil { processedStatus.Err = err - return + return processedStatus } var cons constraints.Value if service.IsPrincipal() { @@ -641,7 +629,7 @@ cons, err = service.Constraints() if err != nil { processedStatus.Err = err - return + return processedStatus } } // TODO(dimitern): Drop support for this in a follow-up. @@ -660,7 +648,7 @@ serviceStatus, err := service.Status() if err != nil { processedStatus.Err = err - return + return processedStatus } processedStatus.Status.Status = serviceStatus.Status processedStatus.Status.Info = serviceStatus.Message diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client/status_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client/status_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client/status_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client/status_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,9 +7,14 @@ jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/apiserver/charmrevisionupdater" + "github.com/juju/juju/apiserver/charmrevisionupdater/testing" "github.com/juju/juju/apiserver/client" + "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" + apiservertesting "github.com/juju/juju/apiserver/testing" "github.com/juju/juju/instance" + jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" "github.com/juju/juju/testing/factory" ) @@ -34,7 +39,7 @@ client := s.APIState.Client() status, err := client.Status(nil) c.Assert(err, jc.ErrorIsNil) - c.Check(status.ModelName, gc.Equals, "dummymodel") + c.Check(status.ModelName, gc.Equals, "admin") c.Check(status.Services, gc.HasLen, 0) c.Check(status.Machines, gc.HasLen, 1) c.Check(status.Networks, gc.HasLen, 0) @@ -148,3 +153,64 @@ } } } + +type statusUpgradeUnitSuite struct { + testing.CharmSuite + jujutesting.JujuConnSuite + + charmrevisionupdater *charmrevisionupdater.CharmRevisionUpdaterAPI + resources *common.Resources + authoriser apiservertesting.FakeAuthorizer +} + +var _ = gc.Suite(&statusUpgradeUnitSuite{}) + +func (s *statusUpgradeUnitSuite) SetUpSuite(c *gc.C) { + s.JujuConnSuite.SetUpSuite(c) + s.CharmSuite.SetUpSuite(c, &s.JujuConnSuite) +} + +func (s *statusUpgradeUnitSuite) TearDownSuite(c *gc.C) { + s.CharmSuite.TearDownSuite(c) + s.JujuConnSuite.TearDownSuite(c) +} + +func (s *statusUpgradeUnitSuite) SetUpTest(c *gc.C) { + s.JujuConnSuite.SetUpTest(c) + s.CharmSuite.SetUpTest(c) + s.resources = common.NewResources() + s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() }) + s.authoriser = apiservertesting.FakeAuthorizer{ + EnvironManager: true, + } + var err error + s.charmrevisionupdater, err = charmrevisionupdater.NewCharmRevisionUpdaterAPI(s.State, s.resources, s.authoriser) + c.Assert(err, jc.ErrorIsNil) +} + +func (s *statusUpgradeUnitSuite) TearDownTest(c *gc.C) { + s.CharmSuite.TearDownTest(c) + s.JujuConnSuite.TearDownTest(c) +} + +func (s *statusUpgradeUnitSuite) TestUpdateRevisions(c *gc.C) { + s.AddMachine(c, "0", state.JobManageModel) + s.SetupScenario(c) + client := s.APIState.Client() + status, _ := client.Status(nil) + + serviceStatus, ok := status.Services["mysql"] + c.Assert(ok, gc.Equals, true) + c.Assert(serviceStatus.CanUpgradeTo, gc.Equals, "") + + // Update to the latest available charm revision. + result, err := s.charmrevisionupdater.UpdateLatestRevisions() + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.Error, gc.IsNil) + + // Check if CanUpgradeTo suggest the latest revision. + status, _ = client.Status(nil) + serviceStatus, ok = status.Services["mysql"] + c.Assert(ok, gc.Equals, true) + c.Assert(serviceStatus.CanUpgradeTo, gc.Equals, "cs:quantal/mysql-23") +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/client_auth_root_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/client_auth_root_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/client_auth_root_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/client_auth_root_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -14,6 +14,7 @@ gc "gopkg.in/check.v1" "github.com/juju/juju/rpc/rpcreflect" + "github.com/juju/juju/state" "github.com/juju/juju/state/testing" ) @@ -51,7 +52,7 @@ } func (s *clientAuthRootSuite) TestReadOnlyUser(c *gc.C) { - envUser := s.Factory.MakeModelUser(c, &factory.ModelUserParams{ReadOnly: true}) + envUser := s.Factory.MakeModelUser(c, &factory.ModelUserParams{Access: state.ModelReadAccess}) client := newClientAuthRoot(&fakeFinder{}, envUser) // deploys are bad s.AssertCallErrPerm(c, client, "Service", 3, "Deploy") diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/action.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/action.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/action.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/action.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,249 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package common + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" + "github.com/juju/juju/state/watcher" +) + +// ParamsActionExecutionResultsToStateActionResults does exactly what +// the name implies. +func ParamsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) { + var status state.ActionStatus + switch arg.Status { + case params.ActionCancelled: + status = state.ActionCancelled + case params.ActionCompleted: + status = state.ActionCompleted + case params.ActionFailed: + status = state.ActionFailed + case params.ActionPending: + status = state.ActionPending + default: + return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status) + } + return state.ActionResults{ + Status: status, + Results: arg.Results, + Message: arg.Message, + }, nil +} + +// TagToActionReceiver takes a tag string and tries to convert it to an +// ActionReceiver. It needs a findEntity function passed in that can search for the tags in state. +func TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(tag string) (state.ActionReceiver, error) { + return func(tag string) (state.ActionReceiver, error) { + receiverTag, err := names.ParseTag(tag) + if err != nil { + return nil, ErrBadId + } + entity, err := findEntity(receiverTag) + if err != nil { + return nil, ErrBadId + } + receiver, ok := entity.(state.ActionReceiver) + if !ok { + return nil, ErrBadId + } + return receiver, nil + } +} + +// AuthAndActionFromTagFn takes in an authorizer function and a function that can fetch action by tags from state +// and returns a function that can fetch an action from state by id and check the authorization. +func AuthAndActionFromTagFn(canAccess AuthFunc, getActionByTag func(names.ActionTag) (state.Action, error)) func(string) (state.Action, error) { + return func(tag string) (state.Action, error) { + actionTag, err := names.ParseActionTag(tag) + if err != nil { + return nil, errors.Trace(err) + } + action, err := getActionByTag(actionTag) + if err != nil { + return nil, errors.Trace(err) + } + receiverTag, err := names.ActionReceiverTag(action.Receiver()) + if err != nil { + return nil, errors.Trace(err) + } + if !canAccess(receiverTag) { + return nil, ErrPerm + } + return action, nil + } +} + +// BeginActions calls begin on every action passed in through args. +// It's a helper function currently used by the uniter and by machineactions +// It needs an actionFn that can fetch an action from state using it's id, that's usually created by AuthAndActionFromTagFn +func BeginActions(args params.Entities, actionFn func(string) (state.Action, error)) params.ErrorResults { + results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))} + + for i, arg := range args.Entities { + action, err := actionFn(arg.Tag) + if err != nil { + results.Results[i].Error = ServerError(err) + continue + } + + _, err = action.Begin() + if err != nil { + results.Results[i].Error = ServerError(err) + continue + } + } + + return results +} + +// FinishActions saves the result of a completed Action. +// It's a helper function currently used by the uniter and by machineactions +// It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn +func FinishActions(args params.ActionExecutionResults, actionFn func(string) (state.Action, error)) params.ErrorResults { + results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))} + + for i, arg := range args.Results { + action, err := actionFn(arg.ActionTag) + if err != nil { + results.Results[i].Error = ServerError(err) + continue + } + actionResults, err := ParamsActionExecutionResultsToStateActionResults(arg) + if err != nil { + results.Results[i].Error = ServerError(err) + continue + } + + _, err = action.Finish(actionResults) + if err != nil { + results.Results[i].Error = ServerError(err) + continue + } + } + + return results +} + +// Actions returns the Actions by Tags passed in and ensures that the receiver asking for +// them is the same one that has the action. +// It's a helper function currently used by the uniter and by machineactions. +// It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn +func Actions(args params.Entities, actionFn func(string) (state.Action, error)) params.ActionResults { + results := params.ActionResults{ + Results: make([]params.ActionResult, len(args.Entities)), + } + + for i, arg := range args.Entities { + action, err := actionFn(arg.Tag) + if err != nil { + results.Results[i].Error = ServerError(err) + continue + } + if action.Status() != state.ActionPending { + results.Results[i].Error = ServerError(ErrActionNotAvailable) + continue + } + results.Results[i].Action = ¶ms.Action{ + Name: action.Name(), + Parameters: action.Parameters(), + } + } + + return results +} + +// WatchOneActionReceiverNotifications to create a watcher for one receiver. +// It needs a tagToActionReceiver function and a registerFunc to register +// resources. +// It's a helper function currently used by the uniter and by machineactions +func WatchOneActionReceiverNotifications(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r Resource) string) func(names.Tag) (params.StringsWatchResult, error) { + return func(tag names.Tag) (params.StringsWatchResult, error) { + nothing := params.StringsWatchResult{} + receiver, err := tagToActionReceiver(tag.String()) + if err != nil { + return nothing, err + } + watch := receiver.WatchActionNotifications() + + if changes, ok := <-watch.Changes(); ok { + return params.StringsWatchResult{ + StringsWatcherId: registerFunc(watch), + Changes: changes, + }, nil + } + return nothing, watcher.EnsureErr(watch) + } +} + +// WatchActionNotifications returns a StringsWatcher for observing incoming actions towards an actionreceiver. +// It's a helper function currently used by the uniter and by machineactions +// canAccess is passed in by the respective caller to provide authorization. +// watchOne is usually a function created by WatchOneActionReceiverNotifications +func WatchActionNotifications(args params.Entities, canAccess AuthFunc, watchOne func(names.Tag) (params.StringsWatchResult, error)) params.StringsWatchResults { + result := params.StringsWatchResults{ + Results: make([]params.StringsWatchResult, len(args.Entities)), + } + + for i, entity := range args.Entities { + tag, err := names.ActionReceiverFromTag(entity.Tag) + if err != nil { + result.Results[i].Error = ServerError(err) + continue + } + err = ErrPerm + if canAccess(tag) { + result.Results[i], err = watchOne(tag) + } + result.Results[i].Error = ServerError(err) + } + + return result +} + +// GetActionsFn declares the function type that returns a slice of +// state.Action and error, used to curry specific list functions. +type GetActionsFn func() ([]state.Action, error) + +// ConvertActions takes a generic getActionsFn to obtain a slice +// of state.Action and then converts them to the API slice of +// params.ActionResult. +func ConvertActions(ar state.ActionReceiver, fn GetActionsFn) ([]params.ActionResult, error) { + items := []params.ActionResult{} + actions, err := fn() + if err != nil { + return items, err + } + for _, action := range actions { + if action == nil { + continue + } + items = append(items, MakeActionResult(ar.Tag(), action)) + } + return items, nil +} + +// MakeActionResult does the actual type conversion from state.Action +// to params.ActionResult. +func MakeActionResult(actionReceiverTag names.Tag, action state.Action) params.ActionResult { + output, message := action.Results() + return params.ActionResult{ + Action: ¶ms.Action{ + Receiver: actionReceiverTag.String(), + Tag: action.ActionTag().String(), + Name: action.Name(), + Parameters: action.Parameters(), + }, + Status: string(action.Status()), + Message: message, + Output: output, + Enqueued: action.Enqueued(), + Started: action.Started(), + Completed: action.Completed(), + } +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/action_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/action_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/action_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/action_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,372 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package common_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" + "github.com/juju/juju/testing" +) + +type actionsSuite struct { + testing.BaseSuite +} + +var _ = gc.Suite(&actionsSuite{}) + +func (s *actionsSuite) TestTagToActionReceiverFn(c *gc.C) { + stubActionReceiver := fakeActionReceiver{} + stubEntity := fakeEntity{} + tagToEntity := map[string]state.Entity{ + "unit-valid-0": stubActionReceiver, + "unit-invalid-0": stubEntity, + } + tagFn := common.TagToActionReceiverFn(makeFindEntity(tagToEntity)) + + for i, test := range []struct { + tag string + err error + result state.ActionReceiver + }{{ + tag: "unit-valid-0", + result: stubActionReceiver, + }, { + tag: "unit-invalid-0", + err: common.ErrBadId, + }, { + tag: "unit-flustered-0", + err: common.ErrBadId, + }, { + tag: "notatag", + err: common.ErrBadId, + }} { + c.Logf("test %d", i) + receiver, err := tagFn(test.tag) + if test.err != nil { + c.Check(err, gc.Equals, test.err) + c.Check(receiver, gc.IsNil) + } else { + c.Assert(err, jc.ErrorIsNil) + c.Assert(receiver, gc.Equals, test.result) + } + } +} + +func (s *actionsSuite) TestAuthAndActionFromTagFn(c *gc.C) { + notFoundActionTag := names.NewActionTag(utils.MustNewUUID().String()) + + authorizedActionTag := names.NewActionTag(utils.MustNewUUID().String()) + authorizedMachineTag := names.NewMachineTag("1") + authorizedAction := fakeAction{name: "action1", receiver: authorizedMachineTag.Id()} + + unauthorizedActionTag := names.NewActionTag(utils.MustNewUUID().String()) + unauthorizedMachineTag := names.NewMachineTag("10") + unauthorizedAction := fakeAction{name: "action2", receiver: unauthorizedMachineTag.Id()} + + invalidReceiverActionTag := names.NewActionTag(utils.MustNewUUID().String()) + invalidReceiverAction := fakeAction{name: "action2", receiver: "masterexploder"} + + canAccess := makeCanAccess(map[names.Tag]bool{ + authorizedMachineTag: true, + }) + getActionByTag := makeGetActionByTag(map[names.ActionTag]state.Action{ + authorizedActionTag: authorizedAction, + unauthorizedActionTag: unauthorizedAction, + invalidReceiverActionTag: invalidReceiverAction, + }) + tagFn := common.AuthAndActionFromTagFn(canAccess, getActionByTag) + + for i, test := range []struct { + tag string + errString string + err error + expectedAction state.Action + }{{ + tag: "invalid-action-tag", + errString: `"invalid-action-tag" is not a valid tag`, + }, { + tag: notFoundActionTag.String(), + errString: "action not found", + }, { + tag: invalidReceiverActionTag.String(), + errString: `invalid actionreceiver name "masterexploder"`, + }, { + tag: unauthorizedActionTag.String(), + err: common.ErrPerm, + }, { + tag: authorizedActionTag.String(), + expectedAction: authorizedAction, + }} { + c.Logf("test %d", i) + action, err := tagFn(test.tag) + if test.errString != "" { + c.Check(err, gc.ErrorMatches, test.errString) + c.Check(action, gc.IsNil) + } else if test.err != nil { + c.Check(err, gc.Equals, test.err) + c.Check(action, gc.IsNil) + } else { + c.Check(err, jc.ErrorIsNil) + c.Check(action, gc.Equals, action) + } + } +} + +func (s *actionsSuite) TestBeginActions(c *gc.C) { + args := entities("success", "fail", "invalid") + expectErr := errors.New("explosivo") + actionFn := makeGetActionByTagString(map[string]state.Action{ + "success": fakeAction{}, + "fail": fakeAction{beginErr: expectErr}, + }) + + results := common.BeginActions(args, actionFn) + + c.Assert(results, jc.DeepEquals, params.ErrorResults{ + []params.ErrorResult{ + {}, + {common.ServerError(expectErr)}, + {common.ServerError(actionNotFoundErr)}, + }, + }) +} + +func (s *actionsSuite) TestGetActions(c *gc.C) { + args := entities("success", "fail", "notPending") + actionFn := makeGetActionByTagString(map[string]state.Action{ + "success": fakeAction{name: "floosh", status: state.ActionPending}, + "notPending": fakeAction{status: state.ActionCancelled}, + }) + + results := common.Actions(args, actionFn) + + c.Assert(results, jc.DeepEquals, params.ActionResults{ + []params.ActionResult{ + {Action: ¶ms.Action{Name: "floosh"}}, + {Error: common.ServerError(actionNotFoundErr)}, + {Error: common.ServerError(common.ErrActionNotAvailable)}, + }, + }) +} + +func (s *actionsSuite) TestFinishActions(c *gc.C) { + args := params.ActionExecutionResults{ + []params.ActionExecutionResult{ + {ActionTag: "success", Status: string(state.ActionCompleted)}, + {ActionTag: "notfound"}, + {ActionTag: "convertFail", Status: "failStatus"}, + {ActionTag: "finishFail", Status: string(state.ActionCancelled)}, + }, + } + expectErr := errors.New("explosivo") + actionFn := makeGetActionByTagString(map[string]state.Action{ + "success": fakeAction{}, + "convertFail": fakeAction{}, + "finishFail": fakeAction{finishErr: expectErr}, + }) + results := common.FinishActions(args, actionFn) + c.Assert(results, jc.DeepEquals, params.ErrorResults{ + []params.ErrorResult{ + {}, + {common.ServerError(actionNotFoundErr)}, + {common.ServerError(errors.New("unrecognized action status 'failStatus'"))}, + {common.ServerError(expectErr)}, + }, + }) +} + +func (s *actionsSuite) TestWatchActionNotifications(c *gc.C) { + args := entities("invalid-actionreceiver", "machine-1", "machine-2", "machine-3") + canAccess := makeCanAccess(map[names.Tag]bool{ + names.NewMachineTag("2"): true, + names.NewMachineTag("3"): true, + }) + expectedStringsWatchResult := params.StringsWatchResult{ + StringsWatcherId: "orosu", + } + watchOne := makeWatchOne(map[names.Tag]params.StringsWatchResult{ + names.NewMachineTag("3"): expectedStringsWatchResult, + }) + + results := common.WatchActionNotifications(args, canAccess, watchOne) + + c.Assert(results, jc.DeepEquals, params.StringsWatchResults{ + []params.StringsWatchResult{ + {Error: common.ServerError(errors.New(`invalid actionreceiver tag "invalid-actionreceiver"`))}, + {Error: common.ServerError(common.ErrPerm)}, + {Error: common.ServerError(errors.New("pax"))}, + {StringsWatcherId: "orosu"}, + }, + }) +} + +func (s *actionsSuite) TestWatchOneActionReceiverNotifications(c *gc.C) { + expectErr := errors.New("zwoosh") + registerFunc := func(common.Resource) string { return "bambalam" } + tagToActionReceiver := common.TagToActionReceiverFn(makeFindEntity(map[string]state.Entity{ + "machine-1": &fakeActionReceiver{watcher: &fakeWatcher{}}, + "machine-2": &fakeActionReceiver{watcher: &fakeWatcher{err: expectErr}}, + })) + + watchOneFn := common.WatchOneActionReceiverNotifications(tagToActionReceiver, registerFunc) + + for i, test := range []struct { + tag names.Tag + err string + watcherId string + }{{ + tag: names.NewMachineTag("0"), + err: "id not found", + }, { + tag: names.NewMachineTag("1"), + watcherId: "bambalam", + }, { + tag: names.NewMachineTag("2"), + err: "zwoosh", + }} { + c.Logf("test %d", i) + c.Logf(test.tag.String()) + result, err := watchOneFn(test.tag) + if test.err != "" { + c.Check(err, gc.ErrorMatches, test.err) + c.Check(result, jc.DeepEquals, params.StringsWatchResult{}) + } else { + c.Check(err, jc.ErrorIsNil) + c.Check(result.StringsWatcherId, gc.Equals, test.watcherId) + } + } +} + +func makeWatchOne(mapping map[names.Tag]params.StringsWatchResult) func(names.Tag) (params.StringsWatchResult, error) { + return func(tag names.Tag) (params.StringsWatchResult, error) { + result, ok := mapping[tag] + if !ok { + return params.StringsWatchResult{}, errors.New("pax") + } + return result, nil + } +} + +func makeFindEntity(tagToEntity map[string]state.Entity) func(tag names.Tag) (state.Entity, error) { + return func(tag names.Tag) (state.Entity, error) { + receiver, ok := tagToEntity[tag.String()] + if !ok { + return nil, errors.New("splat") + } + return receiver, nil + } +} + +func makeCanAccess(allowed map[names.Tag]bool) common.AuthFunc { + return func(tag names.Tag) bool { + _, ok := allowed[tag] + return ok + } +} + +var actionNotFoundErr = errors.New("action not found") + +func makeGetActionByTag(tagToAction map[names.ActionTag]state.Action) func(names.ActionTag) (state.Action, error) { + return func(tag names.ActionTag) (state.Action, error) { + action, ok := tagToAction[tag] + if !ok { + return nil, actionNotFoundErr + } + return action, nil + } +} + +func makeGetActionByTagString(tagToAction map[string]state.Action) func(string) (state.Action, error) { + return func(tag string) (state.Action, error) { + action, ok := tagToAction[tag] + if !ok { + return nil, errors.New("action not found") + } + return action, nil + } +} + +type fakeActionReceiver struct { + state.ActionReceiver + watcher state.StringsWatcher +} + +func (mock fakeActionReceiver) WatchActionNotifications() state.StringsWatcher { + return mock.watcher +} + +type fakeWatcher struct { + state.StringsWatcher + err error +} + +func (mock fakeWatcher) Changes() <-chan []string { + ch := make(chan []string, 1) + if mock.err != nil { + close(ch) + } else { + ch <- []string{"pew", "pew", "pew"} + } + return ch +} + +func (mock fakeWatcher) Err() error { + return mock.err +} + +type fakeEntity struct { + state.Entity +} + +type fakeAction struct { + state.Action + receiver string + name string + beginErr error + finishErr error + status state.ActionStatus +} + +func (mock fakeAction) Status() state.ActionStatus { + return mock.status +} + +func (mock fakeAction) Begin() (state.Action, error) { + return nil, mock.beginErr +} + +func (mock fakeAction) Receiver() string { + return mock.receiver +} + +func (mock fakeAction) Name() string { + return mock.name +} + +func (mock fakeAction) Parameters() map[string]interface{} { + return nil +} + +func (mock fakeAction) Finish(state.ActionResults) (state.Action, error) { + return nil, mock.finishErr +} + +// entities is a convenience constructor for params.Entities. +func entities(tags ...string) params.Entities { + entities := params.Entities{ + Entities: make([]params.Entity, len(tags)), + } + for i, tag := range tags { + entities.Entities[i].Tag = tag + } + return entities +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/addresses.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/addresses.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/addresses.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/addresses.go 2016-04-28 06:03:34.000000000 +0000 @@ -66,9 +66,11 @@ } var addrs = make([]string, 0, len(apiHostPorts)) for _, hostPorts := range apiHostPorts { - addr := network.SelectInternalHostPort(hostPorts, false) - if addr != "" { - addrs = append(addrs, addr) + ordered := network.PrioritizeInternalHostPorts(hostPorts, false) + for _, addr := range ordered { + if addr != "" { + addrs = append(addrs, addr) + } } } return params.StringsResult{ diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/addresses_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/addresses_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/addresses_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/addresses_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -18,13 +18,19 @@ type apiAddresserSuite struct { addresser *common.APIAddresser + fake *fakeAddresses } var _ = gc.Suite(&stateAddresserSuite{}) var _ = gc.Suite(&apiAddresserSuite{}) func (s *stateAddresserSuite) SetUpTest(c *gc.C) { - s.addresser = common.NewStateAddresser(fakeAddresses{}) + s.addresser = common.NewStateAddresser(fakeAddresses{ + hostPorts: [][]network.HostPort{ + network.NewHostPorts(1, "apiaddresses"), + network.NewHostPorts(2, "apiaddresses"), + }, + }) } // Verify that AddressAndCertGetter is satisfied by *state.State. @@ -37,7 +43,13 @@ } func (s *apiAddresserSuite) SetUpTest(c *gc.C) { - s.addresser = common.NewAPIAddresser(fakeAddresses{}, common.NewResources()) + s.fake = &fakeAddresses{ + hostPorts: [][]network.HostPort{ + network.NewHostPorts(1, "apiaddresses"), + network.NewHostPorts(2, "apiaddresses"), + }, + } + s.addresser = common.NewAPIAddresser(s.fake, common.NewResources()) } func (s *apiAddresserSuite) TestAPIAddresses(c *gc.C) { @@ -46,6 +58,36 @@ c.Assert(result.Result, gc.DeepEquals, []string{"apiaddresses:1", "apiaddresses:2"}) } +func (s *apiAddresserSuite) TestAPIAddressesPrivateFirst(c *gc.C) { + ctlr1, err := network.ParseHostPorts("52.7.1.1:17070", "10.0.2.1:17070") + c.Assert(err, jc.ErrorIsNil) + ctlr2, err := network.ParseHostPorts("53.51.121.17:17070", "10.0.1.17:17070") + c.Assert(err, jc.ErrorIsNil) + s.fake.hostPorts = [][]network.HostPort{ + network.NewHostPorts(1, "apiaddresses"), + ctlr1, + ctlr2, + network.NewHostPorts(2, "apiaddresses"), + } + for _, hps := range s.fake.hostPorts { + for _, hp := range hps { + c.Logf("%s - %#v", hp.Scope, hp) + } + } + + result, err := s.addresser.APIAddresses() + c.Assert(err, jc.ErrorIsNil) + + c.Check(result.Result, gc.DeepEquals, []string{ + "apiaddresses:1", + "10.0.2.1:17070", + "52.7.1.1:17070", + "10.0.1.17:17070", + "53.51.121.17:17070", + "apiaddresses:2", + }) +} + func (s *apiAddresserSuite) TestCACert(c *gc.C) { result := s.addresser.CACert() c.Assert(string(result.Result), gc.Equals, "a cert") @@ -58,7 +100,9 @@ var _ common.AddressAndCertGetter = fakeAddresses{} -type fakeAddresses struct{} +type fakeAddresses struct { + hostPorts [][]network.HostPort +} func (fakeAddresses) Addresses() ([]string, error) { return []string{"addresses:1", "addresses:2"}, nil @@ -76,11 +120,8 @@ return "the environ uuid" } -func (fakeAddresses) APIHostPorts() ([][]network.HostPort, error) { - return [][]network.HostPort{ - network.NewHostPorts(1, "apiaddresses"), - network.NewHostPorts(2, "apiaddresses"), - }, nil +func (f fakeAddresses) APIHostPorts() ([][]network.HostPort, error) { + return f.hostPorts, nil } func (fakeAddresses) WatchAPIHostPorts() state.NotifyWatcher { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/apihttp/endpoint.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/apihttp/endpoint.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/apihttp/endpoint.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/apihttp/endpoint.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,20 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package apihttp + +import ( + "net/http" +) + +// Endpoint describes a single HTTP endpoint. +type Endpoint struct { + // Pattern is the pattern to match for the endpoint. + Pattern string + + // Method is the HTTP method to use (e.g. GET). + Method string + + // Handler is the HTTP handler to use. + Handler http.Handler +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/apihttp/handler.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/apihttp/handler.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/apihttp/handler.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/apihttp/handler.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,50 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package apihttp + +import ( + "net/http" + + "github.com/juju/juju/state" +) + +// NewHandlerArgs holds the args to the func in the NewHandler +// field of HandlerSpec. +type NewHandlerArgs struct { + // Connect is the function that is used to connect to Juju's state + // for the given HTTP request. + Connect func(*http.Request) (*state.State, state.Entity, error) +} + +// HandlerConstraints describes conditions under which a handler +// may operate. +type HandlerConstraints struct { + // AuthKind defines the kind of authenticated "user" that the + // handler supports. This correlates directly to entities, as + // identified by tag kinds (e.g. names.UserTagKind). The empty + // string indicates support for unauthenticated requests. + AuthKind string + + // StrictValidation is the value that will be used for the handler's + // httpContext (see apiserver/httpcontext.go). + StrictValidation bool + + // ControllerModelOnly is the value that will be used for the handler's + // httpContext (see apiserver/httpcontext.go). + ControllerModelOnly bool +} + +// HandlerSpec defines an HTTP handler for a specific endpoint +// on Juju's HTTP server. Such endpoints facilitate behavior that is +// not supported through normal (websocket) RPC. That includes file +// transfer. +type HandlerSpec struct { + // Constraints are the handler's constraints. + Constraints HandlerConstraints + + // NewHandler returns a new HTTP handler for the given args. + // The function is idempotent--if given the same args, it will + // produce an equivalent handler each time. + NewHandler func(NewHandlerArgs) http.Handler +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/errors.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/errors.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/errors.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/errors.go 2016-04-28 06:03:34.000000000 +0000 @@ -53,7 +53,7 @@ return &unknownModelError{uuid: uuid} } -func IsUnknownModelError(err error) bool { +func isUnknownModelError(err error) bool { _, ok := err.(*unknownModelError) return ok } @@ -112,35 +112,43 @@ } } +var singletonErrorCodes = map[error]string{ + state.ErrCannotEnterScopeYet: params.CodeCannotEnterScopeYet, + state.ErrCannotEnterScope: params.CodeCannotEnterScope, + state.ErrUnitHasSubordinates: params.CodeUnitHasSubordinates, + state.ErrDead: params.CodeDead, + txn.ErrExcessiveContention: params.CodeExcessiveContention, + leadership.ErrClaimDenied: params.CodeLeadershipClaimDenied, + lease.ErrClaimDenied: params.CodeLeaseClaimDenied, + ErrBadId: params.CodeNotFound, + ErrBadCreds: params.CodeUnauthorized, + ErrPerm: params.CodeUnauthorized, + ErrNotLoggedIn: params.CodeUnauthorized, + ErrUnknownWatcher: params.CodeNotFound, + ErrStoppedWatcher: params.CodeStopped, + ErrTryAgain: params.CodeTryAgain, + ErrActionNotAvailable: params.CodeActionNotAvailable, +} + func singletonCode(err error) (string, bool) { - switch err { - case state.ErrCannotEnterScopeYet: - return params.CodeCannotEnterScopeYet, true - case state.ErrCannotEnterScope: - return params.CodeCannotEnterScope, true - case state.ErrUnitHasSubordinates: - return params.CodeUnitHasSubordinates, true - case state.ErrDead: - return params.CodeDead, true - case txn.ErrExcessiveContention: - return params.CodeExcessiveContention, true - case leadership.ErrClaimDenied: - return params.CodeLeadershipClaimDenied, true - case lease.ErrClaimDenied: - return params.CodeLeaseClaimDenied, true - case ErrBadId, ErrUnknownWatcher: - return params.CodeNotFound, true - case ErrBadCreds, ErrPerm, ErrNotLoggedIn: - return params.CodeUnauthorized, true - case ErrStoppedWatcher: - return params.CodeStopped, true - case ErrTryAgain: - return params.CodeTryAgain, true - case ErrActionNotAvailable: - return params.CodeActionNotAvailable, true - default: - return "", false + // All error types may not be hashable; deal with + // that by catching the panic if we try to look up + // a non-hashable type. + defer func() { + recover() + }() + code, ok := singletonErrorCodes[err] + return code, ok +} + +func singletonError(err error) (error, bool) { + errCode := params.ErrCode(err) + for singleton, code := range singletonErrorCodes { + if errCode == code && singleton.Error() == err.Error() { + return singleton, true + } } + return nil, false } // ServerErrorAndStatus is like ServerError but also @@ -197,6 +205,8 @@ code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits + case state.IsHasHostedModelsError(err): + code = params.CodeHasHostedModels case isNoAddressSetError(err): code = params.CodeNoAddressSet case errors.IsNotProvisioned(err): @@ -205,7 +215,7 @@ code = params.CodeUpgradeInProgress case state.IsHasAttachmentsError(err): code = params.CodeMachineHasAttachedStorage - case IsUnknownModelError(err): + case isUnknownModelError(err): code = params.CodeNotFound case errors.IsNotSupported(err): code = params.CodeNotSupported @@ -244,3 +254,68 @@ msg = fmt.Sprintf(msg, desc) return errors.Errorf("%s: %s", msg, strings.Join(errs, "; ")) } + +// RestoreError makes a best effort at converting the given error +// back into an error originally converted by ServerError(). +func RestoreError(err error) error { + err = errors.Cause(err) + + if apiErr, ok := err.(*params.Error); !ok { + return err + } else if apiErr == nil { + return nil + } + if params.ErrCode(err) == "" { + return err + } + msg := err.Error() + + if singleton, ok := singletonError(err); ok { + return singleton + } + + // TODO(ericsnow) Support the other error types handled by ServerError(). + switch { + case params.IsCodeUnauthorized(err): + return errors.NewUnauthorized(nil, msg) + case params.IsCodeNotFound(err): + // TODO(ericsnow) UnknownModelError should be handled here too. + // ...by parsing msg? + return errors.NewNotFound(nil, msg) + case params.IsCodeAlreadyExists(err): + return errors.NewAlreadyExists(nil, msg) + case params.IsCodeNotAssigned(err): + return errors.NewNotAssigned(nil, msg) + case params.IsCodeHasAssignedUnits(err): + // TODO(ericsnow) Handle state.HasAssignedUnitsError here. + // ...by parsing msg? + return err + case params.IsCodeHasHostedModels(err): + return err + case params.IsCodeNoAddressSet(err): + // TODO(ericsnow) Handle isNoAddressSetError here. + // ...by parsing msg? + return err + case params.IsCodeNotProvisioned(err): + return errors.NewNotProvisioned(nil, msg) + case params.IsCodeUpgradeInProgress(err): + // TODO(ericsnow) Handle state.UpgradeInProgressError here. + // ...by parsing msg? + return err + case params.IsCodeMachineHasAttachedStorage(err): + // TODO(ericsnow) Handle state.HasAttachmentsError here. + // ...by parsing msg? + return err + case params.IsCodeNotSupported(err): + return errors.NewNotSupported(nil, msg) + case params.IsBadRequest(err): + return errors.NewBadRequest(nil, msg) + case params.IsMethodNotAllowed(err): + return errors.NewMethodNotAllowed(nil, msg) + case params.ErrCode(err) == params.CodeDischargeRequired: + // TODO(ericsnow) Handle DischargeRequiredError here. + return err + default: + return err + } +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/errors_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/errors_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/errors_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/errors_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -242,6 +242,18 @@ // ServerError doesn't actually have a case for this code. continue } + + c.Logf(" checking restore (%#v)", err1) + restored := common.RestoreError(err1) + if t.err == nil { + c.Check(restored, jc.ErrorIsNil) + } else if t.code == "" { + c.Check(restored.Error(), gc.Equals, t.err.Error()) + } else { + // TODO(ericsnow) Use a stricter DeepEquals check. + c.Check(errors.Cause(restored), gc.FitsTypeOf, t.err) + c.Check(restored.Error(), gc.Equals, t.err.Error()) + } } } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/export_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,6 +11,7 @@ EnvtoolsFindTools = &envtoolsFindTools SendMetrics = &sendMetrics MockableDestroyMachines = destroyMachines + IsUnknownModelError = isUnknownModelError ) type Patcher interface { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/modeldestroy_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/modeldestroy_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/modeldestroy_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/modeldestroy_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -226,8 +226,7 @@ c.Assert(err, jc.ErrorIsNil) s.otherEnvOwner = names.NewUserTag("jess") s.otherState = factory.NewFactory(s.State).MakeModel(c, &factory.ModelParams{ - Owner: s.otherEnvOwner, - Prepare: true, + Owner: s.otherEnvOwner, ConfigAttrs: jujutesting.Attrs{ "controller": false, }, @@ -332,15 +331,20 @@ err = common.DestroyModel(s.State, s.otherState.ModelTag()) c.Assert(err, jc.ErrorIsNil) + // The hosted model is Dying, not Dead; we cannot destroy + // the controller model until all hosted models are Dead. err = common.DestroyModel(s.State, s.State.ModelTag()) - c.Assert(err, jc.ErrorIsNil) + c.Assert(err, gc.ErrorMatches, "failed to destroy model: hosting 1 other models") - // Make sure we can continue to take the hosted model down while the - // controller environ is dying. + // Continue to take the hosted model down so we can + // destroy the controller model. runAllCleanups(c, s.otherState) assertAllMachinesDeadAndRemove(c, s.otherState) c.Assert(s.otherState.ProcessDyingModel(), jc.ErrorIsNil) + err = common.DestroyModel(s.State, s.State.ModelTag()) + c.Assert(err, jc.ErrorIsNil) + otherEnv, err := s.otherState.Model() c.Assert(err, jc.ErrorIsNil) c.Assert(otherEnv.Life(), gc.Equals, state.Dead) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/modeluser.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/modeluser.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/modeluser.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/modeluser.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,59 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package common + +import ( + "time" + + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" +) + +// ModelUser defines the subset of the state.ModelUser type +// that we require to convert to a params.ModelUserInfo. +type ModelUser interface { + Access() state.ModelAccess + DisplayName() string + LastConnection() (time.Time, error) + UserName() string + UserTag() names.UserTag +} + +// ModelUserInfo converts *state.ModelUser to params.ModelUserInfo. +func ModelUserInfo(user ModelUser) (params.ModelUserInfo, error) { + access, err := StateToParamsModelAccess(user.Access()) + if err != nil { + return params.ModelUserInfo{}, errors.Trace(err) + } + + var lastConn *time.Time + userLastConn, err := user.LastConnection() + if err == nil { + lastConn = &userLastConn + } else if !state.IsNeverConnectedError(err) { + return params.ModelUserInfo{}, errors.Trace(err) + } + + userInfo := params.ModelUserInfo{ + UserName: user.UserName(), + DisplayName: user.DisplayName(), + LastConnection: lastConn, + Access: access, + } + return userInfo, nil +} + +// StateToParamsModelAccess converts state.ModelAccess to params.ModelAccessPermission. +func StateToParamsModelAccess(stateAccess state.ModelAccess) (params.ModelAccessPermission, error) { + switch stateAccess { + case state.ModelReadAccess: + return params.ModelReadAccess, nil + case state.ModelAdminAccess: + return params.ModelWriteAccess, nil + } + return "", errors.Errorf("invalid model access permission %q", stateAccess) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/modelwatcher_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/modelwatcher_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/modelwatcher_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/modelwatcher_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -16,7 +16,6 @@ "github.com/juju/juju/cmd/modelcmd" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" - "github.com/juju/juju/environs/configstore" "github.com/juju/juju/jujuclient/jujuclienttesting" "github.com/juju/juju/provider/dummy" "github.com/juju/juju/state" @@ -37,10 +36,10 @@ } func (*fakeModelAccessor) WatchForModelConfigChanges() state.NotifyWatcher { - changes := make(chan struct{}, 1) + w := apiservertesting.NewFakeNotifyWatcher() // Simulate initial event. - changes <- struct{}{} - return &fakeNotifyWatcher{changes: changes} + w.C <- struct{}{} + return w } func (f *fakeModelAccessor) ModelConfig() (*config.Config, error) { @@ -51,7 +50,7 @@ } func (s *environWatcherSuite) TearDownTest(c *gc.C) { - dummy.Reset() + dummy.Reset(c) s.BaseSuite.TearDownTest(c) } @@ -124,12 +123,14 @@ } func testingEnvConfig(c *gc.C) *config.Config { - cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) - c.Assert(err, jc.ErrorIsNil) env, err := environs.Prepare( - modelcmd.BootstrapContext(testing.Context(c)), configstore.NewMem(), + modelcmd.BootstrapContext(testing.Context(c)), jujuclienttesting.NewMemStore(), - "dummycontroller", environs.PrepareForBootstrapParams{Config: cfg}, + environs.PrepareParams{ + ControllerName: "dummycontroller", + BaseConfig: dummy.SampleConfig(), + CloudName: "dummy", + }, ) c.Assert(err, jc.ErrorIsNil) return env.Config() diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,9 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package networkingcommon + +var ( + NetInterfaces = &netInterfaces + InterfaceAddrs = &interfaceAddrs +) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/spaces.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/spaces.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/spaces.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/spaces.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,11 +11,12 @@ "github.com/juju/juju/apiserver/params" "github.com/juju/juju/environs" "github.com/juju/juju/network" + "github.com/juju/juju/worker/environ" ) // SupportsSpaces checks if the environment implements NetworkingEnviron // and also if it supports spaces. -func SupportsSpaces(backing NetworkBacking) error { +func SupportsSpaces(backing environ.ConfigGetter) error { config, err := backing.ModelConfig() if err != nil { return errors.Annotate(err, "getting model config") diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/subnets.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/subnets.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/subnets.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/subnets.go 2016-04-28 06:03:34.000000000 +0000 @@ -21,7 +21,7 @@ providercommon "github.com/juju/juju/provider/common" ) -var logger = loggo.GetLogger("juju.apiserver.common.networkingcommon.subnets") +var logger = loggo.GetLogger("juju.apiserver.common.networkingcommon") // addSubnetsCache holds cached lists of spaces, zones, and subnets, used for // fast lookups while adding subnets. diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/types.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/types.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/types.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/types.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,12 +4,23 @@ package networkingcommon import ( + "encoding/json" + "net" + "regexp" + "sort" + "strings" + + "github.com/juju/errors" "github.com/juju/names" + "github.com/juju/utils/set" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/cloudconfig/instancecfg" + "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" "github.com/juju/juju/network" providercommon "github.com/juju/juju/provider/common" + "github.com/juju/juju/state" ) // BackingSubnet defines the methods supported by a Subnet entity @@ -148,3 +159,442 @@ Life: subnet.Life(), } } + +type byMACThenCIDRThenIndexThenName []params.NetworkConfig + +func (c byMACThenCIDRThenIndexThenName) Len() int { + return len(c) +} + +func (c byMACThenCIDRThenIndexThenName) Swap(i, j int) { + orgI, orgJ := c[i], c[j] + c[j], c[i] = orgI, orgJ +} + +func (c byMACThenCIDRThenIndexThenName) Less(i, j int) bool { + if c[i].MACAddress == c[j].MACAddress { + // Same MACAddress means related interfaces. + if c[i].CIDR == "" || c[j].CIDR == "" { + // Empty CIDRs go at the bottom, otherwise order by InterfaceName. + return c[i].CIDR != "" || c[i].InterfaceName < c[j].InterfaceName + } + if c[i].DeviceIndex == c[j].DeviceIndex { + if c[i].InterfaceName == c[j].InterfaceName { + // Sort addresses of the same interface. + return c[i].CIDR < c[j].CIDR || c[i].Address < c[j].Address + } + // Prefer shorter names (e.g. parents) with equal DeviceIndex. + return c[i].InterfaceName < c[j].InterfaceName + } + // When both CIDR and DeviceIndex are non-empty, order by DeviceIndex + return c[i].DeviceIndex < c[j].DeviceIndex + } + // Group by MACAddress. + return c[i].MACAddress < c[j].MACAddress +} + +// SortNetworkConfigsByParents returns the given input sorted, such that any +// child interfaces appear after their parents. +func SortNetworkConfigsByParents(input []params.NetworkConfig) []params.NetworkConfig { + sortedInputCopy := CopyNetworkConfigs(input) + sort.Stable(byMACThenCIDRThenIndexThenName(sortedInputCopy)) + return sortedInputCopy +} + +type byInterfaceName []params.NetworkConfig + +func (c byInterfaceName) Len() int { + return len(c) +} + +func (c byInterfaceName) Swap(i, j int) { + orgI, orgJ := c[i], c[j] + c[j], c[i] = orgI, orgJ +} + +func (c byInterfaceName) Less(i, j int) bool { + return c[i].InterfaceName < c[j].InterfaceName +} + +// SortNetworkConfigsByInterfaceName returns the given input sorted by +// InterfaceName. +func SortNetworkConfigsByInterfaceName(input []params.NetworkConfig) []params.NetworkConfig { + sortedInputCopy := CopyNetworkConfigs(input) + sort.Stable(byInterfaceName(sortedInputCopy)) + return sortedInputCopy +} + +// NetworkConfigsToIndentedJSON returns the given input as an indented JSON +// string. +func NetworkConfigsToIndentedJSON(input []params.NetworkConfig) (string, error) { + jsonBytes, err := json.MarshalIndent(input, "", " ") + if err != nil { + return "", err + } + return string(jsonBytes), nil +} + +// CopyNetworkConfigs returns a copy of the given input +func CopyNetworkConfigs(input []params.NetworkConfig) []params.NetworkConfig { + return append([]params.NetworkConfig(nil), input...) +} + +// NetworkConfigFromInterfaceInfo converts a slice of network.InterfaceInfo into +// the equivalent params.NetworkConfig slice. +func NetworkConfigFromInterfaceInfo(interfaceInfos []network.InterfaceInfo) []params.NetworkConfig { + result := make([]params.NetworkConfig, len(interfaceInfos)) + for i, v := range interfaceInfos { + var dnsServers []string + for _, nameserver := range v.DNSServers { + dnsServers = append(dnsServers, nameserver.Value) + } + result[i] = params.NetworkConfig{ + DeviceIndex: v.DeviceIndex, + MACAddress: v.MACAddress, + CIDR: v.CIDR, + MTU: v.MTU, + ProviderId: string(v.ProviderId), + ProviderSubnetId: string(v.ProviderSubnetId), + ProviderSpaceId: string(v.ProviderSpaceId), + ProviderVLANId: string(v.ProviderVLANId), + ProviderAddressId: string(v.ProviderAddressId), + VLANTag: v.VLANTag, + InterfaceName: v.InterfaceName, + ParentInterfaceName: v.ParentInterfaceName, + InterfaceType: string(v.InterfaceType), + Disabled: v.Disabled, + NoAutoStart: v.NoAutoStart, + ConfigType: string(v.ConfigType), + Address: v.Address.Value, + DNSServers: dnsServers, + DNSSearchDomains: v.DNSSearchDomains, + GatewayAddress: v.GatewayAddress.Value, + } + } + return result +} + +// NetworkConfigsToStateArgs splits the given networkConfig into a slice of +// state.LinkLayerDeviceArgs and a slice of state.LinkLayerDeviceAddress. The +// input is expected to come from MergeProviderAndObservedNetworkConfigs and to +// be sorted. +func NetworkConfigsToStateArgs(networkConfig []params.NetworkConfig) ( + []state.LinkLayerDeviceArgs, + []state.LinkLayerDeviceAddress, +) { + var devicesArgs []state.LinkLayerDeviceArgs + var devicesAddrs []state.LinkLayerDeviceAddress + + logger.Tracef("transforming network config to state args: %+v", networkConfig) + seenDeviceNames := set.NewStrings() + for _, netConfig := range networkConfig { + logger.Tracef("transforming device %q", netConfig.InterfaceName) + if !seenDeviceNames.Contains(netConfig.InterfaceName) { + // First time we see this, add it to devicesArgs. + seenDeviceNames.Add(netConfig.InterfaceName) + var mtu uint + if netConfig.MTU >= 0 { + mtu = uint(netConfig.MTU) + } + args := state.LinkLayerDeviceArgs{ + Name: netConfig.InterfaceName, + MTU: mtu, + ProviderID: network.Id(netConfig.ProviderId), + Type: state.LinkLayerDeviceType(netConfig.InterfaceType), + MACAddress: netConfig.MACAddress, + IsAutoStart: !netConfig.NoAutoStart, + IsUp: !netConfig.Disabled, + ParentName: netConfig.ParentInterfaceName, + } + logger.Tracef("state device args for device: %+v", args) + devicesArgs = append(devicesArgs, args) + } + + if netConfig.CIDR == "" || netConfig.Address == "" { + logger.Tracef( + "skipping empty CIDR %q and/or Address %q of %q", + netConfig.CIDR, netConfig.Address, netConfig.InterfaceName, + ) + continue + } + _, ipNet, err := net.ParseCIDR(netConfig.CIDR) + if err != nil { + logger.Warningf("FIXME: ignoring unexpected CIDR format %q: %v", netConfig.CIDR, err) + continue + } + ipAddr := net.ParseIP(netConfig.Address) + if ipAddr == nil { + logger.Warningf("FIXME: ignoring unexpected Address format %q", netConfig.Address) + continue + } + ipNet.IP = ipAddr + cidrAddress := ipNet.String() + + var derivedConfigMethod state.AddressConfigMethod + switch method := state.AddressConfigMethod(netConfig.ConfigType); method { + case state.StaticAddress, state.DynamicAddress, + state.LoopbackAddress, state.ManualAddress: + derivedConfigMethod = method + case "dhcp": // awkward special case + derivedConfigMethod = state.DynamicAddress + default: + derivedConfigMethod = state.StaticAddress + } + + addr := state.LinkLayerDeviceAddress{ + DeviceName: netConfig.InterfaceName, + ProviderID: network.Id(netConfig.ProviderAddressId), + ConfigMethod: derivedConfigMethod, + CIDRAddress: cidrAddress, + DNSServers: netConfig.DNSServers, + DNSSearchDomains: netConfig.DNSSearchDomains, + GatewayAddress: netConfig.GatewayAddress, + } + logger.Tracef("state address args for device: %+v", addr) + devicesAddrs = append(devicesAddrs, addr) + } + logger.Tracef("seen devices: %+v", seenDeviceNames.SortedValues()) + logger.Tracef("network config transformed to state args:\n%+v\n%+v", devicesArgs, devicesAddrs) + return devicesArgs, devicesAddrs +} + +// ModelConfigGetter is used to get the current model configuration. +type ModelConfigGetter interface { + ModelConfig() (*config.Config, error) +} + +// NetworkingEnvironFromModelConfig constructs and returns +// environs.NetworkingEnviron using the given configGetter. Returns an error +// satisfying errors.IsNotSupported() if the model config does not support +// networking features. +func NetworkingEnvironFromModelConfig(configGetter ModelConfigGetter) (environs.NetworkingEnviron, error) { + modelConfig, err := configGetter.ModelConfig() + if err != nil { + return nil, errors.Annotate(err, "failed to get model config") + } + if modelConfig.Type() == "dummy" { + return nil, errors.NotSupportedf("dummy provider network config") + } + model, err := environs.New(modelConfig) + if err != nil { + return nil, errors.Annotate(err, "failed to construct a model from config") + } + netEnviron, supported := environs.SupportsNetworking(model) + if !supported { + // " not supported" will be appended to the message below. + return nil, errors.NotSupportedf("model %q networking", modelConfig.Name()) + } + return netEnviron, nil +} + +var vlanInterfaceNameRegex = regexp.MustCompile(`^.+\.[0-9]{1,4}[^0-9]?$`) + +var ( + netInterfaces = net.Interfaces + interfaceAddrs = (*net.Interface).Addrs +) + +// GetObservedNetworkConfig discovers what network interfaces exist on the +// machine, and returns that as a sorted slice of params.NetworkConfig to later +// update the state network config we have about the machine. +func GetObservedNetworkConfig() ([]params.NetworkConfig, error) { + logger.Tracef("discovering observed machine network config...") + + interfaces, err := netInterfaces() + if err != nil { + return nil, errors.Annotate(err, "cannot get network interfaces") + } + + var observedConfig []params.NetworkConfig + for _, nic := range interfaces { + isUp := nic.Flags&net.FlagUp > 0 + + derivedType := network.EthernetInterface + derivedConfigType := "" + if nic.Flags&net.FlagLoopback > 0 { + derivedType = network.LoopbackInterface + derivedConfigType = string(network.ConfigLoopback) + } else if vlanInterfaceNameRegex.MatchString(nic.Name) { + derivedType = network.VLAN_8021QInterface + } + + nicConfig := params.NetworkConfig{ + DeviceIndex: nic.Index, + MACAddress: nic.HardwareAddr.String(), + ConfigType: derivedConfigType, + MTU: nic.MTU, + InterfaceName: nic.Name, + InterfaceType: string(derivedType), + NoAutoStart: !isUp, + Disabled: !isUp, + } + + addrs, err := interfaceAddrs(&nic) + if err != nil { + return nil, errors.Annotatef(err, "cannot get interface %q addresses", nic.Name) + } + + if len(addrs) == 0 { + observedConfig = append(observedConfig, nicConfig) + logger.Warningf("no addresses observed on interface %q", nic.Name) + continue + } + + for _, addr := range addrs { + cidrAddress := addr.String() + if cidrAddress == "" { + continue + } + ip, ipNet, err := net.ParseCIDR(cidrAddress) + if err != nil { + logger.Warningf("cannot parse interface %q address %q as CIDR: %v", nic.Name, cidrAddress, err) + if ip := net.ParseIP(cidrAddress); ip == nil { + return nil, errors.Errorf("cannot parse interface %q IP address %q", nic.Name, cidrAddress) + } else { + ipNet = &net.IPNet{} + } + ipNet.IP = ip + ipNet.Mask = net.IPv4Mask(255, 255, 255, 0) + logger.Infof("assuming interface %q has observed address %q", nic.Name, ipNet.String()) + } + if ip.To4() == nil { + logger.Warningf("skipping observed IPv6 address %q on %q: not fully supported yet", ip, nic.Name) + continue + } + + nicConfigCopy := nicConfig + nicConfigCopy.CIDR = ipNet.String() + nicConfigCopy.Address = ip.String() + + // TODO(dimitern): Add DNS servers, search domains, and gateway + // later. + + observedConfig = append(observedConfig, nicConfigCopy) + } + } + sortedConfig := SortNetworkConfigsByParents(observedConfig) + + logger.Tracef("about to update network config with observed: %+v", sortedConfig) + return sortedConfig, nil +} + +// MergeProviderAndObservedNetworkConfigs returns the effective, sorted, network +// configs after merging providerConfig with observedConfig. +func MergeProviderAndObservedNetworkConfigs(providerConfigs, observedConfigs []params.NetworkConfig) []params.NetworkConfig { + providerConfigsByName := make(map[string][]params.NetworkConfig) + sortedProviderConfigs := SortNetworkConfigsByParents(providerConfigs) + for _, config := range sortedProviderConfigs { + name := config.InterfaceName + providerConfigsByName[name] = append(providerConfigsByName[name], config) + } + + jsonProviderConfig, err := NetworkConfigsToIndentedJSON(sortedProviderConfigs) + if err != nil { + logger.Warningf("cannot serialize provider config %#v as JSON: %v", sortedProviderConfigs, err) + } else { + logger.Debugf("provider network config of machine:\n%s", jsonProviderConfig) + } + + sortedObservedConfigs := SortNetworkConfigsByParents(observedConfigs) + + jsonObservedConfig, err := NetworkConfigsToIndentedJSON(sortedObservedConfigs) + if err != nil { + logger.Warningf("cannot serialize observed config %#v as JSON: %v", sortedObservedConfigs, err) + } else { + logger.Debugf("observed network config of machine:\n%s", jsonObservedConfig) + } + + var mergedConfigs []params.NetworkConfig + for _, config := range sortedObservedConfigs { + name := config.InterfaceName + logger.Tracef("merging observed config for device %q: %+v", name, config) + if strings.HasPrefix(name, instancecfg.DefaultBridgePrefix) { + logger.Tracef("found potential juju bridge %q in observed config", name) + unprefixedName := strings.TrimPrefix(name, instancecfg.DefaultBridgePrefix) + underlyingConfigs, underlyingKnownByProvider := providerConfigsByName[unprefixedName] + logger.Tracef("device %q underlying %q has provider config: %+v", name, unprefixedName, underlyingConfigs) + if underlyingKnownByProvider { + // This config is for a bridge created by Juju and not known by + // the provider. The bridge is configured to adopt the address + // allocated to the underlying interface, which is known by the + // provider. However, since the same underlying interface can + // have multiple addresses, we need to match the adopted + // bridgeConfig to the correct address. + + var underlyingConfig params.NetworkConfig + for i, underlying := range underlyingConfigs { + if underlying.Address == config.Address { + logger.Tracef("replacing undelying config %+v", underlying) + // Remove what we found before changing it below. + underlyingConfig = underlying + underlyingConfigs = append(underlyingConfigs[:i], underlyingConfigs[i+1:]...) + break + } + } + logger.Tracef("underlying provider config after update: %+v", underlyingConfigs) + + bridgeConfig := config + bridgeConfig.InterfaceType = string(network.BridgeInterface) + bridgeConfig.ConfigType = underlyingConfig.ConfigType + bridgeConfig.VLANTag = underlyingConfig.VLANTag + bridgeConfig.ProviderId = "" // Juju-created bridges never have a ProviderID + bridgeConfig.ProviderSpaceId = underlyingConfig.ProviderSpaceId + bridgeConfig.ProviderVLANId = underlyingConfig.ProviderVLANId + bridgeConfig.ProviderSubnetId = underlyingConfig.ProviderSubnetId + bridgeConfig.ProviderAddressId = underlyingConfig.ProviderAddressId + if underlyingParent := underlyingConfig.ParentInterfaceName; underlyingParent != "" { + bridgeConfig.ParentInterfaceName = instancecfg.DefaultBridgePrefix + underlyingParent + } + + underlyingConfig.ConfigType = string(network.ConfigManual) + underlyingConfig.ParentInterfaceName = name + underlyingConfig.ProviderAddressId = "" + underlyingConfig.CIDR = "" + underlyingConfig.Address = "" + + underlyingConfigs = append(underlyingConfigs, underlyingConfig) + providerConfigsByName[unprefixedName] = underlyingConfigs + logger.Tracef("updated provider network config by name: %+v", providerConfigsByName) + + mergedConfigs = append(mergedConfigs, bridgeConfig) + continue + } + } + + knownProviderConfigs, knownByProvider := providerConfigsByName[name] + if !knownByProvider { + // Not known by the provider and not a Juju-created bridge, so just + // use the observed config for it. + logger.Tracef("device %q not known to provider - adding only observed config: %+v", name, config) + mergedConfigs = append(mergedConfigs, config) + continue + } + logger.Tracef("device %q has known provider network config: %+v", name, knownProviderConfigs) + + for _, providerConfig := range knownProviderConfigs { + if providerConfig.Address == config.Address { + logger.Tracef( + "device %q has observed address %q, index %d, and MTU %q; overriding index %d and MTU %d from provider config", + name, config.Address, config.DeviceIndex, config.MTU, providerConfig.DeviceIndex, providerConfig.MTU, + ) + // Prefer observed device indices and MTU values as more up-to-date. + providerConfig.DeviceIndex = config.DeviceIndex + providerConfig.MTU = config.MTU + + mergedConfigs = append(mergedConfigs, providerConfig) + break + } + } + } + + sortedMergedConfigs := SortNetworkConfigsByParents(mergedConfigs) + + jsonMergedConfig, err := NetworkConfigsToIndentedJSON(sortedMergedConfigs) + if err != nil { + logger.Warningf("cannot serialize merged config %#v as JSON: %v", sortedMergedConfigs, err) + } else { + logger.Debugf("combined machine network config:\n%s", jsonMergedConfig) + } + + return mergedConfigs +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/types_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/types_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/networkingcommon/types_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/networkingcommon/types_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,974 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package networkingcommon_test + +import ( + "fmt" + "math/rand" + "net" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common/networkingcommon" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/network" + "github.com/juju/juju/state" + coretesting "github.com/juju/juju/testing" +) + +type TypesSuite struct { + coretesting.BaseSuite +} + +var _ = gc.Suite(&TypesSuite{}) + +func (s *TypesSuite) TestCopyNetworkConfig(c *gc.C) { + inputAndExpectedOutput := []params.NetworkConfig{{ + InterfaceName: "foo", + DNSServers: []string{"bar", "baz"}, + Address: "0.1.2.3", + }, { + DeviceIndex: 124, + ParentInterfaceName: "parent", + ProviderId: "nic-id", + }} + + output := networkingcommon.CopyNetworkConfigs(inputAndExpectedOutput) + c.Assert(output, jc.DeepEquals, inputAndExpectedOutput) +} + +func mustParseMAC(value string) net.HardwareAddr { + parsedMAC, err := net.ParseMAC(value) + if err != nil { + panic(fmt.Sprintf("cannot parse MAC %q: %v", value, err)) + } + return parsedMAC +} + +var exampleObservedInterfaces = []net.Interface{{ + Index: 1, + MTU: 65536, + Name: "lo", + Flags: net.FlagUp | net.FlagLoopback, +}, { + Index: 2, + MTU: 1500, + Name: "eth0", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 3, + MTU: 1500, + Name: "eth1", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 10, + MTU: 1500, + Name: "br-eth0", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 11, + MTU: 1500, + Name: "br-eth1", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 12, + MTU: 1500, + Name: "br-eth0.100", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 13, + MTU: 1500, + Name: "eth0.100", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 14, + MTU: 1500, + Name: "br-eth0.250", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 15, + MTU: 1500, + Name: "eth0.250", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 16, + MTU: 1500, + Name: "br-eth0.50", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 17, + MTU: 1500, + Name: "eth0.50", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f0"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 18, + MTU: 1500, + Name: "br-eth1.11", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 19, + MTU: 1500, + Name: "eth1.11", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 20, + MTU: 1500, + Name: "br-eth1.12", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 21, + MTU: 1500, + Name: "eth1.12", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 22, + MTU: 1500, + Name: "br-eth1.13", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}, { + Index: 23, + MTU: 1500, + Name: "eth1.13", + HardwareAddr: mustParseMAC("aa:bb:cc:dd:ee:f1"), + Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, +}} + +type fakeAddr string + +func (f fakeAddr) Network() string { return "" } +func (f fakeAddr) String() string { return string(f) } + +var _ net.Addr = (*fakeAddr)(nil) + +var exampleObservedInterfaceAddrs = map[string][]net.Addr{ + "eth0": nil, + "eth1": nil, + "eth0.50": nil, + "eth0.100": nil, + "eth0.25": nil, + "eth1.11": nil, + "eth1.12": nil, + "eth1.13": nil, + "lo": {fakeAddr("127.0.0.1/8"), fakeAddr("::1/128")}, + "br-eth0": {fakeAddr("10.20.19.100/24"), fakeAddr("10.20.19.123/24"), fakeAddr("fe80::5054:ff:fedd:eef0/64")}, + "br-eth1": {fakeAddr("10.20.19.105/24"), fakeAddr("fe80::5054:ff:fedd:eef1/64")}, + "br-eth0.50": {fakeAddr("10.50.19.100/24"), fakeAddr("fe80::5054:ff:fedd:eef0/64")}, + "br-eth0.100": {fakeAddr("10.100.19.100/24"), fakeAddr("fe80::5054:ff:fedd:eef0/64")}, + "br-eth0.250": {fakeAddr("10.250.19.100/24"), fakeAddr("fe80::5054:ff:fedd:eef0/64")}, + "br-eth1.11": {fakeAddr("10.11.19.101/24"), fakeAddr("fe80::5054:ff:fedd:eef1/64")}, + "br-eth1.12": {fakeAddr("10.12.19.101/24"), fakeAddr("fe80::5054:ff:fedd:eef1/64")}, + "br-eth1.13": {fakeAddr("10.13.19.101/24"), fakeAddr("fe80::5054:ff:fedd:eef1/64")}, +} + +var expectedSortedObservedNetworkConfigs = []params.NetworkConfig{{ + DeviceIndex: 1, + InterfaceName: "lo", + InterfaceType: string(network.LoopbackInterface), + MACAddress: "", + CIDR: "127.0.0.0/8", + Address: "127.0.0.1", + MTU: 65536, + ConfigType: string(network.ConfigLoopback), +}, { + DeviceIndex: 10, + InterfaceName: "br-eth0", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.20.19.0/24", + Address: "10.20.19.100", + MTU: 1500, +}, { + DeviceIndex: 10, + InterfaceName: "br-eth0", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.20.19.0/24", + Address: "10.20.19.123", + MTU: 1500, +}, { + DeviceIndex: 12, + InterfaceName: "br-eth0.100", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.100.19.0/24", + Address: "10.100.19.100", + MTU: 1500, +}, { + DeviceIndex: 14, + InterfaceName: "br-eth0.250", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.250.19.0/24", + Address: "10.250.19.100", + MTU: 1500, +}, { + DeviceIndex: 16, + InterfaceName: "br-eth0.50", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.50.19.0/24", + Address: "10.50.19.100", + MTU: 1500, +}, { + DeviceIndex: 2, + InterfaceName: "eth0", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, +}, { + DeviceIndex: 13, + InterfaceName: "eth0.100", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, +}, { + DeviceIndex: 15, + InterfaceName: "eth0.250", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, +}, { + DeviceIndex: 17, + InterfaceName: "eth0.50", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, +}, { + DeviceIndex: 11, + InterfaceName: "br-eth1", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.20.19.0/24", + Address: "10.20.19.105", + MTU: 1500, +}, { + DeviceIndex: 18, + InterfaceName: "br-eth1.11", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.11.19.0/24", + Address: "10.11.19.101", + MTU: 1500, +}, { + DeviceIndex: 20, + InterfaceName: "br-eth1.12", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.12.19.0/24", + Address: "10.12.19.101", + MTU: 1500, +}, { + DeviceIndex: 22, + InterfaceName: "br-eth1.13", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.13.19.0/24", + Address: "10.13.19.101", + MTU: 1500, +}, { + DeviceIndex: 3, + InterfaceName: "eth1", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, +}, { + DeviceIndex: 19, + InterfaceName: "eth1.11", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, +}, { + DeviceIndex: 21, + InterfaceName: "eth1.12", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, +}, { + DeviceIndex: 23, + InterfaceName: "eth1.13", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, +}} + +var expectedSortedProviderNetworkConfigs = []params.NetworkConfig{{ + InterfaceName: "eth0", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.20.19.0/24", + Address: "10.20.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "", + ProviderId: "3", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, + ProviderAddressId: "1287", +}, { + InterfaceName: "eth0", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.20.19.0/24", + Address: "10.20.19.123", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "", + ProviderId: "3", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, + ProviderAddressId: "1288", +}, { + InterfaceName: "eth0.100", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.100.19.0/24", + Address: "10.100.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "eth0", + ProviderId: "516", + ProviderSubnetId: "6", + ProviderVLANId: "5005", + VLANTag: 100, + ProviderAddressId: "1292", +}, { + InterfaceName: "eth0.250", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.250.19.0/24", + Address: "10.250.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "eth0", + ProviderId: "517", + ProviderSubnetId: "8", + ProviderVLANId: "5008", + VLANTag: 250, + ProviderAddressId: "1294", +}, { + InterfaceName: "eth0.50", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.50.19.0/24", + Address: "10.50.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "eth0", + ProviderId: "515", + ProviderSubnetId: "5", + ProviderVLANId: "5004", + VLANTag: 50, + ProviderAddressId: "1290", +}, { + InterfaceName: "eth1", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.20.19.0/24", + Address: "10.20.19.105", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "", + ProviderId: "245", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, + ProviderAddressId: "1295", +}, { + InterfaceName: "eth1.11", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.11.19.0/24", + Address: "10.11.19.101", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "eth1", + ProviderId: "518", + ProviderSubnetId: "9", + ProviderVLANId: "5013", + VLANTag: 11, + ProviderAddressId: "1298", +}, { + InterfaceName: "eth1.12", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.12.19.0/24", + Address: "10.12.19.101", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "eth1", + ProviderId: "519", + ProviderSubnetId: "10", + ProviderVLANId: "5014", + VLANTag: 12, + ProviderAddressId: "1300", +}, { + InterfaceName: "eth1.13", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.13.19.0/24", + Address: "10.13.19.101", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "eth1", + ProviderId: "520", + ProviderSubnetId: "11", + ProviderVLANId: "5015", + VLANTag: 13, + ProviderAddressId: "1302", +}} + +var expectedSortedMergedNetworkConfigs = []params.NetworkConfig{{ + DeviceIndex: 1, + InterfaceName: "lo", + InterfaceType: string(network.LoopbackInterface), + CIDR: "127.0.0.0/8", + Address: "127.0.0.1", + MTU: 65536, + ConfigType: string(network.ConfigLoopback), + ParentInterfaceName: "", +}, { + DeviceIndex: 10, + InterfaceName: "br-eth0", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.20.19.0/24", + Address: "10.20.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, + ProviderAddressId: "1287", +}, { + DeviceIndex: 10, + InterfaceName: "br-eth0", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.20.19.0/24", + Address: "10.20.19.123", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, + ProviderAddressId: "1288", +}, { + DeviceIndex: 12, + InterfaceName: "br-eth0.100", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.100.19.0/24", + Address: "10.100.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "br-eth0", + ProviderSubnetId: "6", + ProviderVLANId: "5005", + VLANTag: 100, + ProviderAddressId: "1292", +}, { + DeviceIndex: 14, + InterfaceName: "br-eth0.250", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.250.19.0/24", + Address: "10.250.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "br-eth0", + ProviderSubnetId: "8", + ProviderVLANId: "5008", + VLANTag: 250, + ProviderAddressId: "1294", +}, { + DeviceIndex: 16, + InterfaceName: "br-eth0.50", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "10.50.19.0/24", + Address: "10.50.19.100", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "br-eth0", + ProviderSubnetId: "5", + ProviderVLANId: "5004", + VLANTag: 50, + ProviderAddressId: "1290", +}, { + DeviceIndex: 2, + InterfaceName: "eth0", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth0", + ProviderId: "3", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, +}, { + DeviceIndex: 13, + InterfaceName: "eth0.100", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth0.100", + ProviderId: "516", + ProviderSubnetId: "6", + ProviderVLANId: "5005", + VLANTag: 100, +}, { + DeviceIndex: 15, + InterfaceName: "eth0.250", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth0.250", + ProviderId: "517", + ProviderSubnetId: "8", + ProviderVLANId: "5008", + VLANTag: 250, +}, { + DeviceIndex: 17, + InterfaceName: "eth0.50", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f0", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth0.50", + ProviderId: "515", + ProviderSubnetId: "5", + ProviderVLANId: "5004", + VLANTag: 50, +}, { + DeviceIndex: 11, + InterfaceName: "br-eth1", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.20.19.0/24", + Address: "10.20.19.105", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, + ProviderAddressId: "1295", +}, { + DeviceIndex: 18, + InterfaceName: "br-eth1.11", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.11.19.0/24", + Address: "10.11.19.101", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "br-eth1", + ProviderSubnetId: "9", + ProviderVLANId: "5013", + VLANTag: 11, + ProviderAddressId: "1298", +}, { + DeviceIndex: 20, + InterfaceName: "br-eth1.12", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.12.19.0/24", + Address: "10.12.19.101", + MTU: 1500, + ConfigType: string(network.ConfigStatic), + ParentInterfaceName: "br-eth1", + ProviderSubnetId: "10", + ProviderVLANId: "5014", + VLANTag: 12, + ProviderAddressId: "1300", +}, { + DeviceIndex: 22, + InterfaceName: "br-eth1.13", + InterfaceType: string(network.BridgeInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "10.13.19.0/24", + Address: "10.13.19.101", + MTU: 1500, + ParentInterfaceName: "br-eth1", + ConfigType: string(network.ConfigStatic), + ProviderSubnetId: "11", + ProviderVLANId: "5015", + VLANTag: 13, + ProviderAddressId: "1302", +}, { + DeviceIndex: 3, + InterfaceName: "eth1", + InterfaceType: string(network.EthernetInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth1", + ProviderId: "245", + ProviderSubnetId: "3", + ProviderVLANId: "5001", + VLANTag: 0, +}, { + DeviceIndex: 19, + InterfaceName: "eth1.11", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth1.11", + ProviderId: "518", + ProviderSubnetId: "9", + ProviderVLANId: "5013", + VLANTag: 11, +}, { + DeviceIndex: 21, + InterfaceName: "eth1.12", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth1.12", + ProviderId: "519", + ProviderSubnetId: "10", + ProviderVLANId: "5014", + VLANTag: 12, +}, { + DeviceIndex: 23, + InterfaceName: "eth1.13", + InterfaceType: string(network.VLAN_8021QInterface), + MACAddress: "aa:bb:cc:dd:ee:f1", + MTU: 1500, + ConfigType: string(network.ConfigManual), + ParentInterfaceName: "br-eth1.13", + ProviderId: "520", + ProviderSubnetId: "11", + ProviderVLANId: "5015", + VLANTag: 13, +}} + +var expectedSortedNetworkConfigsByInterfaceName = []params.NetworkConfig{ + {InterfaceName: "br-eth0"}, + {InterfaceName: "br-eth0.12"}, + {InterfaceName: "br-eth0.34"}, + {InterfaceName: "br-eth1"}, + {InterfaceName: "br-eth1.100"}, + {InterfaceName: "br-eth1.250"}, + {InterfaceName: "br-eth1.50"}, + {InterfaceName: "eth0"}, + {InterfaceName: "eth0.12"}, + {InterfaceName: "eth0.34"}, + {InterfaceName: "eth1"}, + {InterfaceName: "eth1.100"}, + {InterfaceName: "eth1.250"}, + {InterfaceName: "eth1.50"}, +} + +var expectedLinkLayerDeviceArgsWithMergedNetworkConfig = []state.LinkLayerDeviceArgs{{ + Name: "lo", + MTU: 65536, + Type: state.LoopbackDevice, + IsAutoStart: true, + IsUp: true, +}, { + Name: "br-eth0", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, +}, { + Name: "br-eth0.100", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth0", +}, { + Name: "br-eth0.250", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth0", +}, { + Name: "br-eth0.50", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth0", +}, { + Name: "eth0", + MTU: 1500, + ProviderID: "3", + Type: state.EthernetDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth0", +}, { + Name: "eth0.100", + MTU: 1500, + ProviderID: "516", + Type: state.VLAN_8021QDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth0.100", +}, { + Name: "eth0.250", + MTU: 1500, + ProviderID: "517", + Type: state.VLAN_8021QDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth0.250", +}, { + Name: "eth0.50", + MTU: 1500, + ProviderID: "515", + Type: state.VLAN_8021QDevice, + MACAddress: "aa:bb:cc:dd:ee:f0", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth0.50", +}, { + Name: "br-eth1", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, +}, { + Name: "br-eth1.11", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth1", +}, { + Name: "br-eth1.12", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth1", +}, { + Name: "br-eth1.13", + MTU: 1500, + Type: state.BridgeDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth1", +}, { + Name: "eth1", + MTU: 1500, + ProviderID: "245", + Type: state.EthernetDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth1", +}, { + Name: "eth1.11", + MTU: 1500, + ProviderID: "518", + Type: state.VLAN_8021QDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth1.11", +}, { + Name: "eth1.12", + MTU: 1500, + ProviderID: "519", + Type: state.VLAN_8021QDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth1.12", +}, { + Name: "eth1.13", + MTU: 1500, + ProviderID: "520", + Type: state.VLAN_8021QDevice, + MACAddress: "aa:bb:cc:dd:ee:f1", + IsAutoStart: true, + IsUp: true, + ParentName: "br-eth1.13", +}} + +var expectedLinkLayerDeviceAdressesWithMergedNetworkConfig = []state.LinkLayerDeviceAddress{{ + DeviceName: "lo", + ConfigMethod: state.LoopbackAddress, + CIDRAddress: "127.0.0.1/8", +}, { + DeviceName: "br-eth0", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.20.19.100/24", + ProviderID: "1287", +}, { + DeviceName: "br-eth0", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.20.19.123/24", + ProviderID: "1288", +}, { + DeviceName: "br-eth0.100", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.100.19.100/24", + ProviderID: "1292", +}, { + DeviceName: "br-eth0.250", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.250.19.100/24", + ProviderID: "1294", +}, { + DeviceName: "br-eth0.50", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.50.19.100/24", + ProviderID: "1290", +}, { + DeviceName: "br-eth1", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.20.19.105/24", + ProviderID: "1295", +}, { + DeviceName: "br-eth1.11", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.11.19.101/24", + ProviderID: "1298", +}, { + DeviceName: "br-eth1.12", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.12.19.101/24", + ProviderID: "1300", +}, { + DeviceName: "br-eth1.13", + ConfigMethod: state.StaticAddress, + CIDRAddress: "10.13.19.101/24", + ProviderID: "1302", +}} + +func (s *TypesSuite) TestSortNetworkConfigsByParentsWithObservedConfigs(c *gc.C) { + s.checkSortNetworkConfigsByParentsWithAllInputPremutationsMatches(c, expectedSortedObservedNetworkConfigs) +} + +func (s *TypesSuite) checkSortNetworkConfigsByParentsWithAllInputPremutationsMatches(c *gc.C, expectedOutput []params.NetworkConfig) { + expectedLength := len(expectedOutput) + jsonExpected := s.networkConfigsAsJSON(c, expectedOutput) + for i := 0; i < expectedLength; i++ { + shuffledInput := shuffleNetworkConfigs(expectedOutput) + result := networkingcommon.SortNetworkConfigsByParents(shuffledInput) + c.Assert(result, gc.HasLen, expectedLength) + jsonResult := s.networkConfigsAsJSON(c, result) + c.Check(jsonResult, gc.Equals, jsonExpected) + } +} + +func (s *TypesSuite) networkConfigsAsJSON(c *gc.C, input []params.NetworkConfig) string { + asJSON, err := networkingcommon.NetworkConfigsToIndentedJSON(input) + c.Assert(err, jc.ErrorIsNil) + return asJSON +} + +func shuffleNetworkConfigs(input []params.NetworkConfig) []params.NetworkConfig { + inputLength := len(input) + output := make([]params.NetworkConfig, inputLength) + shuffled := rand.Perm(inputLength) + for i, j := range shuffled { + output[i] = input[j] + } + return output +} + +func (s *TypesSuite) TestSortNetworkConfigsByParentsWithProviderConfigs(c *gc.C) { + s.checkSortNetworkConfigsByParentsWithAllInputPremutationsMatches(c, expectedSortedProviderNetworkConfigs) +} + +func (s *TypesSuite) TestSortNetworkConfigsByParentsWithMergedConfigs(c *gc.C) { + s.checkSortNetworkConfigsByParentsWithAllInputPremutationsMatches(c, expectedSortedMergedNetworkConfigs) +} + +func (s *TypesSuite) TestSortNetworkConfigsByInterfaceName(c *gc.C) { + expectedLength := len(expectedSortedNetworkConfigsByInterfaceName) + jsonExpected := s.networkConfigsAsJSON(c, expectedSortedNetworkConfigsByInterfaceName) + for i := 0; i < expectedLength; i++ { + shuffledInput := shuffleNetworkConfigs(expectedSortedNetworkConfigsByInterfaceName) + result := networkingcommon.SortNetworkConfigsByInterfaceName(shuffledInput) + c.Assert(result, gc.HasLen, expectedLength) + jsonResult := s.networkConfigsAsJSON(c, result) + c.Check(jsonResult, gc.Equals, jsonExpected) + } +} + +func (s *TypesSuite) TestMergeProviderAndObservedNetworkConfigs(c *gc.C) { + observedConfigsLength := len(expectedSortedObservedNetworkConfigs) + providerConfigsLength := len(expectedSortedProviderNetworkConfigs) + jsonExpected := s.networkConfigsAsJSON(c, expectedSortedMergedNetworkConfigs) + for i := 0; i < observedConfigsLength; i++ { + shuffledObservedConfigs := shuffleNetworkConfigs(expectedSortedObservedNetworkConfigs) + for j := 0; j < providerConfigsLength; j++ { + shuffledProviderConfigs := shuffleNetworkConfigs(expectedSortedProviderNetworkConfigs) + + mergedConfigs := networkingcommon.MergeProviderAndObservedNetworkConfigs(shuffledProviderConfigs, shuffledObservedConfigs) + jsonResult := s.networkConfigsAsJSON(c, mergedConfigs) + c.Check(jsonResult, gc.Equals, jsonExpected) + } + } +} + +func (s *TypesSuite) TestGetObservedNetworkConfig(c *gc.C) { + s.PatchValue(networkingcommon.NetInterfaces, func() ([]net.Interface, error) { + return exampleObservedInterfaces, nil + }) + s.PatchValue(networkingcommon.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) { + c.Assert(i, gc.NotNil) + if addrs, found := exampleObservedInterfaceAddrs[i.Name]; found { + return addrs, nil + } + return nil, nil + }) + + observedConfig, err := networkingcommon.GetObservedNetworkConfig() + c.Assert(err, jc.ErrorIsNil) + jsonResult := s.networkConfigsAsJSON(c, observedConfig) + jsonExpected := s.networkConfigsAsJSON(c, expectedSortedObservedNetworkConfigs) + c.Check(jsonResult, gc.Equals, jsonExpected) +} + +func (s *TypesSuite) TestNetworkConfigsToStateArgs(c *gc.C) { + devicesArgs, devicesAddrs := networkingcommon.NetworkConfigsToStateArgs(expectedSortedMergedNetworkConfigs) + + c.Check(devicesArgs, jc.DeepEquals, expectedLinkLayerDeviceArgsWithMergedNetworkConfig) + c.Check(devicesAddrs, jc.DeepEquals, expectedLinkLayerDeviceAdressesWithMergedNetworkConfig) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/registry.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/registry.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/registry.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/registry.go 2016-04-28 06:03:34.000000000 +0000 @@ -8,11 +8,13 @@ "reflect" "runtime" "sort" + "strings" "github.com/juju/errors" "github.com/juju/names" "github.com/juju/utils/featureflag" + "github.com/juju/juju/apiserver/common/apihttp" "github.com/juju/juju/state" ) @@ -308,3 +310,47 @@ } } } + +var endpointRegistry = map[string]apihttp.HandlerSpec{} +var endpointRegistryOrder []string + +// RegisterAPIModelEndpoint adds the provided endpoint to the registry. +// The pattern is prefixed with the model pattern: /model/:modeluuid. +func RegisterAPIModelEndpoint(pattern string, spec apihttp.HandlerSpec) error { + if !strings.HasPrefix(pattern, "/") { + pattern = "/" + pattern + } + pattern = "/model/:modeluuid" + pattern + return registerAPIEndpoint(pattern, spec) +} + +func registerAPIEndpoint(pattern string, spec apihttp.HandlerSpec) error { + if _, ok := endpointRegistry[pattern]; ok { + return errors.NewAlreadyExists(nil, fmt.Sprintf("endpoint %q already registered", pattern)) + } + endpointRegistry[pattern] = spec + endpointRegistryOrder = append(endpointRegistryOrder, pattern) + return nil +} + +// DefaultHTTPMethods are the HTTP methods supported by default by the API. +var DefaultHTTPMethods = []string{"GET", "POST", "HEAD", "PUT", "DEL", "OPTIONS"} + +// ResolveAPIEndpoints builds the set of endpoint handlers for all +// registered API endpoints. +func ResolveAPIEndpoints(newArgs func(apihttp.HandlerConstraints) apihttp.NewHandlerArgs) []apihttp.Endpoint { + var endpoints []apihttp.Endpoint + for _, pattern := range endpointRegistryOrder { + spec := endpointRegistry[pattern] + args := newArgs(spec.Constraints) + handler := spec.NewHandler(args) + for _, method := range DefaultHTTPMethods { + endpoints = append(endpoints, apihttp.Endpoint{ + Pattern: pattern, + Method: method, + Handler: handler, + }) + } + } + return endpoints +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/resource.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/resource.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/resource.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/resource.go 2016-04-28 06:03:34.000000000 +0000 @@ -141,3 +141,13 @@ func (s StringResource) String() string { return string(s) } + +// ValueResource is a Resource with a no-op Stop method, containing an +// interface{} value. +type ValueResource struct { + Value interface{} +} + +func (r ValueResource) Stop() error { + return nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/storagecommon/mock_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/storagecommon/mock_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/storagecommon/mock_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/storagecommon/mock_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,7 +7,6 @@ "github.com/juju/errors" "github.com/juju/names" "github.com/juju/testing" - "launchpad.net/tomb" "github.com/juju/juju/apiserver/common/storagecommon" "github.com/juju/juju/state" @@ -143,25 +142,6 @@ return nil, errors.NotFoundf("pool") } -type fakeNotifyWatcher struct { - tomb.Tomb - ch chan struct{} -} - -func (w *fakeNotifyWatcher) Kill() { - w.Tomb.Kill(nil) - w.Tomb.Done() -} - -func (w *fakeNotifyWatcher) Stop() error { - w.Kill() - return w.Wait() -} - -func (w *fakeNotifyWatcher) Changes() <-chan struct{} { - return w.ch -} - type nopSyncStarter struct{} func (nopSyncStarter) StartSync() {} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/storagecommon/storage.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/storagecommon/storage.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/storagecommon/storage.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/storagecommon/storage.go 2016-04-28 06:03:34.000000000 +0000 @@ -256,8 +256,7 @@ storageInstance state.StorageInstance, cfg *config.Config, ) (map[string]string, error) { - uuid, _ := cfg.UUID() - storageTags := tags.ResourceTags(names.NewModelTag(uuid), cfg) + storageTags := tags.ResourceTags(names.NewModelTag(cfg.UUID()), cfg) if storageInstance != nil { storageTags[tags.JujuStorageInstance] = storageInstance.Tag().Id() storageTags[tags.JujuStorageOwner] = storageInstance.Owner().Id() diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/storagecommon/storage_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/storagecommon/storage_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/storagecommon/storage_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/storagecommon/storage_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -12,6 +12,7 @@ gc "gopkg.in/check.v1" "github.com/juju/juju/apiserver/common/storagecommon" + apiservertesting "github.com/juju/juju/apiserver/testing" "github.com/juju/juju/state" statetesting "github.com/juju/juju/state/testing" "github.com/juju/juju/storage" @@ -155,9 +156,9 @@ st *fakeStorage storageInstance *fakeStorageInstance volume *fakeVolume - volumeAttachmentWatcher *fakeNotifyWatcher - blockDevicesWatcher *fakeNotifyWatcher - storageAttachmentWatcher *fakeNotifyWatcher + volumeAttachmentWatcher *apiservertesting.FakeNotifyWatcher + blockDevicesWatcher *apiservertesting.FakeNotifyWatcher + storageAttachmentWatcher *apiservertesting.FakeNotifyWatcher } var _ = gc.Suite(&watchStorageAttachmentSuite{}) @@ -172,12 +173,12 @@ kind: state.StorageKindBlock, } s.volume = &fakeVolume{tag: names.NewVolumeTag("0")} - s.volumeAttachmentWatcher = &fakeNotifyWatcher{ch: make(chan struct{}, 1)} - s.blockDevicesWatcher = &fakeNotifyWatcher{ch: make(chan struct{}, 1)} - s.storageAttachmentWatcher = &fakeNotifyWatcher{ch: make(chan struct{}, 1)} - s.volumeAttachmentWatcher.ch <- struct{}{} - s.blockDevicesWatcher.ch <- struct{}{} - s.storageAttachmentWatcher.ch <- struct{}{} + s.volumeAttachmentWatcher = apiservertesting.NewFakeNotifyWatcher() + s.volumeAttachmentWatcher.C <- struct{}{} + s.blockDevicesWatcher = apiservertesting.NewFakeNotifyWatcher() + s.blockDevicesWatcher.C <- struct{}{} + s.storageAttachmentWatcher = apiservertesting.NewFakeNotifyWatcher() + s.storageAttachmentWatcher.C <- struct{}{} s.st = &fakeStorage{ storageInstance: func(tag names.StorageTag) (state.StorageInstance, error) { return s.storageInstance, nil @@ -199,19 +200,19 @@ func (s *watchStorageAttachmentSuite) TestWatchStorageAttachmentVolumeAttachmentChanges(c *gc.C) { s.testWatchBlockStorageAttachment(c, func() { - s.volumeAttachmentWatcher.ch <- struct{}{} + s.volumeAttachmentWatcher.C <- struct{}{} }) } func (s *watchStorageAttachmentSuite) TestWatchStorageAttachmentStorageAttachmentChanges(c *gc.C) { s.testWatchBlockStorageAttachment(c, func() { - s.storageAttachmentWatcher.ch <- struct{}{} + s.storageAttachmentWatcher.C <- struct{}{} }) } func (s *watchStorageAttachmentSuite) TestWatchStorageAttachmentBlockDevicesChange(c *gc.C) { s.testWatchBlockStorageAttachment(c, func() { - s.blockDevicesWatcher.ch <- struct{}{} + s.blockDevicesWatcher.C <- struct{}{} }) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/tools.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/tools.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/tools.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/tools.go 2016-04-28 06:03:34.000000000 +0000 @@ -10,6 +10,7 @@ "github.com/juju/errors" "github.com/juju/names" + "github.com/juju/version" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/environs" @@ -19,7 +20,6 @@ "github.com/juju/juju/state" "github.com/juju/juju/state/binarystorage" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) var envtoolsFindTools = envtools.FindTools diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/tools_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/tools_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/tools_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/tools_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,6 +11,7 @@ jc "github.com/juju/testing/checkers" "github.com/juju/utils/arch" "github.com/juju/utils/series" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/apiserver/common" @@ -22,7 +23,7 @@ "github.com/juju/juju/state" "github.com/juju/juju/state/binarystorage" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type toolsSuite struct { @@ -33,7 +34,7 @@ var _ = gc.Suite(&toolsSuite{}) var current = version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -213,17 +214,17 @@ s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 }) s.PatchValue(&series.HostSeries, func() string { return "trusty" }) - s.PatchValue(&version.Current, version.MustParseBinary("1.22-beta1-trusty-amd64").Number) + s.PatchValue(&jujuversion.Current, version.MustParseBinary("1.22-beta1-trusty-amd64").Number) s.testFindToolsExact(c, mockToolsStorage, true, true) - s.PatchValue(&version.Current, version.MustParseBinary("1.22.0-trusty-amd64").Number) + s.PatchValue(&jujuversion.Current, version.MustParseBinary("1.22.0-trusty-amd64").Number) s.testFindToolsExact(c, mockToolsStorage, true, false) } func (s *toolsSuite) TestFindToolsExactNotInStorage(c *gc.C) { mockToolsStorage := &mockToolsStorage{} - s.PatchValue(&version.Current, version.MustParse("1.22-beta1")) + s.PatchValue(&jujuversion.Current, version.MustParse("1.22-beta1")) s.testFindToolsExact(c, mockToolsStorage, false, true) - s.PatchValue(&version.Current, version.MustParse("1.22.0")) + s.PatchValue(&jujuversion.Current, version.MustParse("1.22.0")) s.testFindToolsExact(c, mockToolsStorage, false, false) } @@ -231,7 +232,7 @@ var called bool s.PatchValue(common.EnvtoolsFindTools, func(e environs.Environ, major, minor int, stream string, filter coretools.Filter) (list coretools.List, err error) { called = true - c.Assert(filter.Number, gc.Equals, version.Current) + c.Assert(filter.Number, gc.Equals, jujuversion.Current) c.Assert(filter.Series, gc.Equals, series.HostSeries()) c.Assert(filter.Arch, gc.Equals, arch.HostArch()) if develVersion { @@ -243,7 +244,7 @@ }) toolsFinder := common.NewToolsFinder(s.State, t, sprintfURLGetter("tools:%s")) result, err := toolsFinder.FindTools(params.FindToolsParams{ - Number: version.Current, + Number: jujuversion.Current, MajorVersion: -1, MinorVersion: -1, Series: series.HostSeries(), diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/watch.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/watch.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/watch.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/watch.go 2016-04-28 06:03:34.000000000 +0000 @@ -136,6 +136,7 @@ return case <-in: if timer == nil { + // TODO(fwereade): 2016-03-17 lp:1558657 timer = time.After(10 * time.Millisecond) } case <-timer: diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/common/watch_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/common/watch_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/common/watch_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/common/watch_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -9,7 +9,6 @@ "github.com/juju/names" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" - "launchpad.net/tomb" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" @@ -28,37 +27,10 @@ } func (a *fakeAgentEntityWatcher) Watch() state.NotifyWatcher { - changes := make(chan struct{}, 1) + w := apiservertesting.NewFakeNotifyWatcher() // Simulate initial event. - changes <- struct{}{} - return &fakeNotifyWatcher{changes: changes} -} - -type fakeNotifyWatcher struct { - tomb tomb.Tomb - changes chan struct{} -} - -func (w *fakeNotifyWatcher) Stop() error { - w.Kill() - return w.Wait() -} - -func (w *fakeNotifyWatcher) Kill() { - w.tomb.Kill(nil) - w.tomb.Done() -} - -func (w *fakeNotifyWatcher) Wait() error { - return w.tomb.Wait() -} - -func (w *fakeNotifyWatcher) Err() error { - return w.tomb.Err() -} - -func (w *fakeNotifyWatcher) Changes() <-chan struct{} { - return w.changes + w.C <- struct{}{} + return w } func (*agentEntityWatcherSuite) TestWatch(c *gc.C) { @@ -127,10 +99,10 @@ var _ = gc.Suite(&multiNotifyWatcherSuite{}) func (*multiNotifyWatcherSuite) TestMultiNotifyWatcher(c *gc.C) { - w0 := &fakeNotifyWatcher{changes: make(chan struct{}, 1)} - w1 := &fakeNotifyWatcher{changes: make(chan struct{}, 1)} - w0.changes <- struct{}{} - w1.changes <- struct{}{} + w0 := apiservertesting.NewFakeNotifyWatcher() + w0.C <- struct{}{} + w1 := apiservertesting.NewFakeNotifyWatcher() + w1.C <- struct{}{} mw := common.NewMultiNotifyWatcher(w0, w1) defer statetesting.AssertStop(c, mw) @@ -138,21 +110,21 @@ wc := statetesting.NewNotifyWatcherC(c, nopSyncStarter{}, mw) wc.AssertOneChange() - w0.changes <- struct{}{} + w0.C <- struct{}{} wc.AssertOneChange() - w1.changes <- struct{}{} + w1.C <- struct{}{} wc.AssertOneChange() - w0.changes <- struct{}{} - w1.changes <- struct{}{} + w0.C <- struct{}{} + w1.C <- struct{}{} wc.AssertOneChange() } func (*multiNotifyWatcherSuite) TestMultiNotifyWatcherStop(c *gc.C) { - w0 := &fakeNotifyWatcher{changes: make(chan struct{}, 1)} - w1 := &fakeNotifyWatcher{changes: make(chan struct{}, 1)} - w0.changes <- struct{}{} - w1.changes <- struct{}{} + w0 := apiservertesting.NewFakeNotifyWatcher() + w0.C <- struct{}{} + w1 := apiservertesting.NewFakeNotifyWatcher() + w1.C <- struct{}{} mw := common.NewMultiNotifyWatcher(w0, w1) wc := statetesting.NewNotifyWatcherC(c, nopSyncStarter{}, mw) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/controller/controller.go charm-2.2.0/src/github.com/juju/juju/apiserver/controller/controller.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/controller/controller.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/controller/controller.go 2016-04-28 06:03:34.000000000 +0000 @@ -15,6 +15,7 @@ "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" "github.com/juju/juju/state" ) @@ -33,6 +34,7 @@ RemoveBlocks(args params.RemoveBlocksArgs) error WatchAllModels() (params.AllWatcherId, error) ModelStatus(req params.Entities) (params.ModelStatusResults, error) + InitiateModelMigration(params.InitiateModelMigrationArgs) (params.InitiateModelMigrationResults, error) } // ControllerAPI implements the environment manager interface and is @@ -257,6 +259,74 @@ return results, nil } +// InitiateModelMigration attempts to begin the migration of one or +// more models to other controllers. +func (c *ControllerAPI) InitiateModelMigration(reqArgs params.InitiateModelMigrationArgs) ( + params.InitiateModelMigrationResults, error, +) { + out := params.InitiateModelMigrationResults{ + Results: make([]params.InitiateModelMigrationResult, len(reqArgs.Specs)), + } + for i, spec := range reqArgs.Specs { + result := &out.Results[i] + result.ModelTag = spec.ModelTag + id, err := c.initiateOneModelMigration(spec) + if err != nil { + result.Error = common.ServerError(err) + } else { + result.Id = id + } + } + return out, nil +} + +func (c *ControllerAPI) initiateOneModelMigration(spec params.ModelMigrationSpec) (string, error) { + modelTag, err := names.ParseModelTag(spec.ModelTag) + if err != nil { + return "", errors.Annotate(err, "model tag") + } + + // Ensure the model exists. + if _, err := c.state.GetModel(modelTag); err != nil { + return "", errors.Annotate(err, "unable to read model") + } + + // Get State for model. + hostedState, err := c.state.ForModel(modelTag) + if err != nil { + return "", errors.Trace(err) + } + defer hostedState.Close() + + // Start the migration. + targetInfo := spec.TargetInfo + + controllerTag, err := names.ParseModelTag(targetInfo.ControllerTag) + if err != nil { + return "", errors.Annotate(err, "controller tag") + } + authTag, err := names.ParseUserTag(targetInfo.AuthTag) + if err != nil { + return "", errors.Annotate(err, "auth tag") + } + + args := state.ModelMigrationSpec{ + InitiatedBy: c.apiUser, + TargetInfo: migration.TargetInfo{ + ControllerTag: controllerTag, + Addrs: targetInfo.Addrs, + CACert: targetInfo.CACert, + AuthTag: authTag, + Password: targetInfo.Password, + }, + } + mig, err := hostedState.CreateModelMigration(args) + if err != nil { + return "", errors.Trace(err) + } + return mig.Id(), nil +} + func (c *ControllerAPI) environStatus(tag string) (params.ModelStatus, error) { var status params.ModelStatus modelTag, err := names.ParseModelTag(tag) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/controller/controller_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/controller/controller_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/controller/controller_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/controller/controller_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -9,6 +9,7 @@ "github.com/juju/loggo" "github.com/juju/names" jc "github.com/juju/testing/checkers" + "github.com/juju/utils" gc "gopkg.in/check.v1" "github.com/juju/juju/apiserver" @@ -94,7 +95,7 @@ response, err := s.controller.AllModels() c.Assert(err, jc.ErrorIsNil) // The results are sorted. - expected := []string{"dummymodel", "no-access", "owned", "user"} + expected := []string{"admin", "no-access", "owned", "user"} var obtained []string for _, env := range response.UserModels { obtained = append(obtained, env.Name) @@ -120,7 +121,7 @@ c.Assert(list.Models, jc.DeepEquals, []params.ModelBlockInfo{ params.ModelBlockInfo{ - Name: "dummymodel", + Name: "admin", UUID: s.State.ModelUUID(), OwnerTag: s.AdminUserTag(c).String(), Blocks: []string{ @@ -150,7 +151,7 @@ func (s *controllerSuite) TestModelConfig(c *gc.C) { env, err := s.controller.ModelConfig() c.Assert(err, jc.ErrorIsNil) - c.Assert(env.Config["name"], gc.Equals, "dummymodel") + c.Assert(env.Config["name"], gc.Equals, "admin") } func (s *controllerSuite) TestModelConfigFromNonController(c *gc.C) { @@ -163,7 +164,7 @@ c.Assert(err, jc.ErrorIsNil) env, err := controller.ModelConfig() c.Assert(err, jc.ErrorIsNil) - c.Assert(env.Config["name"], gc.Equals, "dummymodel") + c.Assert(env.Config["name"], gc.Equals, "admin") } func (s *controllerSuite) TestRemoveBlocks(c *gc.C) { @@ -223,9 +224,8 @@ func (s *controllerSuite) TestModelStatus(c *gc.C) { otherEnvOwner := s.Factory.MakeModelUser(c, nil) otherSt := s.Factory.MakeModel(c, &factory.ModelParams{ - Name: "dummytoo", - Owner: otherEnvOwner.UserTag(), - Prepare: true, + Name: "dummytoo", + Owner: otherEnvOwner.UserTag(), ConfigAttrs: testing.Attrs{ "controller": false, }, @@ -267,3 +267,122 @@ Life: params.Alive, }}) } + +func (s *controllerSuite) TestInitiateModelMigration(c *gc.C) { + // Create two hosted models to migrate. + st1 := s.Factory.MakeModel(c, nil) + defer st1.Close() + + st2 := s.Factory.MakeModel(c, nil) + defer st2.Close() + + // Kick off the migration. + args := params.InitiateModelMigrationArgs{ + Specs: []params.ModelMigrationSpec{ + { + ModelTag: st1.ModelTag().String(), + TargetInfo: params.ModelMigrationTargetInfo{ + ControllerTag: randomModelTag(), + Addrs: []string{"1.1.1.1:1111", "2.2.2.2:2222"}, + CACert: "cert1", + AuthTag: names.NewUserTag("admin1").String(), + Password: "secret1", + }, + }, { + ModelTag: st2.ModelTag().String(), + TargetInfo: params.ModelMigrationTargetInfo{ + ControllerTag: randomModelTag(), + Addrs: []string{"3.3.3.3:3333"}, + CACert: "cert2", + AuthTag: names.NewUserTag("admin2").String(), + Password: "secret2", + }, + }, + }, + } + out, err := s.controller.InitiateModelMigration(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(out.Results, gc.HasLen, 2) + + states := []*state.State{st1, st2} + for i, spec := range args.Specs { + st := states[i] + result := out.Results[i] + + c.Check(result.Error, gc.IsNil) + c.Check(result.ModelTag, gc.Equals, spec.ModelTag) + expectedId := st.ModelUUID() + ":0" + c.Check(result.Id, gc.Equals, expectedId) + + // Ensure the migration made it into the DB correctly. + mig, err := st.GetModelMigration() + c.Assert(err, jc.ErrorIsNil) + c.Check(mig.Id(), gc.Equals, expectedId) + c.Check(mig.ModelUUID(), gc.Equals, st.ModelUUID()) + c.Check(mig.InitiatedBy(), gc.Equals, s.AdminUserTag(c).Id()) + targetInfo, err := mig.TargetInfo() + c.Assert(err, jc.ErrorIsNil) + c.Check(targetInfo.ControllerTag.String(), gc.Equals, spec.TargetInfo.ControllerTag) + c.Check(targetInfo.Addrs, jc.SameContents, spec.TargetInfo.Addrs) + c.Check(targetInfo.CACert, gc.Equals, spec.TargetInfo.CACert) + c.Check(targetInfo.AuthTag.String(), gc.Equals, spec.TargetInfo.AuthTag) + c.Check(targetInfo.Password, gc.Equals, spec.TargetInfo.Password) + } +} + +func (s *controllerSuite) TestInitiateModelMigrationValidationError(c *gc.C) { + // Create a hosted model to migrate. + st := s.Factory.MakeModel(c, nil) + defer st.Close() + + // Kick off the migration with missing details. + args := params.InitiateModelMigrationArgs{ + Specs: []params.ModelMigrationSpec{{ + ModelTag: st.ModelTag().String(), + // TargetInfo missing + }}, + } + out, err := s.controller.InitiateModelMigration(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(out.Results, gc.HasLen, 1) + result := out.Results[0] + c.Check(result.ModelTag, gc.Equals, args.Specs[0].ModelTag) + c.Check(result.Id, gc.Equals, "") + c.Check(result.Error, gc.ErrorMatches, "controller tag: .+ is not a valid tag") +} + +func (s *controllerSuite) TestInitiateModelMigrationPartialFailure(c *gc.C) { + st := s.Factory.MakeModel(c, nil) + defer st.Close() + + args := params.InitiateModelMigrationArgs{ + Specs: []params.ModelMigrationSpec{ + { + ModelTag: st.ModelTag().String(), + TargetInfo: params.ModelMigrationTargetInfo{ + ControllerTag: randomModelTag(), + Addrs: []string{"1.1.1.1:1111", "2.2.2.2:2222"}, + CACert: "cert", + AuthTag: names.NewUserTag("admin").String(), + Password: "secret", + }, + }, { + ModelTag: randomModelTag(), // Doesn't exist. + }, + }, + } + out, err := s.controller.InitiateModelMigration(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(out.Results, gc.HasLen, 2) + + c.Check(out.Results[0].ModelTag, gc.Equals, st.ModelTag().String()) + c.Check(out.Results[0].Error, gc.IsNil) + + c.Check(out.Results[1].ModelTag, gc.Equals, args.Specs[1].ModelTag) + c.Check(out.Results[1].Error, gc.ErrorMatches, "unable to read model: .+") +} + +func randomModelTag() string { + uuid := utils.MustNewUUID().String() + return names.NewModelTag(uuid).String() +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/controller/destroy.go charm-2.2.0/src/github.com/juju/juju/apiserver/controller/destroy.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/controller/destroy.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/controller/destroy.go 2016-04-28 06:03:34.000000000 +0000 @@ -14,6 +14,12 @@ // DestroyController will attempt to destroy the controller. If the args // specify the removal of blocks or the destruction of the models, this // method will attempt to do so. +// +// If the controller has any non-Dead hosted models, then an error with +// the code params.CodeHasHostedModels will be transmitted, regardless of +// the value of the DestroyModels parameter. This is to inform the client +// that it should wait for hosted models to be completely cleaned up +// before proceeding. func (s *ControllerAPI) DestroyController(args params.DestroyControllerArgs) error { controllerEnv, err := s.state.ControllerModel() if err != nil { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/controller/destroy_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/controller/destroy_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/controller/destroy_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/controller/destroy_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -56,9 +56,8 @@ s.otherEnvOwner = names.NewUserTag("jess@dummy") s.otherState = factory.NewFactory(s.State).MakeModel(c, &factory.ModelParams{ - Name: "dummytoo", - Owner: s.otherEnvOwner, - Prepare: true, + Name: "dummytoo", + Owner: s.otherEnvOwner, ConfigAttrs: testing.Attrs{ "controller": false, }, diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/debuglog_db.go charm-2.2.0/src/github.com/juju/juju/apiserver/debuglog_db.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/debuglog_db.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/debuglog_db.go 2016-04-28 06:03:34.000000000 +0000 @@ -24,7 +24,10 @@ stop <-chan struct{}, ) error { params := makeLogTailerParams(reqParams) - tailer := newLogTailer(st, params) + tailer, err := newLogTailer(st, params) + if err != nil { + return errors.Trace(err) + } defer tailer.Stop() // Indicate that all is well. @@ -87,6 +90,6 @@ var newLogTailer = _newLogTailer // For replacing in tests -func _newLogTailer(st state.LoggingState, params *state.LogTailerParams) state.LogTailer { +func _newLogTailer(st state.LoggingState, params *state.LogTailerParams) (state.LogTailer, error) { return state.NewLogTailer(st, params) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/debuglog_db_internal_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/debuglog_db_internal_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/debuglog_db_internal_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/debuglog_db_internal_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -40,7 +40,7 @@ } called := false - s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) state.LogTailer { + s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) (state.LogTailer, error) { called = true // Start time will be used once the client is extended to send @@ -54,7 +54,7 @@ c.Assert(params.ExcludeEntity, jc.DeepEquals, []string{"baz"}) c.Assert(params.ExcludeModule, jc.DeepEquals, []string{"qux"}) - return newFakeLogTailer() + return newFakeLogTailer(), nil }) stop := make(chan struct{}) @@ -71,13 +71,13 @@ } called := false - s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) state.LogTailer { + s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) (state.LogTailer, error) { called = true c.Assert(params.StartTime.IsZero(), jc.IsTrue) c.Assert(params.InitialLines, gc.Equals, 0) - return newFakeLogTailer() + return newFakeLogTailer(), nil }) stop := make(chan struct{}) @@ -106,8 +106,8 @@ Level: loggo.ERROR, Message: "whoops", } - s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) state.LogTailer { - return tailer + s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) (state.LogTailer, error) { + return tailer, nil }) stop := make(chan struct{}) @@ -126,9 +126,9 @@ func (s *debugLogDBIntSuite) TestRequestStopsWhenTailerStops(c *gc.C) { tailer := newFakeLogTailer() - s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) state.LogTailer { + s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) (state.LogTailer, error) { close(tailer.logsCh) // make the request stop immediately - return tailer + return tailer, nil }) err := handleDebugLogDBRequest(nil, &debugLogParams{}, s.sock, nil) @@ -149,8 +149,8 @@ Message: "stuff happened", } } - s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) state.LogTailer { - return tailer + s.PatchValue(&newLogTailer, func(_ state.LoggingState, params *state.LogTailerParams) (state.LogTailer, error) { + return tailer, nil }) done := s.runRequest(&debugLogParams{maxLines: 3}, nil) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/export_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,7 +11,6 @@ "github.com/juju/names" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" - "gopkg.in/macaroon-bakery.v1/bakery" "gopkg.in/macaroon.v1" "github.com/juju/juju/apiserver/authentication" @@ -27,6 +26,7 @@ MongoPingInterval = &mongoPingInterval NewBackups = &newBackups AllowedMethodsDuringUpgrades = allowedMethodsDuringUpgrades + BZMimeType = bzMimeType JSMimeType = jsMimeType SpritePath = spritePath ) @@ -36,15 +36,15 @@ if err != nil { return nil, err } - return auth.(*authentication.MacaroonAuthenticator).Macaroon, nil + return auth.(*authentication.ExternalMacaroonAuthenticator).Macaroon, nil } -func ServerBakeryService(srv *Server) (*bakery.Service, error) { +func ServerBakeryService(srv *Server) (authentication.BakeryService, error) { auth, err := srv.authCtxt.macaroonAuth() if err != nil { return nil, err } - return auth.(*authentication.MacaroonAuthenticator).Service, nil + return auth.(*authentication.ExternalMacaroonAuthenticator).Service, nil } // ServerAuthenticatorForTag calls the authenticatorForTag method @@ -91,9 +91,12 @@ // TestingApiHandler gives you an ApiHandler that isn't connected to // anything real. It's enough to let test some basic functionality though. func TestingApiHandler(c *gc.C, srvSt, st *state.State) (*apiHandler, *common.Resources) { + authCtxt, err := newAuthContext(srvSt) + c.Assert(err, jc.ErrorIsNil) srv := &Server{ - state: srvSt, - tag: names.NewMachineTag("0"), + authCtxt: authCtxt, + state: srvSt, + tag: names.NewMachineTag("0"), } h, err := newApiHandler(srv, st, nil, nil, st.ModelUUID()) c.Assert(err, jc.ErrorIsNil) @@ -188,3 +191,25 @@ func (srv *Server) Addr() *net.TCPAddr { return srv.lis.Addr().(*net.TCPAddr) // cannot fail } + +// PatchGetMigrationBackend overrides the getMigrationBackend function +// to support testing. +func PatchGetMigrationBackend(p Patcher, st migrationBackend) { + p.PatchValue(&getMigrationBackend, func(*state.State) migrationBackend { + return st + }) +} + +// PatchGetControllerCACert overrides the getControllerCACert function +// to support testing. +func PatchGetControllerCACert(p Patcher, caCert string) { + p.PatchValue(&getControllerCACert, func(migrationBackend) (string, error) { + return caCert, nil + }) +} + +// Patcher defines an interface that matches the PatchValue method on +// CleanupSuite +type Patcher interface { + PatchValue(ptr, value interface{}) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/gui.go charm-2.2.0/src/github.com/juju/juju/apiserver/gui.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/gui.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/gui.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,7 +5,9 @@ import ( "archive/tar" + "bytes" "compress/bzip2" + "encoding/json" "fmt" "io" "io/ioutil" @@ -18,12 +20,20 @@ "strings" "text/template" - "github.com/bmizerany/pat" "github.com/juju/errors" + "github.com/juju/version" agenttools "github.com/juju/juju/agent/tools" + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/common/apihttp" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" "github.com/juju/juju/state/binarystorage" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" +) + +const ( + bzMimeType = "application/x-tar-bzip2" ) var ( @@ -44,39 +54,49 @@ // required to render the Juju GUI index file; // - the "jujugui" directory includes a "templates/index.html.go" file which is // used to render the Juju GUI index. The template receives at least the -// following variables in its context: "comboURL", "configURL", "debug" -// and "spriteContent". It might receive more variables but cannot assume -// them to be always provided; +// following variables in its context: "staticURL", comboURL", "configURL", +// "debug" and "spriteContent". It might receive more variables but cannot +// assume them to be always provided; // - the "jujugui" directory includes a "templates/config.js.go" file which is // used to render the Juju GUI configuration file. The template receives at // least the following variables in its context: "base", "host", "socket", -// "uuid" and "version". It might receive more variables but cannot assume -// them to be always provided. +// "staticURL", "uuid" and "version". It might receive more variables but +// cannot assume them to be always provided. type guiRouter struct { dataDir string ctxt httpContext pattern string } -// handleGUI adds the Juju GUI routes to the given serve mux. -// The given pattern is used as base URL path, and is assumed to include -// ":modeluuid" and a trailing slash. -func handleGUI(mux *pat.PatternServeMux, pattern string, dataDir string, ctxt httpContext) { +func guiEndpoints(pattern, dataDir string, ctxt httpContext) []apihttp.Endpoint { gr := &guiRouter{ dataDir: dataDir, ctxt: ctxt, pattern: pattern, } - guiHandleAll := func(pattern string, h func(*guiHandler, http.ResponseWriter, *http.Request)) { - handleAll(mux, pattern, gr.ensureFileHandler(h)) + var endpoints []apihttp.Endpoint + add := func(pattern string, h func(*guiHandler, http.ResponseWriter, *http.Request)) { + handler := gr.ensureFileHandler(h) + // TODO: We can switch from all methods to specific ones for entries + // where we only want to support specific request methods. However, our + // tests currently assert that errors come back as application/json and + // pat only does "text/plain" responses. + for _, method := range common.DefaultHTTPMethods { + endpoints = append(endpoints, apihttp.Endpoint{ + Pattern: pattern, + Method: method, + Handler: handler, + }) + } } hashedPattern := pattern + ":hash" - guiHandleAll(hashedPattern+"/config.js", (*guiHandler).serveConfig) - guiHandleAll(hashedPattern+"/combo", (*guiHandler).serveCombo) - guiHandleAll(hashedPattern+"/static/", (*guiHandler).serveStatic) + add(hashedPattern+"/config.js", (*guiHandler).serveConfig) + add(hashedPattern+"/combo", (*guiHandler).serveCombo) + add(hashedPattern+"/static/", (*guiHandler).serveStatic) // The index is served when all remaining URLs are requested, so that // the single page JavaScript application can properly handles its routes. - guiHandleAll(pattern, (*guiHandler).serveIndex) + add(pattern, (*guiHandler).serveIndex) + return endpoints } // ensureFileHandler decorates the given function to ensure the Juju GUI files @@ -121,7 +141,7 @@ return "", "", errors.Annotate(err, "cannot open GUI storage") } defer storage.Close() - vers, hash, err := guiVersionAndHash(storage) + vers, hash, err := guiVersionAndHash(st, storage) if err != nil { return "", "", errors.Trace(err) } @@ -162,17 +182,19 @@ // guiVersionAndHash returns the version and the SHA256 hash of the current // Juju GUI archive. -func guiVersionAndHash(storage binarystorage.Storage) (vers, hash string, err error) { - // TODO frankban: retrieve current GUI version from somewhere. - // For now, just return an arbitrary version from the storage. - allMeta, err := storage.AllMetadata() +func guiVersionAndHash(st *state.State, storage binarystorage.Storage) (vers, hash string, err error) { + currentVers, err := st.GUIVersion() + if errors.IsNotFound(err) { + return "", "", errors.NotFoundf("Juju GUI") + } if err != nil { - return "", "", errors.Annotate(err, "cannot retrieve GUI metadata") + return "", "", errors.Annotate(err, "cannot retrieve current GUI version") } - if len(allMeta) == 0 { - return "", "", errors.NotFoundf("Juju GUI") + metadata, err := storage.Metadata(currentVers.String()) + if err != nil { + return "", "", errors.Annotate(err, "cannot retrieve GUI metadata") } - return allMeta[0].Version, allMeta[0].SHA256, nil + return metadata.Version, metadata.SHA256, nil } // uncompressGUI uncompresses the tar.bz2 Juju GUI archive provided in r. @@ -303,6 +325,9 @@ } tmpl := filepath.Join(h.rootDir, "templates", "index.html.go") renderGUITemplate(w, tmpl, map[string]interface{}{ + // staticURL holds the root of the static hierarchy, hence why the + // empty string is used here. + "staticURL": h.hashedPath(""), "comboURL": h.hashedPath("combo"), "configURL": h.hashedPath("config.js"), // TODO frankban: make it possible to enable debug. @@ -316,11 +341,14 @@ w.Header().Set("Content-Type", jsMimeType) tmpl := filepath.Join(h.rootDir, "templates", "config.js.go") renderGUITemplate(w, tmpl, map[string]interface{}{ - "base": h.baseURLPath, - "host": req.Host, - "socket": "/model/$uuid/api", - "uuid": h.uuid, - "version": version.Current.String(), + "base": h.baseURLPath, + "host": req.Host, + "socket": "/model/$uuid/api", + // staticURL holds the root of the static hierarchy, hence why the + // empty string is used here. + "staticURL": h.hashedPath(""), + "uuid": h.uuid, + "version": jujuversion.Current.String(), }) } @@ -341,3 +369,186 @@ sendError(w, errors.Annotate(err, "cannot render template")) } } + +// guiArchiveHandler serves the Juju GUI archive endpoints, used for uploading +// and retrieving information about GUI archives. +type guiArchiveHandler struct { + ctxt httpContext +} + +// ServeHTTP implements http.Handler. +func (h *guiArchiveHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + var handler func(http.ResponseWriter, *http.Request) error + switch req.Method { + case "GET": + handler = h.handleGet + case "POST": + handler = h.handlePost + default: + sendError(w, errors.MethodNotAllowedf("unsupported method: %q", req.Method)) + return + } + if err := handler(w, req); err != nil { + sendError(w, errors.Trace(err)) + } +} + +// handleGet returns information on Juju GUI archives in the controller. +func (h *guiArchiveHandler) handleGet(w http.ResponseWriter, req *http.Request) error { + // Open the GUI archive storage. + st, err := h.ctxt.stateForRequestUnauthenticated(req) + if err != nil { + return errors.Annotate(err, "cannot open state") + } + storage, err := st.GUIStorage() + if err != nil { + return errors.Annotate(err, "cannot open GUI storage") + } + defer storage.Close() + + // Retrieve metadata information. + allMeta, err := storage.AllMetadata() + if err != nil { + return errors.Annotate(err, "cannot retrieve GUI metadata") + } + + // Prepare and send the response. + var currentVersion string + vers, err := st.GUIVersion() + if err == nil { + currentVersion = vers.String() + } else if !errors.IsNotFound(err) { + return errors.Annotate(err, "cannot retrieve current GUI version") + } + versions := make([]params.GUIArchiveVersion, len(allMeta)) + for i, m := range allMeta { + vers, err := version.Parse(m.Version) + if err != nil { + return errors.Annotate(err, "cannot parse GUI version") + } + versions[i] = params.GUIArchiveVersion{ + Version: vers, + SHA256: m.SHA256, + Current: m.Version == currentVersion, + } + } + sendStatusAndJSON(w, http.StatusOK, params.GUIArchiveResponse{ + Versions: versions, + }) + return nil +} + +// handlePost is used to upload new Juju GUI archives to the controller. +func (h *guiArchiveHandler) handlePost(w http.ResponseWriter, req *http.Request) error { + // Validate the request. + if ctype := req.Header.Get("Content-Type"); ctype != bzMimeType { + return errors.BadRequestf("invalid content type %q: expected %q", ctype, bzMimeType) + } + if err := req.ParseForm(); err != nil { + return errors.Annotate(err, "cannot parse form") + } + versParam := req.Form.Get("version") + if versParam == "" { + return errors.BadRequestf("version parameter not provided") + } + vers, err := version.Parse(versParam) + if err != nil { + return errors.BadRequestf("invalid version parameter %q", versParam) + } + hashParam := req.Form.Get("hash") + if hashParam == "" { + return errors.BadRequestf("hash parameter not provided") + } + if req.ContentLength == -1 { + return errors.BadRequestf("content length not provided") + } + + // Open the GUI archive storage. + st, _, err := h.ctxt.stateForRequestAuthenticatedUser(req) + if err != nil { + return errors.Annotate(err, "cannot open state") + } + storage, err := st.GUIStorage() + if err != nil { + return errors.Annotate(err, "cannot open GUI storage") + } + defer storage.Close() + + // Read and validate the archive data. + data, hash, err := readAndHash(req.Body) + size := int64(len(data)) + if size != req.ContentLength { + return errors.BadRequestf("archive does not match provided content length") + } + if hash != hashParam { + return errors.BadRequestf("archive does not match provided hash") + } + + // Add the archive to the GUI storage. + metadata := binarystorage.Metadata{ + Version: vers.String(), + Size: size, + SHA256: hash, + } + if err := storage.Add(bytes.NewReader(data), metadata); err != nil { + return errors.Annotate(err, "cannot add GUI archive to storage") + } + + // Prepare and return the response. + resp := params.GUIArchiveVersion{ + Version: vers, + SHA256: hash, + } + if currentVers, err := st.GUIVersion(); err == nil { + if currentVers == vers { + resp.Current = true + } + } else if !errors.IsNotFound(err) { + return errors.Annotate(err, "cannot retrieve current GUI version") + } + sendStatusAndJSON(w, http.StatusOK, resp) + return nil +} + +// guiVersionHandler is used to select the Juju GUI version served by the +// controller. The specified version must be available in the controller. +type guiVersionHandler struct { + ctxt httpContext +} + +// ServeHTTP implements http.Handler. +func (h *guiVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.Method != "PUT" { + sendError(w, errors.MethodNotAllowedf("unsupported method: %q", req.Method)) + return + } + if err := h.handlePut(w, req); err != nil { + sendError(w, errors.Trace(err)) + } +} + +// handlePut is used to switch to a specific Juju GUI version. +func (h *guiVersionHandler) handlePut(w http.ResponseWriter, req *http.Request) error { + // Validate the request. + if ctype := req.Header.Get("Content-Type"); ctype != params.ContentTypeJSON { + return errors.BadRequestf("invalid content type %q: expected %q", ctype, params.ContentTypeJSON) + } + + // Authenticate the request and retrieve the Juju state. + st, _, err := h.ctxt.stateForRequestAuthenticatedUser(req) + if err != nil { + return errors.Annotate(err, "cannot open state") + } + + var selected params.GUIVersionRequest + decoder := json.NewDecoder(req.Body) + if err := decoder.Decode(&selected); err != nil { + return errors.NewBadRequest(err, "invalid request body") + } + + // Switch to the provided GUI version. + if err = st.GUISetVersion(selected.Version); err != nil { + return errors.Trace(err) + } + return nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/gui_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/gui_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/gui_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/gui_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -12,6 +12,7 @@ "io" "io/ioutil" "net/http" + "net/url" "os" "os/exec" "path/filepath" @@ -19,13 +20,14 @@ "strings" jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" agenttools "github.com/juju/juju/agent/tools" "github.com/juju/juju/apiserver" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/state/binarystorage" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) const ( @@ -65,6 +67,9 @@ // Optionally it can return a GUI archive hash which is used by the test // to build the URL path for the HTTP request. setup guiSetupFunc + // currentVersion optionally holds the GUI version that must be set as + // current right after setup is called and before the test is run. + currentVersion string // pathAndquery holds the optional path and query for the request, for // instance "/combo?file". If not provided, the "/" path is used. pathAndquery string @@ -87,7 +92,8 @@ about: "GUI directory is a file", setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { err := storage.Add(strings.NewReader(""), binarystorage.Metadata{ - SHA256: "fake-hash", + SHA256: "fake-hash", + Version: "2.1.0", }) c.Assert(err, jc.ErrorIsNil) err = os.MkdirAll(baseDir, 0755) @@ -97,38 +103,55 @@ c.Assert(err, jc.ErrorIsNil) return "" }, + currentVersion: "2.1.0", expectedStatus: http.StatusInternalServerError, expectedError: "cannot use Juju GUI root directory .*", }, { about: "GUI directory is unaccessible", setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { err := storage.Add(strings.NewReader(""), binarystorage.Metadata{ - SHA256: "fake-hash", + SHA256: "fake-hash", + Version: "2.2.0", }) c.Assert(err, jc.ErrorIsNil) err = os.MkdirAll(baseDir, 0000) c.Assert(err, jc.ErrorIsNil) return "" }, + currentVersion: "2.2.0", expectedStatus: http.StatusInternalServerError, expectedError: "cannot stat Juju GUI root directory: .*", }, { about: "invalid GUI archive", setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { err := storage.Add(strings.NewReader(""), binarystorage.Metadata{ - SHA256: "fake-hash", + SHA256: "fake-hash", + Version: "2.3.0", }) c.Assert(err, jc.ErrorIsNil) return "" }, + currentVersion: "2.3.0", expectedStatus: http.StatusInternalServerError, expectedError: "cannot uncompress Juju GUI archive: cannot parse archive: .*", }, { + about: "GUI current version not set", + setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { + err := storage.Add(strings.NewReader(""), binarystorage.Metadata{ + SHA256: "fake-hash", + }) + c.Assert(err, jc.ErrorIsNil) + return "" + }, + expectedStatus: http.StatusNotFound, + expectedError: "Juju GUI not found", +}, { about: "index: sprite file not found", setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { setupGUIArchive(c, storage, "2.0.42", nil) return "" }, + currentVersion: "2.0.42", expectedStatus: http.StatusInternalServerError, expectedError: "cannot read sprite file: .*", }, { @@ -139,6 +162,7 @@ }) return "" }, + currentVersion: "2.0.42", expectedStatus: http.StatusInternalServerError, expectedError: "cannot parse template: .*: no such file or directory", }, { @@ -150,6 +174,7 @@ }) return "" }, + currentVersion: "2.0.47", expectedStatus: http.StatusInternalServerError, expectedError: `cannot parse template: template: index.html.go:1: unexpected ".47" .*`, }, { @@ -161,6 +186,7 @@ }) return "" }, + currentVersion: "2.0.47", expectedStatus: http.StatusInternalServerError, expectedError: `cannot render template: template: .*: range can't iterate over .*`, }, { @@ -168,6 +194,7 @@ setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { return setupGUIArchive(c, storage, "2.0.42", nil) }, + currentVersion: "2.0.42", pathAndquery: "/config.js", expectedStatus: http.StatusInternalServerError, expectedError: "cannot parse template: .*: no such file or directory", @@ -178,6 +205,7 @@ guiConfigPath: "{{.BadWolf.47}}", }) }, + currentVersion: "2.0.47", pathAndquery: "/config.js", expectedStatus: http.StatusInternalServerError, expectedError: `cannot parse template: template: config.js.go:1: unexpected ".47" .*`, @@ -187,6 +215,7 @@ setupGUIArchive(c, storage, "2.0.47", nil) return "invalid" }, + currentVersion: "2.0.47", pathAndquery: "/config.js", expectedStatus: http.StatusNotFound, expectedError: `resource with "invalid" hash not found`, @@ -195,6 +224,7 @@ setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { return setupGUIArchive(c, storage, "1.0.0", nil) }, + currentVersion: "1.0.0", pathAndquery: "/combo?foo&%%", expectedStatus: http.StatusBadRequest, expectedError: `cannot combine files: invalid file name "%": invalid URL escape "%%"`, @@ -203,6 +233,7 @@ setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { return setupGUIArchive(c, storage, "1.0.0", nil) }, + currentVersion: "1.0.0", pathAndquery: "/combo?../../../../../../etc/passwd", expectedStatus: http.StatusBadRequest, expectedError: `cannot combine files: forbidden file path "../../../../../../etc/passwd"`, @@ -212,6 +243,7 @@ setupGUIArchive(c, storage, "2.0.47", nil) return "invalid" }, + currentVersion: "2.0.47", pathAndquery: "/combo?foo", expectedStatus: http.StatusNotFound, expectedError: `resource with "invalid" hash not found`, @@ -225,6 +257,7 @@ "static/gui/build/borg.js": "cube", }) }, + currentVersion: "1.0.0", pathAndquery: "/combo?voy/janeway.js&tng/picard.js&borg.js&ds9/sisko.js", expectedStatus: http.StatusOK, expectedContentType: apiserver.JSMimeType, @@ -244,6 +277,7 @@ "static/gui/build/foo.css": "my-style", }) }, + currentVersion: "1.0.0", pathAndquery: "/combo?no-such.css&foo.css&bad-wolf.css", expectedStatus: http.StatusOK, expectedContentType: "text/css; charset=utf-8", @@ -257,6 +291,7 @@ "static/file.js": "static file content", }) }, + currentVersion: "1.0.0", pathAndquery: "/static/file.js", expectedStatus: http.StatusOK, expectedContentType: apiserver.JSMimeType, @@ -267,9 +302,24 @@ setupGUIArchive(c, storage, "2.0.47", nil) return "bad-wolf" }, + currentVersion: "2.0.47", pathAndquery: "/static/file.js", expectedStatus: http.StatusNotFound, expectedError: `resource with "bad-wolf" hash not found`, +}, { + about: "static files: old version hash", + setup: func(c *gc.C, baseDir string, storage binarystorage.Storage) string { + setupGUIArchive(c, storage, "2.1.1", map[string]string{ + "static/file.js": "static file version 2.1.1", + }) + return setupGUIArchive(c, storage, "2.1.2", map[string]string{ + "static/file.js": "static file version 2.1.2", + }) + }, + currentVersion: "2.1.1", + pathAndquery: "/static/file.js", + expectedStatus: http.StatusNotFound, + expectedError: `resource with ".*" hash not found`, }} func (s *guiSuite) TestGUIHandler(c *gc.C) { @@ -278,7 +328,7 @@ // only served from Linux machines. c.Skip("bzip2 command not available") } - sendRequest := func(setup guiSetupFunc, pathAndquery string) *http.Response { + sendRequest := func(setup guiSetupFunc, currentVersion, pathAndquery string) *http.Response { // Set up the GUI base directory. datadir := filepath.ToSlash(s.DataDir()) baseDir := filepath.FromSlash(agenttools.SharedGUIDir(datadir)) @@ -302,6 +352,12 @@ hash = setup(c, baseDir, storage) } + // Set the current GUI version if required. + if currentVersion != "" { + err := s.State.GUISetVersion(version.MustParse(currentVersion)) + c.Assert(err, jc.ErrorIsNil) + } + // Send a request to the test path. if pathAndquery == "" { pathAndquery = "/" @@ -318,14 +374,14 @@ s.Reset(c) // Perform the request. - resp := sendRequest(test.setup, test.pathAndquery) + resp := sendRequest(test.setup, test.currentVersion, test.pathAndquery) // Check the response. if test.expectedStatus == 0 { test.expectedStatus = http.StatusOK } if test.expectedError != "" { - test.expectedContentType = "application/json" + test.expectedContentType = params.ContentTypeJSON } body := assertResponse(c, resp, test.expectedStatus, test.expectedContentType) if test.expectedError == "" { @@ -349,20 +405,26 @@ + staticURL: {{.staticURL}} comboURL: {{.comboURL}} configURL: {{.configURL}} debug: {{.debug}} spriteContent: {{.spriteContent}} ` - hash := setupGUIArchive(c, storage, "2.0.0", map[string]string{ + vers := version.MustParse("2.0.0") + hash := setupGUIArchive(c, storage, vers.String(), map[string]string{ guiIndexPath: indexContent, apiserver.SpritePath: "sprite content", }) + err = s.State.GUISetVersion(vers) + c.Assert(err, jc.ErrorIsNil) + expectedIndexContent := fmt.Sprintf(` + staticURL: /gui/%[1]s/%[2]s comboURL: /gui/%[1]s/%[2]s/combo configURL: /gui/%[1]s/%[2]s/config.js debug: false @@ -384,6 +446,45 @@ c.Assert(string(body), gc.Equals, expectedIndexContent) } +func (s *guiSuite) TestGUIIndexVersions(c *gc.C) { + storage, err := s.State.GUIStorage() + c.Assert(err, jc.ErrorIsNil) + defer storage.Close() + + // Create Juju GUI archives and save it into the storage. + setupGUIArchive(c, storage, "1.0.0", map[string]string{ + guiIndexPath: "index version 1.0.0", + apiserver.SpritePath: "sprite content", + }) + vers2 := version.MustParse("2.0.0") + setupGUIArchive(c, storage, vers2.String(), map[string]string{ + guiIndexPath: "index version 2.0.0", + apiserver.SpritePath: "sprite content", + }) + vers3 := version.MustParse("3.0.0") + setupGUIArchive(c, storage, vers3.String(), map[string]string{ + guiIndexPath: "index version 3.0.0", + apiserver.SpritePath: "sprite content", + }) + + // Check that the correct index version is served. + err = s.State.GUISetVersion(vers2) + c.Assert(err, jc.ErrorIsNil) + resp := s.sendRequest(c, httpRequestParams{ + url: s.guiURL(c, "", "/"), + }) + body := assertResponse(c, resp, http.StatusOK, "text/plain; charset=utf-8") + c.Assert(string(body), gc.Equals, "index version 2.0.0") + + err = s.State.GUISetVersion(vers3) + c.Assert(err, jc.ErrorIsNil) + resp = s.sendRequest(c, httpRequestParams{ + url: s.guiURL(c, "", "/"), + }) + body = assertResponse(c, resp, http.StatusOK, "text/plain; charset=utf-8") + c.Assert(string(body), gc.Equals, "index version 3.0.0") +} + func (s *guiSuite) TestGUIConfig(c *gc.C) { storage, err := s.State.GUIStorage() c.Assert(err, jc.ErrorIsNil) @@ -396,21 +497,27 @@ base: '{{.base}}', host: '{{.host}}', socket: '{{.socket}}', + staticURL: '{{.staticURL}}', uuid: '{{.uuid}}', version: '{{.version}}' };` - hash := setupGUIArchive(c, storage, "2.0.0", map[string]string{ + vers := version.MustParse("2.0.0") + hash := setupGUIArchive(c, storage, vers.String(), map[string]string{ guiConfigPath: configContent, }) + err = s.State.GUISetVersion(vers) + c.Assert(err, jc.ErrorIsNil) + expectedConfigContent := fmt.Sprintf(` var config = { // This is just an example and does not reflect the real Juju GUI config. - base: '/gui/%s/', - host: '%s', + base: '/gui/%[1]s/', + host: '%[2]s', socket: '/model/$uuid/api', - uuid: '%s', - version: '%s' -};`, s.modelUUID, s.baseURL(c).Host, s.modelUUID, version.Current) + staticURL: '/gui/%[1]s/%[3]s', + uuid: '%[1]s', + version: '%[4]s' +};`, s.modelUUID, s.baseURL(c).Host, hash, jujuversion.Current) // Make a request for the Juju GUI config. resp := s.sendRequest(c, httpRequestParams{ @@ -427,10 +534,13 @@ // Create a Juju GUI archive and save it into the storage. indexContent := "Exterminate!" - hash := setupGUIArchive(c, storage, "2.0.0", map[string]string{ + vers := version.MustParse("2.0.0") + hash := setupGUIArchive(c, storage, vers.String(), map[string]string{ guiIndexPath: indexContent, apiserver.SpritePath: "", }) + err = s.State.GUISetVersion(vers) + c.Assert(err, jc.ErrorIsNil) // Initially the GUI directory on the server is empty. baseDir := agenttools.SharedGUIDir(s.DataDir()) @@ -452,6 +562,395 @@ c.Assert(string(b), gc.Equals, indexContent) } +type guiArchiveSuite struct { + authHttpSuite +} + +var _ = gc.Suite(&guiArchiveSuite{}) + +// guiURL returns the URL used to retrieve info on or upload Juju GUI archives. +func (s *guiArchiveSuite) guiURL(c *gc.C) string { + u := s.baseURL(c) + u.Path = "/gui-archive" + return u.String() +} + +func (s *guiArchiveSuite) TestGUIArchiveMethodNotAllowed(c *gc.C) { + resp := s.authRequest(c, httpRequestParams{ + method: "PUT", + url: s.guiURL(c), + }) + body := assertResponse(c, resp, http.StatusMethodNotAllowed, params.ContentTypeJSON) + var jsonResp params.ErrorResult + err := json.Unmarshal(body, &jsonResp) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResp.Error.Message, gc.Matches, `unsupported method: "PUT"`) +} + +var guiArchiveGetTests = []struct { + about string + versions []string + current string +}{{ + about: "empty storage", +}, { + about: "one version", + versions: []string{"2.42.0"}, +}, { + about: "one version (current)", + versions: []string{"2.42.0"}, + current: "2.42.0", +}, { + about: "multiple versions", + versions: []string{"2.42.0", "3.0.0", "2.47.1"}, +}, { + about: "multiple versions (current)", + versions: []string{"2.42.0", "3.0.0", "2.47.1"}, + current: "3.0.0", +}} + +func (s *guiArchiveSuite) TestGUIArchiveGet(c *gc.C) { + for i, test := range guiArchiveGetTests { + c.Logf("\n%d: %s", i, test.about) + + uploadVersions := func(versions []string, current string) params.GUIArchiveResponse { + // Open the GUI storage. + storage, err := s.State.GUIStorage() + c.Assert(err, jc.ErrorIsNil) + defer storage.Close() + + // Add the versions to the storage. + expectedVersions := make([]params.GUIArchiveVersion, len(versions)) + for i, vers := range versions { + files := map[string]string{"file": fmt.Sprintf("content %d", i)} + v := version.MustParse(vers) + hash := setupGUIArchive(c, storage, vers, files) + expectedVersions[i] = params.GUIArchiveVersion{ + Version: v, + SHA256: hash, + } + if vers == current { + err := s.State.GUISetVersion(v) + c.Assert(err, jc.ErrorIsNil) + expectedVersions[i].Current = true + } + } + return params.GUIArchiveResponse{ + Versions: expectedVersions, + } + } + + // Reset the db so that the GUI storage is empty in each test. + s.Reset(c) + + // Send the request to retrieve GUI version information. + expectedResponse := uploadVersions(test.versions, test.current) + resp := s.sendRequest(c, httpRequestParams{ + url: s.guiURL(c), + }) + + // Check that a successful response is returned. + body := assertResponse(c, resp, http.StatusOK, params.ContentTypeJSON) + var jsonResponse params.GUIArchiveResponse + err := json.Unmarshal(body, &jsonResponse) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResponse, jc.DeepEquals, expectedResponse) + } +} + +var guiArchivePostErrorsTests = []struct { + about string + contentType string + query string + noContentLength bool + expectedStatus int + expectedError string +}{{ + about: "no content type", + expectedStatus: http.StatusBadRequest, + expectedError: fmt.Sprintf(`invalid content type "": expected %q`, apiserver.BZMimeType), +}, { + about: "invalid content type", + contentType: "text/html", + expectedStatus: http.StatusBadRequest, + expectedError: fmt.Sprintf(`invalid content type "text/html": expected %q`, apiserver.BZMimeType), +}, { + about: "no version provided", + contentType: apiserver.BZMimeType, + expectedStatus: http.StatusBadRequest, + expectedError: "version parameter not provided", +}, { + about: "invalid version", + contentType: apiserver.BZMimeType, + query: "?version=bad-wolf", + expectedStatus: http.StatusBadRequest, + expectedError: `invalid version parameter "bad-wolf"`, +}, { + about: "no content length provided", + contentType: apiserver.BZMimeType, + query: "?version=2.0.42&hash=sha", + noContentLength: true, + expectedStatus: http.StatusBadRequest, + expectedError: "content length not provided", +}, { + about: "no hash provided", + contentType: apiserver.BZMimeType, + query: "?version=2.0.42", + expectedStatus: http.StatusBadRequest, + expectedError: "hash parameter not provided", +}, { + about: "content hash mismatch", + contentType: apiserver.BZMimeType, + query: "?version=2.0.42&hash=bad-wolf", + expectedStatus: http.StatusBadRequest, + expectedError: "archive does not match provided hash", +}} + +func (s *guiArchiveSuite) TestGUIArchivePostErrors(c *gc.C) { + type exoticReader struct { + io.Reader + } + for i, test := range guiArchivePostErrorsTests { + c.Logf("\n%d: %s", i, test.about) + + // Prepare the request. + var r io.Reader = strings.NewReader("archive contents") + if test.noContentLength { + // net/http will automatically add a Content-Length header if it + // sees *strings.Reader, but not if it's a type it doesn't know. + r = exoticReader{r} + } + + // Send the request and retrieve the error response. + resp := s.authRequest(c, httpRequestParams{ + method: "POST", + url: s.guiURL(c) + test.query, + contentType: test.contentType, + body: r, + }) + body := assertResponse(c, resp, test.expectedStatus, params.ContentTypeJSON) + var jsonResp params.ErrorResult + err := json.Unmarshal(body, &jsonResp) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResp.Error.Message, gc.Matches, test.expectedError) + } +} + +func (s *guiArchiveSuite) TestGUIArchivePostErrorUnauthorized(c *gc.C) { + resp := s.sendRequest(c, httpRequestParams{ + method: "POST", + url: s.guiURL(c) + "?version=2.0.0&hash=sha", + contentType: apiserver.BZMimeType, + body: strings.NewReader("archive contents"), + }) + body := assertResponse(c, resp, http.StatusUnauthorized, params.ContentTypeJSON) + var jsonResp params.ErrorResult + err := json.Unmarshal(body, &jsonResp) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResp.Error.Message, gc.Matches, "cannot open state: no credentials provided") +} + +func (s *guiArchiveSuite) TestGUIArchivePostSuccess(c *gc.C) { + // Create a GUI archive to be uploaded. + vers := "2.0.42" + r, hash, size := makeGUIArchive(c, vers, nil) + + // Prepare and send the request to upload a new GUI archive. + v := url.Values{} + v.Set("version", vers) + v.Set("hash", hash) + resp := s.authRequest(c, httpRequestParams{ + method: "POST", + url: s.guiURL(c) + "?" + v.Encode(), + contentType: apiserver.BZMimeType, + body: r, + }) + + // Check that the response reflects a successful upload. + body := assertResponse(c, resp, http.StatusOK, params.ContentTypeJSON) + var jsonResponse params.GUIArchiveVersion + err := json.Unmarshal(body, &jsonResponse) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResponse, jc.DeepEquals, params.GUIArchiveVersion{ + Version: version.MustParse(vers), + SHA256: hash, + Current: false, + }) + + // Check that the new archive is actually present in the GUI storage. + storage, err := s.State.GUIStorage() + c.Assert(err, jc.ErrorIsNil) + defer storage.Close() + allMeta, err := storage.AllMetadata() + c.Assert(err, jc.ErrorIsNil) + c.Assert(allMeta, gc.HasLen, 1) + c.Assert(allMeta[0].SHA256, gc.Equals, hash) + c.Assert(allMeta[0].Size, gc.Equals, size) +} + +func (s *guiArchiveSuite) TestGUIArchivePostCurrent(c *gc.C) { + // Add an existing GUI archive and set it as the current one. + storage, err := s.State.GUIStorage() + c.Assert(err, jc.ErrorIsNil) + defer storage.Close() + vers := version.MustParse("2.0.47") + setupGUIArchive(c, storage, vers.String(), nil) + err = s.State.GUISetVersion(vers) + c.Assert(err, jc.ErrorIsNil) + + // Create a GUI archive to be uploaded. + r, hash, _ := makeGUIArchive(c, vers.String(), map[string]string{"filename": "content"}) + + // Prepare and send the request to upload a new GUI archive. + v := url.Values{} + v.Set("version", vers.String()) + v.Set("hash", hash) + resp := s.authRequest(c, httpRequestParams{ + method: "POST", + url: s.guiURL(c) + "?" + v.Encode(), + contentType: apiserver.BZMimeType, + body: r, + }) + + // Check that the response reflects a successful upload. + body := assertResponse(c, resp, http.StatusOK, params.ContentTypeJSON) + var jsonResponse params.GUIArchiveVersion + err = json.Unmarshal(body, &jsonResponse) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResponse, jc.DeepEquals, params.GUIArchiveVersion{ + Version: vers, + SHA256: hash, + Current: true, + }) +} + +type guiVersionSuite struct { + authHttpSuite +} + +var _ = gc.Suite(&guiVersionSuite{}) + +// guiURL returns the URL used to select the Juju GUI archive version. +func (s *guiVersionSuite) guiURL(c *gc.C) string { + u := s.baseURL(c) + u.Path = "/gui-version" + return u.String() +} + +func (s *guiVersionSuite) TestGUIVersionMethodNotAllowed(c *gc.C) { + resp := s.authRequest(c, httpRequestParams{ + method: "GET", + url: s.guiURL(c), + }) + body := assertResponse(c, resp, http.StatusMethodNotAllowed, params.ContentTypeJSON) + var jsonResp params.ErrorResult + err := json.Unmarshal(body, &jsonResp) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResp.Error.Message, gc.Matches, `unsupported method: "GET"`) +} + +var guiVersionPutTests = []struct { + about string + contentType string + body interface{} + expectedStatus int + expectedVersion string + expectedError string +}{{ + about: "no content type", + expectedStatus: http.StatusBadRequest, + expectedError: fmt.Sprintf(`invalid content type "": expected %q`, params.ContentTypeJSON), +}, { + about: "invalid content type", + contentType: "text/html", + expectedStatus: http.StatusBadRequest, + expectedError: fmt.Sprintf(`invalid content type "text/html": expected %q`, params.ContentTypeJSON), +}, { + about: "invalid body", + contentType: params.ContentTypeJSON, + body: "bad wolf", + expectedStatus: http.StatusBadRequest, + expectedError: "invalid request body: json: .*", +}, { + about: "non existing version", + contentType: params.ContentTypeJSON, + body: params.GUIVersionRequest{ + Version: version.MustParse("2.0.1"), + }, + expectedStatus: http.StatusNotFound, + expectedError: `cannot find "2.0.1" GUI version in the storage: 2.0.1 binary metadata not found`, +}, { + about: "success: switch to new version", + contentType: params.ContentTypeJSON, + body: params.GUIVersionRequest{ + Version: version.MustParse("2.47.0"), + }, + expectedStatus: http.StatusOK, + expectedVersion: "2.47.0", +}, { + about: "success: same version", + contentType: params.ContentTypeJSON, + body: params.GUIVersionRequest{ + Version: version.MustParse("2.42.0"), + }, + expectedStatus: http.StatusOK, + expectedVersion: "2.42.0", +}} + +func (s *guiVersionSuite) TestGUIVersionPut(c *gc.C) { + // Prepare the initial Juju state. + storage, err := s.State.GUIStorage() + c.Assert(err, jc.ErrorIsNil) + defer storage.Close() + setupGUIArchive(c, storage, "2.42.0", nil) + setupGUIArchive(c, storage, "2.47.0", nil) + err = s.State.GUISetVersion(version.MustParse("2.42.0")) + c.Assert(err, jc.ErrorIsNil) + + for i, test := range guiVersionPutTests { + c.Logf("\n%d: %s", i, test.about) + + // Prepare the request. + content, err := json.Marshal(test.body) + c.Assert(err, jc.ErrorIsNil) + + // Send the request and retrieve the response. + resp := s.authRequest(c, httpRequestParams{ + method: "PUT", + url: s.guiURL(c), + contentType: test.contentType, + body: bytes.NewReader(content), + }) + var body []byte + if test.expectedError != "" { + body = assertResponse(c, resp, test.expectedStatus, params.ContentTypeJSON) + var jsonResp params.ErrorResult + err := json.Unmarshal(body, &jsonResp) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResp.Error.Message, gc.Matches, test.expectedError) + } else { + body = assertResponse(c, resp, test.expectedStatus, "text/plain; charset=utf-8") + c.Assert(body, gc.HasLen, 0) + vers, err := s.State.GUIVersion() + c.Assert(err, jc.ErrorIsNil) + c.Assert(vers.String(), gc.Equals, test.expectedVersion) + } + } +} + +func (s *guiVersionSuite) TestGUIVersionPutErrorUnauthorized(c *gc.C) { + resp := s.sendRequest(c, httpRequestParams{ + method: "PUT", + url: s.guiURL(c), + contentType: params.ContentTypeJSON, + }) + body := assertResponse(c, resp, http.StatusUnauthorized, params.ContentTypeJSON) + var jsonResp params.ErrorResult + err := json.Unmarshal(body, &jsonResp) + c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body)) + c.Assert(jsonResp.Error.Message, gc.Matches, "cannot open state: no credentials provided") +} + // makeGUIArchive creates a Juju GUI tar.bz2 archive with the given files. // The files parameter maps file names (relative to the internal "jujugui" // directory) to their contents. This function returns a reader for the diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/httpcontext.go charm-2.2.0/src/github.com/juju/juju/apiserver/httpcontext.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/httpcontext.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/httpcontext.go 2016-04-28 06:03:34.000000000 +0000 @@ -111,7 +111,7 @@ authHeader := r.Header.Get("Authorization") if authHeader == "" { // No authorization header implies an attempt - // to login with macaroon authentication. + // to login with external user macaroon authentication. return params.LoginRequest{ Macaroons: httpbakery.RequestMacaroons(r), }, nil @@ -139,6 +139,7 @@ return params.LoginRequest{ AuthTag: tagPass[0], Credentials: tagPass[1], + Macaroons: httpbakery.RequestMacaroons(r), Nonce: r.Header.Get(params.MachineNonceHeader), }, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/imagemetadata/functions_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/imagemetadata/functions_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/imagemetadata/functions_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/imagemetadata/functions_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -13,7 +13,6 @@ "github.com/juju/juju/apiserver/params" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" - "github.com/juju/juju/environs/configstore" envtesting "github.com/juju/juju/environs/testing" "github.com/juju/juju/jujuclient/jujuclienttesting" "github.com/juju/juju/state/cloudimagemetadata" @@ -32,15 +31,18 @@ func (s *funcSuite) SetUpTest(c *gc.C) { s.baseImageMetadataSuite.SetUpTest(c) - cfg, err := config.New(config.NoDefaults, mockConfig()) - c.Assert(err, jc.ErrorIsNil) + var err error s.env, err = environs.Prepare( - envtesting.BootstrapContext(c), configstore.NewMem(), + envtesting.BootstrapContext(c), jujuclienttesting.NewMemStore(), - "dummycontroller", environs.PrepareForBootstrapParams{Config: cfg}, + environs.PrepareParams{ + ControllerName: "dummycontroller", + BaseConfig: mockConfig(), + CloudName: "dummy", + }, ) c.Assert(err, jc.ErrorIsNil) - s.state = s.constructState(cfg) + s.state = s.constructState(s.env.Config()) s.expected = cloudimagemetadata.Metadata{ cloudimagemetadata.MetadataAttributes{ diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/imagemetadata/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/imagemetadata/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/imagemetadata/package_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/imagemetadata/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -16,7 +16,6 @@ "github.com/juju/juju/apiserver/testing" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" - "github.com/juju/juju/environs/configstore" imagetesting "github.com/juju/juju/environs/imagemetadata/testing" envtesting "github.com/juju/juju/environs/testing" "github.com/juju/juju/jujuclient/jujuclienttesting" @@ -122,15 +121,16 @@ attrs := coretesting.FakeConfig().Merge(coretesting.Attrs{ "type": "mock", "controller": true, - "state-id": "1", }) - cfg, err := config.New(config.NoDefaults, attrs) - c.Assert(err, jc.ErrorIsNil) - _, err = environs.Prepare( - envtesting.BootstrapContext(c), configstore.NewMem(), + env, err := environs.Prepare( + envtesting.BootstrapContext(c), jujuclienttesting.NewMemStore(), - "dummycontroller", environs.PrepareForBootstrapParams{Config: cfg}, + environs.PrepareParams{ + ControllerName: "dummycontroller", + BaseConfig: attrs, + CloudName: "dummy", + }, ) c.Assert(err, jc.ErrorIsNil) - return cfg + return env.Config() } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/imagemetadata/updatefrompublished_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/imagemetadata/updatefrompublished_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/imagemetadata/updatefrompublished_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/imagemetadata/updatefrompublished_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -15,7 +15,6 @@ "github.com/juju/juju/cmd/modelcmd" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" - "github.com/juju/juju/environs/configstore" "github.com/juju/juju/environs/imagemetadata" imagetesting "github.com/juju/juju/environs/imagemetadata/testing" "github.com/juju/juju/environs/simplestreams" @@ -164,12 +163,14 @@ // testingEnvConfig prepares an environment configuration using // the dummy provider since it doesn't implement simplestreams.HasRegion. s.state.environConfig = func() (*config.Config, error) { - cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) - c.Assert(err, jc.ErrorIsNil) env, err := environs.Prepare( - modelcmd.BootstrapContext(testing.Context(c)), configstore.NewMem(), + modelcmd.BootstrapContext(testing.Context(c)), jujuclienttesting.NewMemStore(), - "dummycontroller", environs.PrepareForBootstrapParams{Config: cfg}, + environs.PrepareParams{ + ControllerName: "dummycontroller", + BaseConfig: dummy.SampleConfig(), + CloudName: "dummy", + }, ) c.Assert(err, jc.ErrorIsNil) return env.Config(), err @@ -221,7 +222,11 @@ environs.EnvironProvider } -func (p mockEnvironProvider) PrepareForBootstrap(environs.BootstrapContext, environs.PrepareForBootstrapParams) (environs.Environ, error) { +func (p mockEnvironProvider) BootstrapConfig(args environs.BootstrapConfigParams) (*config.Config, error) { + return args.Config, nil +} + +func (p mockEnvironProvider) PrepareForBootstrap(environs.BootstrapContext, *config.Config) (environs.Environ, error) { return &mockEnviron{}, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/facade.go charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/facade.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/facade.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/facade.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,39 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag + +import ( + "github.com/juju/names" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/state" +) + +type Backend interface { + ModelUUID() string + state.EntityFinder +} + +func NewFacade(backend Backend, resources *common.Resources, authorizer common.Authorizer) (*Facade, error) { + if !authorizer.AuthModelManager() { + return nil, common.ErrPerm + } + expect := names.NewModelTag(backend.ModelUUID()) + getCanAccess := func() (common.AuthFunc, error) { + return func(tag names.Tag) bool { + return tag == expect + }, nil + } + life := common.NewLifeGetter(backend, getCanAccess) + watch := common.NewAgentEntityWatcher(backend, resources, getCanAccess) + return &Facade{ + LifeGetter: life, + AgentEntityWatcher: watch, + }, nil +} + +type Facade struct { + *common.LifeGetter + *common.AgentEntityWatcher +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/facade_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/facade_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/facade_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/facade_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,148 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag_test + +import ( + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/lifeflag" + "github.com/juju/juju/apiserver/params" +) + +type FacadeSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&FacadeSuite{}) + +func (*FacadeSuite) TestFacadeAuthFailure(c *gc.C) { + facade, err := lifeflag.NewFacade(nil, nil, auth(false)) + c.Check(facade, gc.IsNil) + c.Check(err, gc.Equals, common.ErrPerm) +} + +func (*FacadeSuite) TestLifeBadEntity(c *gc.C) { + backend := &mockBackend{} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Assert(err, jc.ErrorIsNil) + + results, err := facade.Life(entities("archibald snookums")) + c.Check(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + result := results.Results[0] + c.Check(result.Life, gc.Equals, params.Life("")) + + // TODO(fwereade): this is DUMB. should just be a parse error. + // but I'm not fixing the underlying implementation as well. + c.Check(result.Error, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (*FacadeSuite) TestLifeAuthFailure(c *gc.C) { + backend := &mockBackend{} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Assert(err, jc.ErrorIsNil) + + results, err := facade.Life(entities("unit-foo-1")) + c.Check(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + result := results.Results[0] + c.Check(result.Life, gc.Equals, params.Life("")) + c.Check(result.Error, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (*FacadeSuite) TestLifeNotFound(c *gc.C) { + backend := &mockBackend{} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Assert(err, jc.ErrorIsNil) + + results, err := facade.Life(modelEntity()) + c.Check(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + result := results.Results[0] + c.Check(result.Life, gc.Equals, params.Life("")) + c.Check(result.Error, jc.Satisfies, params.IsCodeNotFound) +} + +func (*FacadeSuite) TestLifeSuccess(c *gc.C) { + backend := &mockBackend{exist: true} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Check(err, jc.ErrorIsNil) + + results, err := facade.Life(modelEntity()) + c.Check(err, jc.ErrorIsNil) + c.Check(results, jc.DeepEquals, params.LifeResults{ + Results: []params.LifeResult{{Life: params.Dying}}, + }) +} + +func (*FacadeSuite) TestWatchBadEntity(c *gc.C) { + backend := &mockBackend{} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Assert(err, jc.ErrorIsNil) + + results, err := facade.Watch(entities("archibald snookums")) + c.Check(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + result := results.Results[0] + c.Check(result.NotifyWatcherId, gc.Equals, "") + + // TODO(fwereade): this is DUMB. should just be a parse error. + // but I'm not fixing the underlying implementation as well. + c.Check(result.Error, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (*FacadeSuite) TestWatchAuthFailure(c *gc.C) { + backend := &mockBackend{} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Assert(err, jc.ErrorIsNil) + + results, err := facade.Watch(entities("unit-foo-1")) + c.Check(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + result := results.Results[0] + c.Check(result.NotifyWatcherId, gc.Equals, "") + c.Check(result.Error, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (*FacadeSuite) TestWatchNotFound(c *gc.C) { + backend := &mockBackend{} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Assert(err, jc.ErrorIsNil) + + results, err := facade.Watch(modelEntity()) + c.Check(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + result := results.Results[0] + c.Check(result.NotifyWatcherId, gc.Equals, "") + c.Check(result.Error, jc.Satisfies, params.IsCodeNotFound) +} + +func (*FacadeSuite) TestWatchBadWatcher(c *gc.C) { + backend := &mockBackend{exist: true} + facade, err := lifeflag.NewFacade(backend, nil, auth(true)) + c.Check(err, jc.ErrorIsNil) + + results, err := facade.Watch(modelEntity()) + c.Check(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + result := results.Results[0] + c.Check(result.NotifyWatcherId, gc.Equals, "") + c.Check(result.Error, gc.ErrorMatches, "blammo") +} + +func (*FacadeSuite) TestWatchSuccess(c *gc.C) { + backend := &mockBackend{exist: true, watch: true} + resources := common.NewResources() + facade, err := lifeflag.NewFacade(backend, resources, auth(true)) + c.Check(err, jc.ErrorIsNil) + + results, err := facade.Watch(modelEntity()) + c.Check(err, jc.ErrorIsNil) + c.Check(results, jc.DeepEquals, params.NotifyWatchResults{ + Results: []params.NotifyWatchResult{{NotifyWatcherId: "1"}}, + }) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/shim.go charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/shim.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/shim.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/shim.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,18 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag + +import ( + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/state" +) + +func init() { + common.RegisterStandardFacade( + "LifeFlag", 1, + func(st *state.State, resources *common.Resources, authorizer common.Authorizer) (*Facade, error) { + return NewFacade(st, resources, authorizer) + }, + ) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/util_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/util_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/lifeflag/util_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/lifeflag/util_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,101 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package lifeflag_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" + coretesting "github.com/juju/juju/testing" +) + +// mockAuth implements common.Authorizer for the tests' convenience. +type mockAuth struct { + common.Authorizer + modelManager bool +} + +func (mock mockAuth) AuthModelManager() bool { + return mock.modelManager +} + +// auth is a convenience constructor for a mockAuth. +func auth(modelManager bool) common.Authorizer { + return mockAuth{modelManager: modelManager} +} + +// mockBackend implements lifeflag.Backend for the tests' convenience. +type mockBackend struct { + exist bool + watch bool +} + +func (mock *mockBackend) ModelUUID() string { + return coretesting.ModelTag.Id() +} + +func (mock *mockBackend) FindEntity(tag names.Tag) (state.Entity, error) { + if tag != coretesting.ModelTag { + panic("should never happen -- bad auth somewhere") + } + if !mock.exist { + return nil, errors.NotFoundf("model") + } + return &mockEntity{ + watch: mock.watch, + }, nil +} + +// mockEntity implements state.Entity for the tests' convenience. +type mockEntity struct { + watch bool +} + +func (mock *mockEntity) Tag() names.Tag { + return coretesting.ModelTag +} + +func (mock *mockEntity) Life() state.Life { + return state.Dying +} + +func (mock *mockEntity) Watch() state.NotifyWatcher { + changes := make(chan struct{}, 1) + if mock.watch { + changes <- struct{}{} + } else { + close(changes) + } + return &mockWatcher{changes: changes} +} + +// mockWatcher implements state.NotifyWatcher for the tests' convenience. +type mockWatcher struct { + state.NotifyWatcher + changes chan struct{} +} + +func (mock *mockWatcher) Changes() <-chan struct{} { + return mock.changes +} + +func (mock *mockWatcher) Err() error { + return errors.New("blammo") +} + +// entities is a convenience constructor for params.Entities. +func entities(tags ...string) params.Entities { + entities := params.Entities{Entities: make([]params.Entity, len(tags))} + for i, tag := range tags { + entities.Entities[i].Tag = tag + } + return entities +} + +func modelEntity() params.Entities { + return entities(coretesting.ModelTag.String()) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/machine/machiner.go charm-2.2.0/src/github.com/juju/juju/apiserver/machine/machiner.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/machine/machiner.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/machine/machiner.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,14 +7,18 @@ import ( "github.com/juju/errors" + "github.com/juju/loggo" "github.com/juju/names" "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/common/networkingcommon" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/state" "github.com/juju/juju/state/multiwatcher" ) +var logger = loggo.GetLogger("juju.apiserver.machine") + func init() { common.RegisterStandardFacade("Machiner", 1, NewMachinerAPI) } @@ -132,3 +136,145 @@ } return result, nil } + +func (api *MachinerAPI) SetObservedNetworkConfig(args params.SetMachineNetworkConfig) error { + m, err := api.getMachineForSettingNetworkConfig(args.Tag) + if err != nil { + return errors.Trace(err) + } + if m.IsContainer() { + return nil + } + observedConfig := args.Config + logger.Tracef("observed network config of machine %q: %+v", m.Id(), observedConfig) + if len(observedConfig) == 0 { + logger.Infof("not updating machine network config: no observed network config found") + return nil + } + + providerConfig, err := api.getOneMachineProviderNetworkConfig(m) + if errors.IsNotProvisioned(err) { + logger.Infof("not updating provider network config: %v", err) + return nil + } else if err != nil { + return errors.Trace(err) + } + if len(providerConfig) == 0 { + logger.Infof("not updating machine network config: no provider network config found") + return nil + } + + mergedConfig := networkingcommon.MergeProviderAndObservedNetworkConfigs(providerConfig, observedConfig) + logger.Tracef("merged observed and provider network config: %+v", mergedConfig) + + return api.setOneMachineNetworkConfig(m, mergedConfig) +} + +func (api *MachinerAPI) getMachineForSettingNetworkConfig(machineTag string) (*state.Machine, error) { + canModify, err := api.getCanModify() + if err != nil { + return nil, errors.Trace(err) + } + + tag, err := names.ParseMachineTag(machineTag) + if err != nil { + return nil, errors.Trace(err) + } + if !canModify(tag) { + return nil, errors.Trace(common.ErrPerm) + } + + m, err := api.getMachine(tag) + if errors.IsNotFound(err) { + return nil, errors.Trace(common.ErrPerm) + } else if err != nil { + return nil, errors.Trace(err) + } + + if m.IsContainer() { + logger.Warningf("not updating network config for container %q", m.Id()) + } + + return m, nil +} + +func (api *MachinerAPI) setOneMachineNetworkConfig(m *state.Machine, networkConfig []params.NetworkConfig) error { + devicesArgs, devicesAddrs := networkingcommon.NetworkConfigsToStateArgs(networkConfig) + + logger.Debugf("setting devices: %+v", devicesArgs) + if err := m.SetParentLinkLayerDevicesBeforeTheirChildren(devicesArgs); err != nil { + return errors.Trace(err) + } + + logger.Debugf("setting addresses: %+v", devicesAddrs) + if err := m.SetDevicesAddressesIdempotently(devicesAddrs); err != nil { + return errors.Trace(err) + } + + logger.Debugf("updated machine %q network config", m.Id()) + return nil +} + +func (api *MachinerAPI) SetProviderNetworkConfig(args params.Entities) (params.ErrorResults, error) { + result := params.ErrorResults{ + Results: make([]params.ErrorResult, len(args.Entities)), + } + + for i, arg := range args.Entities { + m, err := api.getMachineForSettingNetworkConfig(arg.Tag) + if err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } + + if m.IsContainer() { + continue + } + + providerConfig, err := api.getOneMachineProviderNetworkConfig(m) + if err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } else if len(providerConfig) == 0 { + continue + } + + sortedProviderConfig := networkingcommon.SortNetworkConfigsByParents(providerConfig) + logger.Tracef("sorted provider network config for %q: %+v", m.Id(), sortedProviderConfig) + + if err := api.setOneMachineNetworkConfig(m, sortedProviderConfig); err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } + } + return result, nil +} + +func (api *MachinerAPI) getOneMachineProviderNetworkConfig(m *state.Machine) ([]params.NetworkConfig, error) { + instId, err := m.InstanceId() + if err != nil { + return nil, errors.Trace(err) + } + + netEnviron, err := networkingcommon.NetworkingEnvironFromModelConfig(api.st) + if errors.IsNotSupported(err) { + logger.Infof("not updating provider network config: %v", err) + return nil, nil + } else if err != nil { + return nil, errors.Annotate(err, "cannot get provider network config") + } + + interfaceInfos, err := netEnviron.NetworkInterfaces(instId) + if err != nil { + return nil, errors.Annotatef(err, "cannot get network interfaces of %q", instId) + } + if len(interfaceInfos) == 0 { + logger.Infof("not updating provider network config: no interfaces returned") + return nil, nil + } + + providerConfig := networkingcommon.NetworkConfigFromInterfaceInfo(interfaceInfos) + logger.Tracef("provider network config instance %q: %+v", instId, providerConfig) + + return providerConfig, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/machine/machiner_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/machine/machiner_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/machine/machiner_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/machine/machiner_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -263,3 +263,110 @@ wc := statetesting.NewNotifyWatcherC(c, s.State, resource.(state.NotifyWatcher)) wc.AssertNoChange() } + +func (s *machinerSuite) TestSetObservedNetworkConfig(c *gc.C) { + c.Skip("dimitern: Test disabled until dummy provider is fixed properly") + devices, err := s.machine1.AllLinkLayerDevices() + c.Assert(err, jc.ErrorIsNil) + c.Assert(devices, gc.HasLen, 0) + + err = s.machine1.SetInstanceInfo("i-foo", "FAKE_NONCE", nil, nil, nil, nil, nil) + c.Assert(err, jc.ErrorIsNil) + + observedConfig := []params.NetworkConfig{{ + InterfaceName: "lo", + InterfaceType: "loopback", + CIDR: "127.0.0.0/8", + Address: "127.0.0.1", + }, { + InterfaceName: "eth0", + InterfaceType: "ethernet", + MACAddress: "aa:bb:cc:dd:ee:f0", + CIDR: "0.10.0.0/24", + Address: "0.10.0.2", + }, { + InterfaceName: "eth1", + InterfaceType: "ethernet", + MACAddress: "aa:bb:cc:dd:ee:f1", + CIDR: "0.20.0.0/24", + Address: "0.20.0.2", + }} + args := params.SetMachineNetworkConfig{ + Tag: s.machine1.Tag().String(), + Config: observedConfig, + } + + err = s.machiner.SetObservedNetworkConfig(args) + c.Assert(err, jc.ErrorIsNil) + + devices, err = s.machine1.AllLinkLayerDevices() + c.Assert(err, jc.ErrorIsNil) + c.Assert(devices, gc.HasLen, 3) + + for _, device := range devices { + c.Check(device.Name(), gc.Matches, `(lo|eth0|eth1)`) + c.Check(string(device.Type()), gc.Matches, `(loopback|ethernet)`) + c.Check(device.MACAddress(), gc.Matches, `(|aa:bb:cc:dd:ee:f0|aa:bb:cc:dd:ee:f1)`) + } +} + +func (s *machinerSuite) TestSetObservedNetworkConfigPermissions(c *gc.C) { + args := params.SetMachineNetworkConfig{ + Tag: "machine-0", + Config: nil, + } + + err := s.machiner.SetObservedNetworkConfig(args) + c.Assert(err, gc.ErrorMatches, "permission denied") +} + +func (s *machinerSuite) TestSetProviderNetworkConfig(c *gc.C) { + c.Skip("dimitern: Test disabled until dummy provider is fixed properly") + devices, err := s.machine1.AllLinkLayerDevices() + c.Assert(err, jc.ErrorIsNil) + c.Assert(devices, gc.HasLen, 0) + + err = s.machine1.SetInstanceInfo("i-foo", "FAKE_NONCE", nil, nil, nil, nil, nil) + c.Assert(err, jc.ErrorIsNil) + + args := params.Entities{Entities: []params.Entity{ + {Tag: s.machine1.Tag().String()}, + }} + + result, err := s.machiner.SetProviderNetworkConfig(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, gc.DeepEquals, params.ErrorResults{ + Results: []params.ErrorResult{{nil}}, + }) + + devices, err = s.machine1.AllLinkLayerDevices() + c.Assert(err, jc.ErrorIsNil) + c.Assert(devices, gc.HasLen, 3) + + for _, device := range devices { + c.Check(device.Name(), gc.Matches, `eth[0-2]`) + c.Check(string(device.Type()), gc.Equals, "ethernet") + c.Check(device.MACAddress(), gc.Matches, `aa:bb:cc:dd:ee:f[0-2]`) + addrs, err := device.Addresses() + c.Check(err, jc.ErrorIsNil) + c.Check(addrs, gc.HasLen, 1) + } +} + +func (s *machinerSuite) TestSetProviderNetworkConfigPermissions(c *gc.C) { + args := params.Entities{Entities: []params.Entity{ + {Tag: "machine-1"}, + {Tag: "machine-0"}, + {Tag: "machine-42"}, + }} + + result, err := s.machiner.SetProviderNetworkConfig(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, gc.DeepEquals, params.ErrorResults{ + Results: []params.ErrorResult{ + {Error: apiservertesting.NotProvisionedError(s.machine1.Id())}, + {Error: apiservertesting.ErrUnauthorized}, + {Error: apiservertesting.ErrUnauthorized}, + }, + }) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/machineactions.go charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/machineactions.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/machineactions.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/machineactions.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,108 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +// machineactions implements the the apiserver side of +// running actions on machines +package machineactions + +import ( + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" + "github.com/juju/names" +) + +type Backend interface { + ActionByTag(tag names.ActionTag) (state.Action, error) + FindEntity(tag names.Tag) (state.Entity, error) + TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(string) (state.ActionReceiver, error) + ConvertActions(ar state.ActionReceiver, fn common.GetActionsFn) ([]params.ActionResult, error) +} + +// Facade implements the machineactions interface and is the concrete +// implementation of the api end point. +type Facade struct { + backend Backend + resources *common.Resources + accessMachine common.AuthFunc +} + +// NewFacade creates a new server-side machineactions API end point. +func NewFacade( + backend Backend, + resources *common.Resources, + authorizer common.Authorizer, +) (*Facade, error) { + if !authorizer.AuthMachineAgent() { + return nil, common.ErrPerm + } + return &Facade{ + backend: backend, + resources: resources, + accessMachine: authorizer.AuthOwner, + }, nil +} + +// Actions returns the Actions by Tags passed and ensures that the machine asking +// for them is the machine that has the actions +func (f *Facade) Actions(args params.Entities) params.ActionResults { + actionFn := common.AuthAndActionFromTagFn(f.accessMachine, f.backend.ActionByTag) + return common.Actions(args, actionFn) +} + +// BeginActions marks the actions represented by the passed in Tags as running. +func (f *Facade) BeginActions(args params.Entities) params.ErrorResults { + actionFn := common.AuthAndActionFromTagFn(f.accessMachine, f.backend.ActionByTag) + return common.BeginActions(args, actionFn) +} + +// FinishActions saves the result of a completed Action +func (f *Facade) FinishActions(args params.ActionExecutionResults) params.ErrorResults { + actionFn := common.AuthAndActionFromTagFn(f.accessMachine, f.backend.ActionByTag) + return common.FinishActions(args, actionFn) +} + +// WatchActionNotifications returns a StringsWatcher for observing +// incoming action calls to a machine. +func (f *Facade) WatchActionNotifications(args params.Entities) params.StringsWatchResults { + tagToActionReceiver := f.backend.TagToActionReceiverFn(f.backend.FindEntity) + watchOne := common.WatchOneActionReceiverNotifications(tagToActionReceiver, f.resources.Register) + return common.WatchActionNotifications(args, f.accessMachine, watchOne) +} + +// RunningActions lists the actions running for the entities passed in. +// If we end up needing more than ListRunning at some point we could follow/abstract +// what's done in the client actions package. +func (f *Facade) RunningActions(args params.Entities) params.ActionsByReceivers { + canAccess := f.accessMachine + tagToActionReceiver := f.backend.TagToActionReceiverFn(f.backend.FindEntity) + + response := params.ActionsByReceivers{ + Actions: make([]params.ActionsByReceiver, len(args.Entities)), + } + + for i, entity := range args.Entities { + currentResult := &response.Actions[i] + receiver, err := tagToActionReceiver(entity.Tag) + if err != nil { + currentResult.Error = common.ServerError(common.ErrBadId) + continue + } + currentResult.Receiver = receiver.Tag().String() + + if !canAccess(receiver.Tag()) { + currentResult.Error = common.ServerError(common.ErrPerm) + continue + } + + results, err := f.backend.ConvertActions(receiver, receiver.RunningActions) + if err != nil { + currentResult.Error = common.ServerError(err) + continue + } + currentResult.Actions = results + } + + return response +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/machineactions_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/machineactions_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/machineactions_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/machineactions_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,158 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package machineactions_test + +import ( + "errors" + + "github.com/juju/names" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/machineactions" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" +) + +type FacadeSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&FacadeSuite{}) + +func (*FacadeSuite) TestAcceptsMachineAgent(c *gc.C) { + facade, err := machineactions.NewFacade(nil, nil, agentAuth{machine: true}) + c.Check(err, jc.ErrorIsNil) + c.Check(facade, gc.NotNil) +} + +func (*FacadeSuite) TestOtherAgent(c *gc.C) { + facade, err := machineactions.NewFacade(nil, nil, agentAuth{}) + c.Check(err, gc.Equals, common.ErrPerm) + c.Check(facade, gc.IsNil) +} + +func (*FacadeSuite) TestRunningActions(c *gc.C) { + stub := &testing.Stub{} + auth := agentAuth{ + machine: true, + } + backend := &mockBackend{ + stub: stub, + } + + facade, err := machineactions.NewFacade(backend, nil, auth) + c.Assert(err, jc.ErrorIsNil) + + stub.SetErrors(errors.New("boom")) + results := facade.RunningActions(entities( + "valid", // we will cause this one to err out + "valid", + "invalid", + "unauthorized", + )) + + c.Assert(results, gc.DeepEquals, params.ActionsByReceivers{ + Actions: []params.ActionsByReceiver{{ + Receiver: "valid", + Error: common.ServerError(errors.New("boom")), + }, { + Receiver: "valid", + Actions: actions, + }, { + Error: common.ServerError(common.ErrBadId), + }, { + Receiver: "unauthorized", + Error: common.ServerError(common.ErrPerm), + }}, + }) + stub.CheckCallNames(c, "TagToActionReceiverFn", "ConvertActions", "ConvertActions") +} + +// entities is a convenience constructor for params.Entities. +func entities(tags ...string) params.Entities { + entities := params.Entities{ + Entities: make([]params.Entity, len(tags)), + } + for i, tag := range tags { + entities.Entities[i].Tag = tag + } + return entities +} + +// agentAuth implements common.Authorizer for use in the tests. +type agentAuth struct { + common.Authorizer + machine bool +} + +// AuthMachineAgent is part of the common.Authorizer interface. +func (auth agentAuth) AuthMachineAgent() bool { + return auth.machine +} + +func (auth agentAuth) AuthOwner(tag names.Tag) bool { + if tag.String() == "valid" { + return true + } + return false +} + +// mockBackend implements machineactions.Backend for use in the tests. +type mockBackend struct { + machineactions.Backend + stub *testing.Stub +} + +func (mock *mockBackend) TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(string) (state.ActionReceiver, error) { + mock.stub.AddCall("TagToActionReceiverFn", findEntity) + return tagToActionReceiver +} + +func tagToActionReceiver(tag string) (state.ActionReceiver, error) { + switch tag { + case "valid": + return validReceiver, nil + case "unauthorized": + return unauthorizedReceiver, nil + default: + return nil, errors.New("invalid actionReceiver tag") + } +} + +var validReceiver = fakeActionReceiver{tag: validTag} +var unauthorizedReceiver = fakeActionReceiver{tag: unauthorizedTag} +var validTag = fakeTag{s: "valid"} +var unauthorizedTag = fakeTag{s: "unauthorized"} + +type fakeActionReceiver struct { + state.ActionReceiver + tag fakeTag +} + +func (mock fakeActionReceiver) Tag() names.Tag { + return mock.tag +} + +type fakeTag struct { + names.Tag + s string +} + +func (mock fakeTag) String() string { + return mock.s +} + +func (mock *mockBackend) ConvertActions(ar state.ActionReceiver, fn common.GetActionsFn) ([]params.ActionResult, error) { + mock.stub.AddCall("ConvertActions", ar, fn) + if err := mock.stub.NextErr(); err != nil { + return nil, err + } + return actions, nil +} + +var actions = []params.ActionResult{params.ActionResult{Action: ¶ms.Action{Name: "foo"}}} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,15 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package machineactions_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/shim.go charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/shim.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/machineactions/shim.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/machineactions/shim.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,40 @@ +// Copyright 2016 Canonical Ltd. +// Copyright 2016 Cloudbase Solutions +// Licensed under the AGPLv3, see LICENCE file for details. + +package machineactions + +import ( + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" + "github.com/juju/names" +) + +func init() { + common.RegisterStandardFacade("MachineActions", 1, newFacade) +} + +func newFacade(st *state.State, res *common.Resources, auth common.Authorizer) (*Facade, error) { + return NewFacade(backendShim{st}, res, auth) +} + +type backendShim struct { + st *state.State +} + +func (shim backendShim) ActionByTag(tag names.ActionTag) (state.Action, error) { + return shim.st.ActionByTag(tag) +} + +func (shim backendShim) FindEntity(tag names.Tag) (state.Entity, error) { + return shim.st.FindEntity(tag) +} + +func (shim backendShim) TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(string) (state.ActionReceiver, error) { + return common.TagToActionReceiverFn(findEntity) +} + +func (shim backendShim) ConvertActions(ar state.ActionReceiver, fn common.GetActionsFn) ([]params.ActionResult, error) { + return common.ConvertActions(ar, fn) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/metricsender/metricsender.go charm-2.2.0/src/github.com/juju/juju/apiserver/metricsender/metricsender.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/metricsender/metricsender.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/metricsender/metricsender.go 2016-04-28 06:03:34.000000000 +0000 @@ -93,6 +93,7 @@ if response != nil { // TODO (mattyw) We are currently ignoring errors during response handling. handleResponse(metricsManager, st, *response) + // TODO(fwereade): 2016-03-17 lp:1558657 if err := metricsManager.SetLastSuccessfulSend(time.Now()); err != nil { err = errors.Annotate(err, "failed to set successful send time") logger.Warningf("%v", err) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/metricsender/sender_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/metricsender/sender_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/metricsender/sender_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/metricsender/sender_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -32,7 +32,7 @@ var _ = gc.Suite(&SenderSuite{}) func createCerts(c *gc.C, serverName string) (*x509.CertPool, tls.Certificate) { - certCaPem, keyCaPem, err := cert.NewCA("sender-test", time.Now().Add(time.Minute)) + certCaPem, keyCaPem, err := cert.NewCA("sender-test", "1", time.Now().Add(time.Minute)) c.Assert(err, jc.ErrorIsNil) certPem, keyPem, err := cert.NewServer(certCaPem, keyCaPem, time.Now().Add(time.Minute), []string{serverName}) c.Assert(err, jc.ErrorIsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/facade.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/facade.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/facade.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/facade.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,112 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package migrationflag + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/state" + "github.com/juju/juju/state/watcher" +) + +// Backend exposes information about any current model migrations. +type Backend interface { + ModelUUID() string + MigrationPhase() (migration.Phase, error) + WatchMigrationPhase() (state.NotifyWatcher, error) +} + +// Facade lets clients watch and get models' migration phases. +type Facade struct { + backend Backend + resources *common.Resources +} + +// New creates a Facade backed by backend and resources. If auth +// doesn't identity the client as a machine agent or a unit agent, +// it will return common.ErrPerm. +func New(backend Backend, resources *common.Resources, auth common.Authorizer) (*Facade, error) { + if !auth.AuthMachineAgent() && !auth.AuthUnitAgent() { + return nil, common.ErrPerm + } + return &Facade{ + backend: backend, + resources: resources, + }, nil +} + +// auth is very simplistic: it only accepts the model tag reported by +// the backend. +func (facade *Facade) auth(tagString string) error { + tag, err := names.ParseModelTag(tagString) + if err != nil { + return errors.Trace(err) + } + if tag.Id() != facade.backend.ModelUUID() { + return common.ErrPerm + } + return nil +} + +// Phase returns the current migration phase or an error for every +// supplied entity. +func (facade *Facade) Phase(entities params.Entities) params.PhaseResults { + count := len(entities.Entities) + results := params.PhaseResults{ + Results: make([]params.PhaseResult, count), + } + for i, entity := range entities.Entities { + phase, err := facade.onePhase(entity.Tag) + results.Results[i].Phase = phase + results.Results[i].Error = common.ServerError(err) + } + return results +} + +// onePhase does auth and lookup for a single entity. +func (facade *Facade) onePhase(tagString string) (string, error) { + if err := facade.auth(tagString); err != nil { + return "", errors.Trace(err) + } + phase, err := facade.backend.MigrationPhase() + if err != nil { + return "", errors.Trace(err) + } + return phase.String(), nil +} + +// Watch returns an id for use with the NotifyWatcher facade, or an +// error, for every supplied entity. +func (facade *Facade) Watch(entities params.Entities) params.NotifyWatchResults { + count := len(entities.Entities) + results := params.NotifyWatchResults{ + Results: make([]params.NotifyWatchResult, count), + } + for i, entity := range entities.Entities { + id, err := facade.oneWatch(entity.Tag) + results.Results[i].NotifyWatcherId = id + results.Results[i].Error = common.ServerError(err) + } + return results +} + +// oneWatch does auth, and watcher creation/registration, for a single +// entity. +func (facade *Facade) oneWatch(tagString string) (string, error) { + if err := facade.auth(tagString); err != nil { + return "", errors.Trace(err) + } + watch, err := facade.backend.WatchMigrationPhase() + if err != nil { + return "", errors.Trace(err) + } + if _, ok := <-watch.Changes(); ok { + return facade.resources.Register(watch), nil + } + return "", watcher.EnsureErr(watch) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/facade_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/facade_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/facade_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/facade_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,151 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package migrationflag_test + +import ( + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/migrationflag" + "github.com/juju/juju/apiserver/params" + coretesting "github.com/juju/juju/testing" +) + +type FacadeSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&FacadeSuite{}) + +func (*FacadeSuite) TestAcceptsMachineAgent(c *gc.C) { + facade, err := migrationflag.New(nil, nil, agentAuth{machine: true}) + c.Check(err, jc.ErrorIsNil) + c.Check(facade, gc.NotNil) +} + +func (*FacadeSuite) TestAcceptsUnitAgent(c *gc.C) { + facade, err := migrationflag.New(nil, nil, agentAuth{machine: true}) + c.Check(err, jc.ErrorIsNil) + c.Check(facade, gc.NotNil) +} + +func (*FacadeSuite) TestRejectsNonAgent(c *gc.C) { + facade, err := migrationflag.New(nil, nil, agentAuth{}) + c.Check(err, gc.Equals, common.ErrPerm) + c.Check(facade, gc.IsNil) +} + +func (*FacadeSuite) TestPhaseSuccess(c *gc.C) { + stub := &testing.Stub{} + backend := newMockBackend(stub) + facade, err := migrationflag.New(backend, nil, authOK) + c.Assert(err, jc.ErrorIsNil) + + results := facade.Phase(entities( + coretesting.ModelTag.String(), + coretesting.ModelTag.String(), + )) + c.Assert(results.Results, gc.HasLen, 2) + stub.CheckCallNames(c, "MigrationPhase", "MigrationPhase") + + for _, result := range results.Results { + c.Check(result.Error, gc.IsNil) + c.Check(result.Phase, gc.Equals, "REAP") + } +} + +func (*FacadeSuite) TestPhaseErrors(c *gc.C) { + stub := &testing.Stub{} + stub.SetErrors(errors.New("ouch")) + backend := newMockBackend(stub) + facade, err := migrationflag.New(backend, nil, authOK) + c.Assert(err, jc.ErrorIsNil) + + // 3 entities: unparseable, unauthorized, call error. + results := facade.Phase(entities( + "urgle", + unknownModel, + coretesting.ModelTag.String(), + )) + c.Assert(results.Results, gc.HasLen, 3) + stub.CheckCallNames(c, "MigrationPhase") + + c.Check(results.Results, jc.DeepEquals, []params.PhaseResult{{ + Error: ¶ms.Error{ + Message: `"urgle" is not a valid tag`, + }}, { + Error: ¶ms.Error{ + Message: "permission denied", + Code: "unauthorized access", + }}, { + Error: ¶ms.Error{ + Message: "ouch", + }, + }}) +} + +func (*FacadeSuite) TestWatchSuccess(c *gc.C) { + stub := &testing.Stub{} + backend := newMockBackend(stub) + resources := common.NewResources() + facade, err := migrationflag.New(backend, resources, authOK) + c.Assert(err, jc.ErrorIsNil) + + results := facade.Watch(entities( + coretesting.ModelTag.String(), + coretesting.ModelTag.String(), + )) + c.Assert(results.Results, gc.HasLen, 2) + stub.CheckCallNames(c, "WatchMigrationPhase", "WatchMigrationPhase") + + check := func(result params.NotifyWatchResult) { + c.Check(result.Error, gc.IsNil) + resource := resources.Get(result.NotifyWatcherId) + c.Check(resource, gc.NotNil) + } + first := results.Results[0] + second := results.Results[1] + check(first) + check(second) + c.Check(first.NotifyWatcherId, gc.Not(gc.Equals), second.NotifyWatcherId) +} + +func (*FacadeSuite) TestWatchErrors(c *gc.C) { + stub := &testing.Stub{} + stub.SetErrors(errors.New("blort"), nil, errors.New("squish")) + backend := newMockBackend(stub) + resources := common.NewResources() + facade, err := migrationflag.New(backend, resources, authOK) + c.Assert(err, jc.ErrorIsNil) + + // 4 entities: unparseable, unauthorized, watch error, closed chan. + results := facade.Watch(entities( + "urgle", + unknownModel, + coretesting.ModelTag.String(), + coretesting.ModelTag.String(), + )) + c.Assert(results.Results, gc.HasLen, 4) + stub.CheckCallNames(c, "WatchMigrationPhase", "WatchMigrationPhase") + + c.Check(results.Results, jc.DeepEquals, []params.NotifyWatchResult{{ + Error: ¶ms.Error{ + Message: `"urgle" is not a valid tag`, + }}, { + Error: ¶ms.Error{ + Message: "permission denied", + Code: "unauthorized access", + }}, { + Error: ¶ms.Error{ + Message: "blort", + }}, { + Error: ¶ms.Error{ + Message: "squish", + }, + }}) + c.Check(resources.Count(), gc.Equals, 0) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package migrationflag_test + +import ( + stdtesting "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *stdtesting.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/shim.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/shim.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/shim.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/shim.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,59 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package migrationflag + +import ( + "github.com/juju/errors" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/state" +) + +func init() { + common.RegisterStandardFacade("MigrationFlag", 1, newFacade) +} + +// newFacade wraps New to express the supplied *state.State as a Backend. +func newFacade(st *state.State, resources *common.Resources, auth common.Authorizer) (*Facade, error) { + facade, err := New(&backend{st}, resources, auth) + if err != nil { + return nil, errors.Trace(err) + } + return facade, nil +} + +// backend implements Backend by wrapping a *state.State. +type backend struct { + st *state.State +} + +// ModelUUID is part of the Backend interface. +func (shim *backend) ModelUUID() string { + return shim.st.ModelUUID() +} + +// WatchMigrationPhase is part of the Backend interface. +func (shim *backend) WatchMigrationPhase() (state.NotifyWatcher, error) { + watcher, err := shim.st.WatchMigrationStatus() + if err != nil { + return nil, errors.Trace(err) + } + return watcher, nil +} + +// MigrationPhase is part of the Backend interface. +func (shim *backend) MigrationPhase() (migration.Phase, error) { + mig, err := shim.st.GetModelMigration() + if errors.IsNotFound(err) { + return migration.NONE, nil + } else if err != nil { + return migration.UNKNOWN, errors.Trace(err) + } + phase, err := mig.Phase() + if err != nil { + return migration.UNKNOWN, errors.Trace(err) + } + return phase, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/util_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/util_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationflag/util_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationflag/util_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,119 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package migrationflag_test + +import ( + "github.com/juju/testing" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/state" + coretesting "github.com/juju/juju/testing" +) + +// agentAuth implements common.Authorizer for use in the tests. +type agentAuth struct { + common.Authorizer + machine bool + unit bool +} + +// AuthMachineAgent is part of the common.Authorizer interface. +func (auth agentAuth) AuthMachineAgent() bool { + return auth.machine +} + +// AuthUnitAgent is part of the common.Authorizer interface. +func (auth agentAuth) AuthUnitAgent() bool { + return auth.unit +} + +// newMockBackend returns a mock Backend that will add calls to the +// supplied testing.Stub, and return errors in the sequence it +// specifies. +func newMockBackend(stub *testing.Stub) *mockBackend { + return &mockBackend{ + stub: stub, + } +} + +// mockBackend implements migrationflag.Backend for use in the tests. +type mockBackend struct { + stub *testing.Stub +} + +// ModelUUID is part of the migrationflag.Backend interface. +func (mock *mockBackend) ModelUUID() string { + return coretesting.ModelTag.Id() +} + +// MigrationPhase is part of the migrationflag.Backend interface. +func (mock *mockBackend) MigrationPhase() (migration.Phase, error) { + mock.stub.AddCall("MigrationPhase") + if err := mock.stub.NextErr(); err != nil { + return migration.UNKNOWN, err + } + return migration.REAP, nil +} + +// WatchMigrationPhase is part of the migrationflag.Backend interface. +func (mock *mockBackend) WatchMigrationPhase() (state.NotifyWatcher, error) { + mock.stub.AddCall("WatchMigrationPhase") + if err := mock.stub.NextErr(); err != nil { + return nil, err + } + return newMockWatcher(mock.stub), nil +} + +// newMockWatcher consumes an error from the supplied testing.Stub, and +// returns a state.NotifyWatcher that either works or doesn't depending +// on whether the error was nil. +func newMockWatcher(stub *testing.Stub) *mockWatcher { + changes := make(chan struct{}, 1) + err := stub.NextErr() + if err == nil { + changes <- struct{}{} + } else { + close(changes) + } + return &mockWatcher{ + err: err, + changes: changes, + } +} + +// mockWatcher implements state.NotifyWatcher for use in the tests. +type mockWatcher struct { + state.NotifyWatcher + changes chan struct{} + err error +} + +// Changes is part of the state.NotifyWatcher interface. +func (mock *mockWatcher) Changes() <-chan struct{} { + return mock.changes +} + +// Err is part of the state.NotifyWatcher interface. +func (mock *mockWatcher) Err() error { + return mock.err +} + +// entities is a convenience constructor for params.Entities. +func entities(tags ...string) params.Entities { + entities := params.Entities{ + Entities: make([]params.Entity, len(tags)), + } + for i, tag := range tags { + entities.Entities[i].Tag = tag + } + return entities +} + +// authOK will always authenticate successfully. +var authOK = agentAuth{machine: true} + +// unknownModel is expected to induce a permissions error. +const unknownModel = "model-01234567-89ab-cdef-0123-456789abcdef" diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/doc.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/doc.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/doc.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/doc.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,6 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// This package defines the API facade for use by the migration master +// worker when communicating to it's own controller. +package migrationmaster diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,23 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster + +import ( + "github.com/juju/juju/migration" + "github.com/juju/juju/state" +) + +func PatchState(p Patcher, st Backend) { + p.PatchValue(&getBackend, func(*state.State) Backend { + return st + }) +} + +func PatchExportModel(p Patcher, f func(migration.StateExporter) ([]byte, error)) { + p.PatchValue(&exportModel, f) +} + +type Patcher interface { + PatchValue(ptr, value interface{}) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,131 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + coremigration "github.com/juju/juju/core/migration" + "github.com/juju/juju/migration" + "github.com/juju/juju/state" +) + +func init() { + common.RegisterStandardFacade("MigrationMaster", 1, NewAPI) +} + +// API implements the API required for the model migration +// master worker. +type API struct { + backend Backend + authorizer common.Authorizer + resources *common.Resources +} + +// NewAPI creates a new API server endpoint for the model migration +// master worker. +func NewAPI( + st *state.State, + resources *common.Resources, + authorizer common.Authorizer, +) (*API, error) { + if !authorizer.AuthModelManager() { + return nil, common.ErrPerm + } + return &API{ + backend: getBackend(st), + authorizer: authorizer, + resources: resources, + }, nil +} + +// Watch starts watching for an active migration for the model +// associated with the API connection. The returned id should be used +// with the NotifyWatcher facade to receive events. +func (api *API) Watch() (params.NotifyWatchResult, error) { + w, err := api.backend.WatchForModelMigration() + if err != nil { + return params.NotifyWatchResult{}, errors.Trace(err) + } + return params.NotifyWatchResult{ + NotifyWatcherId: api.resources.Register(w), + }, nil +} + +// GetMigrationStatus returns the details and progress of the latest +// model migration. +func (api *API) GetMigrationStatus() (params.FullMigrationStatus, error) { + empty := params.FullMigrationStatus{} + + mig, err := api.backend.GetModelMigration() + if err != nil { + return empty, errors.Annotate(err, "retrieving model migration") + } + + target, err := mig.TargetInfo() + if err != nil { + return empty, errors.Annotate(err, "retrieving target info") + } + + attempt, err := mig.Attempt() + if err != nil { + return empty, errors.Annotate(err, "retrieving attempt") + } + + phase, err := mig.Phase() + if err != nil { + return empty, errors.Annotate(err, "retrieving phase") + } + + return params.FullMigrationStatus{ + Spec: params.ModelMigrationSpec{ + ModelTag: names.NewModelTag(mig.ModelUUID()).String(), + TargetInfo: params.ModelMigrationTargetInfo{ + ControllerTag: target.ControllerTag.String(), + Addrs: target.Addrs, + CACert: target.CACert, + AuthTag: target.AuthTag.String(), + Password: target.Password, + }, + }, + Attempt: attempt, + Phase: phase.String(), + }, nil +} + +// SetPhase sets the phase of the active model migration. The provided +// phase must be a valid phase value, for example QUIESCE" or +// "ABORT". See the core/migration package for the complete list. +func (api *API) SetPhase(args params.SetMigrationPhaseArgs) error { + mig, err := api.backend.GetModelMigration() + if err != nil { + return errors.Annotate(err, "could not get migration") + } + + phase, ok := coremigration.ParsePhase(args.Phase) + if !ok { + return errors.Errorf("invalid phase: %q", args.Phase) + } + + err = mig.SetPhase(phase) + return errors.Annotate(err, "failed to set phase") +} + +var exportModel = migration.ExportModel + +// Export serializes the model associated with the API connection. +func (api *API) Export() (params.SerializedModel, error) { + var serialized params.SerializedModel + + bytes, err := exportModel(api.backend) + if err != nil { + return serialized, err + } + + serialized.Bytes = bytes + return serialized, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/migrationmaster_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,219 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/migrationmaster" + "github.com/juju/juju/apiserver/params" + apiservertesting "github.com/juju/juju/apiserver/testing" + coremigration "github.com/juju/juju/core/migration" + "github.com/juju/juju/migration" + "github.com/juju/juju/state" + "github.com/juju/juju/testing" +) + +// Ensure that Backend remains compatible with *state.State +var _ migrationmaster.Backend = (*state.State)(nil) + +type Suite struct { + testing.BaseSuite + + backend *stubBackend + resources *common.Resources + authorizer apiservertesting.FakeAuthorizer +} + +var _ = gc.Suite(&Suite{}) + +func (s *Suite) SetUpTest(c *gc.C) { + s.BaseSuite.SetUpTest(c) + + s.backend = &stubBackend{ + migration: new(stubMigration), + } + migrationmaster.PatchState(s, s.backend) + + s.resources = common.NewResources() + s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) + + s.authorizer = apiservertesting.FakeAuthorizer{ + EnvironManager: true, + } +} + +func (s *Suite) TestNotEnvironManager(c *gc.C) { + s.authorizer.EnvironManager = false + + api, err := s.makeAPI() + c.Assert(api, gc.IsNil) + c.Assert(err, gc.Equals, common.ErrPerm) +} + +func (s *Suite) TestWatch(c *gc.C) { + api := s.mustMakeAPI(c) + + watchResult, err := api.Watch() + c.Assert(err, jc.ErrorIsNil) + c.Assert(watchResult.NotifyWatcherId, gc.Not(gc.Equals), "") +} + +func (s *Suite) TestWatchError(c *gc.C) { + s.backend.watchError = errors.New("boom") + api := s.mustMakeAPI(c) + + w, err := api.Watch() + c.Assert(w, gc.Equals, params.NotifyWatchResult{}) + c.Assert(err, gc.ErrorMatches, "boom") +} + +func (s *Suite) TestGetMigrationStatus(c *gc.C) { + api := s.mustMakeAPI(c) + + status, err := api.GetMigrationStatus() + c.Assert(err, jc.ErrorIsNil) + c.Assert(status, gc.DeepEquals, params.FullMigrationStatus{ + Spec: params.ModelMigrationSpec{ + ModelTag: names.NewModelTag(modelUUID).String(), + TargetInfo: params.ModelMigrationTargetInfo{ + ControllerTag: names.NewModelTag(controllerUUID).String(), + Addrs: []string{"1.1.1.1:1", "2.2.2.2:2"}, + CACert: "trust me", + AuthTag: names.NewUserTag("admin").String(), + Password: "secret", + }, + }, + Attempt: 1, + Phase: "READONLY", + }) +} + +func (s *Suite) TestSetPhase(c *gc.C) { + api := s.mustMakeAPI(c) + + err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(s.backend.migration.phaseSet, gc.Equals, coremigration.ABORT) +} + +func (s *Suite) TestSetPhaseNoMigration(c *gc.C) { + s.backend.getErr = errors.New("boom") + api := s.mustMakeAPI(c) + + err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) + c.Assert(err, gc.ErrorMatches, "could not get migration: boom") +} + +func (s *Suite) TestSetPhaseBadPhase(c *gc.C) { + api := s.mustMakeAPI(c) + + err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "wat"}) + c.Assert(err, gc.ErrorMatches, `invalid phase: "wat"`) +} + +func (s *Suite) TestSetPhaseError(c *gc.C) { + s.backend.migration.setPhaseErr = errors.New("blam") + api := s.mustMakeAPI(c) + + err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) + c.Assert(err, gc.ErrorMatches, "failed to set phase: blam") +} + +func (s *Suite) TestExport(c *gc.C) { + exportModel := func(migration.StateExporter) ([]byte, error) { + return []byte("foo"), nil + } + migrationmaster.PatchExportModel(s, exportModel) + api := s.mustMakeAPI(c) + + serialized, err := api.Export() + + c.Assert(err, jc.ErrorIsNil) + c.Assert(serialized, gc.DeepEquals, params.SerializedModel{ + Bytes: []byte("foo"), + }) +} + +func (s *Suite) makeAPI() (*migrationmaster.API, error) { + return migrationmaster.NewAPI(nil, s.resources, s.authorizer) +} + +func (s *Suite) mustMakeAPI(c *gc.C) *migrationmaster.API { + api, err := migrationmaster.NewAPI(nil, s.resources, s.authorizer) + c.Assert(err, jc.ErrorIsNil) + return api +} + +type stubBackend struct { + migrationmaster.Backend + + watchError error + getErr error + migration *stubMigration +} + +func (b *stubBackend) WatchForModelMigration() (state.NotifyWatcher, error) { + if b.watchError != nil { + return nil, b.watchError + } + return apiservertesting.NewFakeNotifyWatcher(), nil +} + +func (b *stubBackend) GetModelMigration() (state.ModelMigration, error) { + if b.getErr != nil { + return nil, b.getErr + } + return b.migration, nil +} + +type stubMigration struct { + state.ModelMigration + setPhaseErr error + phaseSet coremigration.Phase +} + +func (m *stubMigration) Phase() (coremigration.Phase, error) { + return coremigration.READONLY, nil +} + +func (m *stubMigration) Attempt() (int, error) { + return 1, nil +} + +func (m *stubMigration) ModelUUID() string { + return modelUUID +} + +func (m *stubMigration) TargetInfo() (*coremigration.TargetInfo, error) { + return &coremigration.TargetInfo{ + ControllerTag: names.NewModelTag(controllerUUID), + Addrs: []string{"1.1.1.1:1", "2.2.2.2:2"}, + CACert: "trust me", + AuthTag: names.NewUserTag("admin"), + Password: "secret", + }, nil +} + +func (m *stubMigration) SetPhase(phase coremigration.Phase) error { + if m.setPhaseErr != nil { + return m.setPhaseErr + } + m.phaseSet = phase + return nil +} + +var modelUUID string +var controllerUUID string + +func init() { + modelUUID = utils.MustNewUUID().String() + controllerUUID = utils.MustNewUUID().String() +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/state.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/state.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationmaster/state.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationmaster/state.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,22 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationmaster + +import ( + "github.com/juju/juju/migration" + "github.com/juju/juju/state" +) + +// Backend defines the state functionality required by the +// migrationmaster facade. +type Backend interface { + migration.StateExporter + + WatchForModelMigration() (state.NotifyWatcher, error) + GetModelMigration() (state.ModelMigration, error) +} + +var getBackend = func(st *state.State) Backend { + return st +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/doc.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/doc.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/doc.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/doc.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,9 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// Package migrationminion defines the API facade for use by the +// migration minion worker to monitor the progress of, and interact +// with, model migrations. +// +// The migration minion runs inside every non-controller agent. +package migrationminion diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,16 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion + +import "github.com/juju/juju/state" + +func PatchState(p Patcher, st Backend) { + p.PatchValue(&getBackend, func(*state.State) Backend { + return st + }) +} + +type Patcher interface { + PatchValue(ptr, value interface{}) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/migrationminion.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/migrationminion.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/migrationminion.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/migrationminion.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,58 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion + +import ( + "github.com/juju/errors" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" +) + +func init() { + common.RegisterStandardFacade("MigrationMinion", 1, NewAPI) +} + +// API implements the API required for the model migration +// master worker. +type API struct { + backend Backend + authorizer common.Authorizer + resources *common.Resources +} + +// NewAPI creates a new API server endpoint for the model migration +// master worker. +func NewAPI( + st *state.State, + resources *common.Resources, + authorizer common.Authorizer, +) (*API, error) { + if !(authorizer.AuthMachineAgent() || authorizer.AuthUnitAgent()) { + return nil, common.ErrPerm + } + return &API{ + backend: getBackend(st), + authorizer: authorizer, + resources: resources, + }, nil +} + +// Watch starts watching for status updates for a migration attempt +// for the model. It will report when a migration starts and when its +// status changes (including when it finishes). An initial event will +// be fired if there has ever been a migration attempt for the model. +// +// The MigrationStatusWatcher facade must be used to receive events +// from the watcher. +func (api *API) Watch() (params.NotifyWatchResult, error) { + w, err := api.backend.WatchMigrationStatus() + if err != nil { + return params.NotifyWatchResult{}, errors.Trace(err) + } + return params.NotifyWatchResult{ + NotifyWatcherId: api.resources.Register(w), + }, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/migrationminion_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/migrationminion_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/migrationminion_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/migrationminion_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,97 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/migrationminion" + apiservertesting "github.com/juju/juju/apiserver/testing" + "github.com/juju/juju/state" + "github.com/juju/juju/testing" +) + +// Ensure that Backend remains compatible with *state.State +var _ migrationminion.Backend = (*state.State)(nil) + +type Suite struct { + testing.BaseSuite + + backend *stubBackend + resources *common.Resources + authorizer apiservertesting.FakeAuthorizer +} + +var _ = gc.Suite(&Suite{}) + +func (s *Suite) SetUpTest(c *gc.C) { + s.BaseSuite.SetUpTest(c) + + s.backend = &stubBackend{} + migrationminion.PatchState(s, s.backend) + + s.resources = common.NewResources() + s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) + + s.authorizer = apiservertesting.FakeAuthorizer{ + Tag: names.NewMachineTag("99"), + } +} + +func (s *Suite) TestAuthMachineAgent(c *gc.C) { + s.authorizer.Tag = names.NewMachineTag("42") + s.mustMakeAPI(c) +} + +func (s *Suite) TestAuthUnitAgent(c *gc.C) { + s.authorizer.Tag = names.NewUnitTag("foo/0") + s.mustMakeAPI(c) +} + +func (s *Suite) TestAuthNotAgent(c *gc.C) { + s.authorizer.Tag = names.NewUserTag("dorothy") + _, err := s.makeAPI() + c.Assert(err, gc.Equals, common.ErrPerm) +} + +func (s *Suite) TestWatchError(c *gc.C) { + s.backend.watchError = errors.New("boom") + api := s.mustMakeAPI(c) + _, err := api.Watch() + c.Assert(err, gc.ErrorMatches, "boom") + c.Assert(s.resources.Count(), gc.Equals, 0) +} + +func (s *Suite) TestWatch(c *gc.C) { + api := s.mustMakeAPI(c) + result, err := api.Watch() + c.Assert(err, jc.ErrorIsNil) + c.Assert(s.resources.Get(result.NotifyWatcherId), gc.NotNil) +} + +func (s *Suite) makeAPI() (*migrationminion.API, error) { + return migrationminion.NewAPI(nil, s.resources, s.authorizer) +} + +func (s *Suite) mustMakeAPI(c *gc.C) *migrationminion.API { + api, err := migrationminion.NewAPI(nil, s.resources, s.authorizer) + c.Assert(err, jc.ErrorIsNil) + return api +} + +type stubBackend struct { + migrationminion.Backend + watchError error +} + +func (b *stubBackend) WatchMigrationStatus() (state.NotifyWatcher, error) { + if b.watchError != nil { + return nil, b.watchError + } + return apiservertesting.NewFakeNotifyWatcher(), nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/state.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/state.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationminion/state.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationminion/state.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,16 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationminion + +import "github.com/juju/juju/state" + +// Backend defines the state functionality required by the +// MigrationMinion facade. +type Backend interface { + WatchMigrationStatus() (state.NotifyWatcher, error) +} + +var getBackend = func(st *state.State) Backend { + return st +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/doc.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/doc.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/doc.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/doc.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,7 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// This package defines the API facade for use by the migration master +// worker when interacting with the target controller during a +// migration. +package migrationtarget diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,113 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationtarget + +import ( + "github.com/juju/errors" + "github.com/juju/names" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/migration" + "github.com/juju/juju/state" +) + +func init() { + common.RegisterStandardFacade("MigrationTarget", 1, NewAPI) +} + +// API implements the API required for the model migration +// master worker when communicating with the target controller. +type API struct { + state *state.State + authorizer common.Authorizer + resources *common.Resources +} + +// NewAPI returns a new API. +func NewAPI( + st *state.State, + resources *common.Resources, + authorizer common.Authorizer, +) (*API, error) { + if err := checkAuth(authorizer, st); err != nil { + return nil, errors.Trace(err) + } + return &API{ + state: st, + authorizer: authorizer, + resources: resources, + }, nil +} + +func checkAuth(authorizer common.Authorizer, st *state.State) error { + if !authorizer.AuthClient() { + return errors.Trace(common.ErrPerm) + } + + // Type assertion is fine because AuthClient is true. + apiUser := authorizer.GetAuthTag().(names.UserTag) + if isAdmin, err := st.IsControllerAdministrator(apiUser); err != nil { + return errors.Trace(err) + } else if !isAdmin { + // The entire facade is only accessible to controller administrators. + return errors.Trace(common.ErrPerm) + } + return nil +} + +// Import takes a serialized Juju model, deserializes it, and +// recreates it in the receiving controller. +func (api *API) Import(serialized params.SerializedModel) error { + _, st, err := migration.ImportModel(api.state, serialized.Bytes) + if err != nil { + return err + } + defer st.Close() + // TODO(mjs) - post import checks + return err +} + +func (api *API) getModel(args params.ModelArgs) (*state.Model, error) { + tag, err := names.ParseModelTag(args.ModelTag) + if err != nil { + return nil, errors.Trace(err) + } + model, err := api.state.GetModel(tag) + if err != nil { + return nil, errors.Trace(err) + } + if model.MigrationMode() != state.MigrationModeImporting { + return nil, errors.New("migration mode for the model is not importing") + } + return model, nil +} + +// Abort removes the specified model from the database. It is an error to +// attempt to Abort a model that has a migration mode other than importing. +func (api *API) Abort(args params.ModelArgs) error { + model, err := api.getModel(args) + if err != nil { + return errors.Trace(err) + } + + st, err := api.state.ForModel(model.ModelTag()) + if err != nil { + return errors.Trace(err) + } + defer st.Close() + + return st.RemoveImportingModelDocs() +} + +// Activate sets the migration mode of the model to "active". It is an error to +// attempt to Abort a model that has a migration mode other than importing. +func (api *API) Activate(args params.ModelArgs) error { + model, err := api.getModel(args) + if err != nil { + return errors.Trace(err) + } + + return model.SetMigrationMode(state.MigrationModeActive) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/migrationtarget_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,197 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationtarget_test + +import ( + "github.com/juju/errors" + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/migrationtarget" + "github.com/juju/juju/apiserver/params" + apiservertesting "github.com/juju/juju/apiserver/testing" + "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/core/description" + "github.com/juju/juju/environs" + "github.com/juju/juju/jujuclient/jujuclienttesting" + "github.com/juju/juju/provider/dummy" + "github.com/juju/juju/state" + statetesting "github.com/juju/juju/state/testing" + "github.com/juju/juju/testing" +) + +type Suite struct { + statetesting.StateSuite + resources *common.Resources + authorizer apiservertesting.FakeAuthorizer +} + +var _ = gc.Suite(&Suite{}) + +func (s *Suite) SetUpTest(c *gc.C) { + // Set up InitialConfig with a dummy provider configuration. This + // is required to allow model import test to work. + env, err := environs.Prepare( + modelcmd.BootstrapContext(testing.Context(c)), + jujuclienttesting.NewMemStore(), + environs.PrepareParams{ + ControllerName: "dummycontroller", + BaseConfig: dummy.SampleConfig(), + CloudName: "dummy", + }, + ) + c.Assert(err, jc.ErrorIsNil) + s.InitialConfig = testing.CustomModelConfig(c, env.Config().AllAttrs()) + + // The call up to StateSuite's SetUpTest uses s.InitialConfig so + // it has to happen here. + s.StateSuite.SetUpTest(c) + + s.resources = common.NewResources() + s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) + + s.authorizer = apiservertesting.FakeAuthorizer{ + Tag: s.Owner, + } +} + +func (s *Suite) TestFacadeRegistered(c *gc.C) { + factory, err := common.Facades.GetFactory("MigrationTarget", 1) + c.Assert(err, jc.ErrorIsNil) + + api, err := factory(s.State, s.resources, s.authorizer, "") + c.Assert(err, jc.ErrorIsNil) + c.Assert(api, gc.FitsTypeOf, new(migrationtarget.API)) +} + +func (s *Suite) TestNotUser(c *gc.C) { + s.authorizer.Tag = names.NewMachineTag("0") + _, err := s.newAPI() + c.Assert(errors.Cause(err), gc.Equals, common.ErrPerm) +} + +func (s *Suite) TestNotControllerAdmin(c *gc.C) { + s.authorizer.Tag = names.NewUserTag("jrandomuser") + _, err := s.newAPI() + c.Assert(errors.Cause(err), gc.Equals, common.ErrPerm) +} + +func (s *Suite) importModel(c *gc.C, api *migrationtarget.API) names.ModelTag { + uuid, bytes := s.makeExportedModel(c) + err := api.Import(params.SerializedModel{Bytes: bytes}) + c.Assert(err, jc.ErrorIsNil) + return names.NewModelTag(uuid) +} + +func (s *Suite) TestImport(c *gc.C) { + api := s.mustNewAPI(c) + tag := s.importModel(c, api) + // Check the model was imported. + model, err := s.State.GetModel(tag) + c.Assert(err, jc.ErrorIsNil) + c.Assert(model.Name(), gc.Equals, "some-model") + c.Assert(model.MigrationMode(), gc.Equals, state.MigrationModeImporting) +} + +func (s *Suite) TestAbort(c *gc.C) { + api := s.mustNewAPI(c) + tag := s.importModel(c, api) + + err := api.Abort(params.ModelArgs{ModelTag: tag.String()}) + c.Assert(err, jc.ErrorIsNil) + + // The model should no longer exist. + _, err = s.State.GetModel(tag) + c.Assert(err, gc.ErrorMatches, `model not found`) +} + +func (s *Suite) TestAbortNotATag(c *gc.C) { + api := s.mustNewAPI(c) + err := api.Abort(params.ModelArgs{ModelTag: "not-a-tag"}) + c.Assert(err, gc.ErrorMatches, `"not-a-tag" is not a valid tag`) +} + +func (s *Suite) TestAbortMissingEnv(c *gc.C) { + api := s.mustNewAPI(c) + newUUID := utils.MustNewUUID().String() + err := api.Abort(params.ModelArgs{ModelTag: names.NewModelTag(newUUID).String()}) + c.Assert(err, gc.ErrorMatches, `model not found`) +} + +func (s *Suite) TestAbortNotImportingEnv(c *gc.C) { + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + api := s.mustNewAPI(c) + err = api.Abort(params.ModelArgs{ModelTag: model.ModelTag().String()}) + c.Assert(err, gc.ErrorMatches, `migration mode for the model is not importing`) +} + +func (s *Suite) TestActivate(c *gc.C) { + api := s.mustNewAPI(c) + tag := s.importModel(c, api) + + err := api.Activate(params.ModelArgs{ModelTag: tag.String()}) + c.Assert(err, jc.ErrorIsNil) + + // The model should no longer exist. + model, err := s.State.GetModel(tag) + c.Assert(err, jc.ErrorIsNil) + c.Assert(model.MigrationMode(), gc.Equals, state.MigrationModeActive) +} + +func (s *Suite) TestActivateNotATag(c *gc.C) { + api := s.mustNewAPI(c) + err := api.Activate(params.ModelArgs{ModelTag: "not-a-tag"}) + c.Assert(err, gc.ErrorMatches, `"not-a-tag" is not a valid tag`) +} + +func (s *Suite) TestActivateMissingEnv(c *gc.C) { + api := s.mustNewAPI(c) + newUUID := utils.MustNewUUID().String() + err := api.Activate(params.ModelArgs{ModelTag: names.NewModelTag(newUUID).String()}) + c.Assert(err, gc.ErrorMatches, `model not found`) +} + +func (s *Suite) TestActivateNotImportingEnv(c *gc.C) { + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + api := s.mustNewAPI(c) + err = api.Activate(params.ModelArgs{ModelTag: model.ModelTag().String()}) + c.Assert(err, gc.ErrorMatches, `migration mode for the model is not importing`) +} + +func (s *Suite) newAPI() (*migrationtarget.API, error) { + return migrationtarget.NewAPI(s.State, s.resources, s.authorizer) +} + +func (s *Suite) mustNewAPI(c *gc.C) *migrationtarget.API { + api, err := s.newAPI() + c.Assert(err, jc.ErrorIsNil) + return api +} + +func (s *Suite) makeExportedModel(c *gc.C) (string, []byte) { + model, err := s.State.Export() + c.Assert(err, jc.ErrorIsNil) + + newUUID := utils.MustNewUUID().String() + model.UpdateConfig(map[string]interface{}{ + "name": "some-model", + "uuid": newUUID, + "ca-cert": "not really a cert", + }) + + bytes, err := description.Serialize(model) + c.Assert(err, jc.ErrorIsNil) + return newUUID, bytes +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/migrationtarget/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/migrationtarget/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package migrationtarget_test + +import ( + stdtesting "testing" + + "github.com/juju/juju/testing" +) + +func TestAll(t *stdtesting.T) { + testing.MgoTestPackage(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/export_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -1,8 +1,24 @@ -// Copyright 2015 Canonical Ltd. +// Copyright 2016 Canonical Ltd. // Licensed under the AGPLv3, see LICENCE file for details. package modelmanager -func RestrictedProviderFields(mm *ModelManagerAPI, providerType string) ([]string, error) { - return mm.restrictedProviderFields(providerType) +import ( + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/names" +) + +func AuthCheck(c *gc.C, mm *ModelManagerAPI, user names.UserTag) bool { + mm.authCheck(user) + return mm.isAdmin +} + +func NewModelManagerAPIForTest( + st stateInterface, + authorizer common.Authorizer, + toolsFinder *common.ToolsFinder, +) *ModelManagerAPI { + return &ModelManagerAPI{st, authorizer, toolsFinder, false} } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/modelinfo_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/modelinfo_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/modelinfo_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/modelinfo_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,264 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package modelmanager_test + +import ( + "time" + + "github.com/juju/errors" + "github.com/juju/names" + gitjujutesting "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/modelmanager" + "github.com/juju/juju/apiserver/params" + apiservertesting "github.com/juju/juju/apiserver/testing" + "github.com/juju/juju/environs/config" + "github.com/juju/juju/state" + coretesting "github.com/juju/juju/testing" +) + +type modelInfoSuite struct { + coretesting.BaseSuite + authorizer apiservertesting.FakeAuthorizer + st *mockState + modelmanager *modelmanager.ModelManagerAPI +} + +var _ = gc.Suite(&modelInfoSuite{}) + +func (s *modelInfoSuite) SetUpTest(c *gc.C) { + s.BaseSuite.SetUpTest(c) + s.authorizer = apiservertesting.FakeAuthorizer{ + Tag: names.NewUserTag("admin@local"), + } + s.st = &mockState{} + s.st.model = &mockModel{ + owner: names.NewUserTag("bob@local"), + cfg: coretesting.ModelConfig(c), + users: []*mockModelUser{{ + userName: "admin", + access: state.ModelAdminAccess, + }, { + userName: "bob@local", + displayName: "Bob", + access: state.ModelReadAccess, + }, { + userName: "charlotte@local", + displayName: "Charlotte", + access: state.ModelReadAccess, + }}, + } + s.modelmanager = modelmanager.NewModelManagerAPIForTest(s.st, &s.authorizer, nil) +} + +func (s *modelInfoSuite) TestModelInfo(c *gc.C) { + s.st.model.users[1].SetErrors( + nil, state.NeverConnectedError("never connected"), + ) + info := s.getModelInfo(c) + c.Assert(info, jc.DeepEquals, params.ModelInfo{ + Name: "testenv", + UUID: s.st.model.cfg.UUID(), + ControllerUUID: coretesting.ModelTag.Id(), + OwnerTag: "user-bob@local", + ProviderType: "someprovider", + DefaultSeries: coretesting.FakeDefaultSeries, + Users: []params.ModelUserInfo{{ + UserName: "admin", + LastConnection: &time.Time{}, + Access: params.ModelWriteAccess, + }, { + UserName: "bob@local", + DisplayName: "Bob", + LastConnection: nil, // never connected + Access: params.ModelReadAccess, + }, { + UserName: "charlotte@local", + DisplayName: "Charlotte", + LastConnection: &time.Time{}, + Access: params.ModelReadAccess, + }}, + }) + s.st.CheckCalls(c, []gitjujutesting.StubCall{ + {"GetModel", []interface{}{names.NewModelTag(s.st.model.cfg.UUID())}}, + {"IsControllerAdministrator", []interface{}{names.NewUserTag("admin@local")}}, + }) + s.st.model.CheckCalls(c, []gitjujutesting.StubCall{ + {"Config", nil}, + {"Users", nil}, + {"Owner", nil}, + }) +} + +func (s *modelInfoSuite) TestModelInfoOwner(c *gc.C) { + s.authorizer.Tag = names.NewUserTag("bob@local") + info := s.getModelInfo(c) + c.Assert(info.Users, gc.HasLen, 3) +} + +func (s *modelInfoSuite) TestModelInfoNonOwner(c *gc.C) { + s.authorizer.Tag = names.NewUserTag("charlotte@local") + info := s.getModelInfo(c) + c.Assert(info.Users, gc.HasLen, 1) + c.Assert(info.Users[0].UserName, gc.Equals, "charlotte@local") +} + +func (s *modelInfoSuite) getModelInfo(c *gc.C) params.ModelInfo { + results, err := s.modelmanager.ModelInfo(params.Entities{ + Entities: []params.Entity{{ + names.NewModelTag(s.st.model.cfg.UUID()).String(), + }}, + }) + c.Assert(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + c.Assert(results.Results[0].Result, gc.NotNil) + c.Assert(results.Results[0].Error, gc.IsNil) + return *results.Results[0].Result +} + +func (s *modelInfoSuite) TestModelInfoErrorInvalidTag(c *gc.C) { + s.testModelInfoError(c, "user-bob", `"user-bob" is not a valid model tag`) +} + +func (s *modelInfoSuite) TestModelInfoErrorGetModelNotFound(c *gc.C) { + s.st.SetErrors(errors.NotFoundf("model")) + s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) +} + +func (s *modelInfoSuite) TestModelInfoErrorModelConfig(c *gc.C) { + s.st.model.SetErrors(errors.Errorf("no config for you")) + s.testModelInfoError(c, coretesting.ModelTag.String(), `no config for you`) +} + +func (s *modelInfoSuite) TestModelInfoErrorModelUsers(c *gc.C) { + s.st.model.SetErrors(errors.Errorf("no users for you")) + s.testModelInfoError(c, coretesting.ModelTag.String(), `no users for you`) +} + +func (s *modelInfoSuite) TestModelInfoErrorNoModelUsers(c *gc.C) { + s.st.model.users = nil + s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) +} + +func (s *modelInfoSuite) TestModelInfoErrorNoAccess(c *gc.C) { + s.authorizer.Tag = names.NewUserTag("nemo@local") + s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) +} + +func (s *modelInfoSuite) testModelInfoError(c *gc.C, modelTag, expectedErr string) { + results, err := s.modelmanager.ModelInfo(params.Entities{ + Entities: []params.Entity{{modelTag}}, + }) + c.Assert(err, jc.ErrorIsNil) + c.Assert(results.Results, gc.HasLen, 1) + c.Assert(results.Results[0].Result, gc.IsNil) + c.Assert(results.Results[0].Error, gc.ErrorMatches, expectedErr) +} + +type mockState struct { + gitjujutesting.Stub + model *mockModel + owner names.UserTag + users []*state.ModelUser +} + +func (st *mockState) ModelsForUser(user names.UserTag) ([]*state.UserModel, error) { + st.MethodCall(st, "ModelsForUser", user) + return nil, st.NextErr() +} + +func (st *mockState) IsControllerAdministrator(user names.UserTag) (bool, error) { + st.MethodCall(st, "IsControllerAdministrator", user) + return user.Canonical() == "admin@local", st.NextErr() +} + +func (st *mockState) NewModel(args state.ModelArgs) (*state.Model, *state.State, error) { + st.MethodCall(st, "NewModel", args) + return nil, nil, st.NextErr() +} + +func (st *mockState) ControllerModel() (*state.Model, error) { + st.MethodCall(st, "ControllerModel") + return nil, st.NextErr() +} + +func (st *mockState) ForModel(tag names.ModelTag) (*state.State, error) { + st.MethodCall(st, "ForModel", tag) + return nil, st.NextErr() +} + +func (st *mockState) GetModel(tag names.ModelTag) (modelmanager.Model, error) { + st.MethodCall(st, "GetModel", tag) + return st.model, st.NextErr() +} + +type mockModel struct { + gitjujutesting.Stub + owner names.UserTag + cfg *config.Config + users []*mockModelUser +} + +func (m *mockModel) Config() (*config.Config, error) { + m.MethodCall(m, "Config") + return m.cfg, m.NextErr() +} + +func (m *mockModel) Owner() names.UserTag { + m.MethodCall(m, "Owner") + m.PopNoErr() + return m.owner +} + +func (m *mockModel) Users() ([]common.ModelUser, error) { + m.MethodCall(m, "Users") + if err := m.NextErr(); err != nil { + return nil, err + } + users := make([]common.ModelUser, len(m.users)) + for i, user := range m.users { + users[i] = user + } + return users, nil +} + +type mockModelUser struct { + gitjujutesting.Stub + userName string + displayName string + access state.ModelAccess + lastConnection time.Time +} + +func (u *mockModelUser) Access() state.ModelAccess { + u.MethodCall(u, "Access") + u.PopNoErr() + return u.access +} + +func (u *mockModelUser) DisplayName() string { + u.MethodCall(u, "DisplayName") + u.PopNoErr() + return u.displayName +} + +func (u *mockModelUser) LastConnection() (time.Time, error) { + u.MethodCall(u, "LastConnection") + return u.lastConnection, u.NextErr() +} + +func (u *mockModelUser) UserName() string { + u.MethodCall(u, "UserName") + u.PopNoErr() + return u.userName +} + +func (u *mockModelUser) UserTag() names.UserTag { + u.MethodCall(u, "UserTag") + u.PopNoErr() + return names.NewUserTag(u.userName) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/modelmanager.go charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/modelmanager.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/modelmanager.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/modelmanager.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,14 +11,16 @@ "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" - "github.com/juju/utils" + "github.com/juju/txn" + "github.com/juju/version" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/environs" + "github.com/juju/juju/controller/modelmanager" "github.com/juju/juju/environs/config" + "github.com/juju/juju/juju/permission" "github.com/juju/juju/state" - "github.com/juju/juju/version" + "github.com/juju/juju/tools" ) var logger = loggo.GetLogger("juju.apiserver.modelmanager") @@ -27,8 +29,7 @@ common.RegisterStandardFacade("ModelManager", 2, NewModelManagerAPI) } -// ModelManager defines the methods on the modelmanager API end -// point. +// ModelManager defines the methods on the modelmanager API endpoint. type ModelManager interface { ConfigSkeleton(args params.ModelSkeletonConfigArgs) (params.ModelConfigResult, error) CreateModel(args params.ModelCreateArgs) (params.Model, error) @@ -41,6 +42,7 @@ state stateInterface authorizer common.Authorizer toolsFinder *common.ToolsFinder + isAdmin bool } var _ ModelManager = (*ModelManagerAPI)(nil) @@ -66,14 +68,15 @@ // authCheck checks if the user is acting on their own behalf, or if they // are an administrator acting on behalf of another user. -func (em *ModelManagerAPI) authCheck(user names.UserTag) error { +func (mm *ModelManagerAPI) authCheck(user names.UserTag) error { // Since we know this is a user tag (because AuthClient is true), // we just do the type assertion to the UserTag. - apiUser, _ := em.authorizer.GetAuthTag().(names.UserTag) - isAdmin, err := em.state.IsControllerAdministrator(apiUser) + apiUser, _ := mm.authorizer.GetAuthTag().(names.UserTag) + isAdmin, err := mm.state.IsControllerAdministrator(apiUser) if err != nil { return errors.Trace(err) } + mm.isAdmin = isAdmin if isAdmin { logger.Tracef("%q is a controller admin", apiUser.Canonical()) return nil @@ -94,32 +97,21 @@ Config() (*config.Config, error) } -var configValuesFromController = []string{ - "type", - "ca-cert", - "state-port", - "api-port", -} - // ConfigSkeleton returns config values to be used as a starting point for the // API caller to construct a valid model specific config. The provider // and region params are there for future use, and current behaviour expects // both of these to be empty. -func (em *ModelManagerAPI) ConfigSkeleton(args params.ModelSkeletonConfigArgs) (params.ModelConfigResult, error) { +func (mm *ModelManagerAPI) ConfigSkeleton(args params.ModelSkeletonConfigArgs) (params.ModelConfigResult, error) { var result params.ModelConfigResult - if args.Provider != "" { - return result, errors.NotValidf("provider value %q", args.Provider) - } if args.Region != "" { return result, errors.NotValidf("region value %q", args.Region) } - controllerEnv, err := em.state.ControllerModel() + controllerEnv, err := mm.state.ControllerModel() if err != nil { return result, errors.Trace(err) } - - config, err := em.configSkeleton(controllerEnv) + config, err := mm.configSkeleton(controllerEnv, args.Provider) if err != nil { return result, errors.Trace(err) } @@ -128,26 +120,19 @@ return result, nil } -func (em *ModelManagerAPI) restrictedProviderFields(providerType string) ([]string, error) { - provider, err := environs.Provider(providerType) - if err != nil { - return nil, errors.Trace(err) - } - - var fields []string - fields = append(fields, configValuesFromController...) - fields = append(fields, provider.RestrictedConfigAttributes()...) - return fields, nil -} - -func (em *ModelManagerAPI) configSkeleton(source ConfigSource) (map[string]interface{}, error) { +func (mm *ModelManagerAPI) configSkeleton(source ConfigSource, requestedProviderType string) (map[string]interface{}, error) { baseConfig, err := source.Config() if err != nil { return nil, errors.Trace(err) } + if requestedProviderType != "" && baseConfig.Type() != requestedProviderType { + return nil, errors.Errorf( + "cannot create new model with credentials for provider type %q on controller with provider type %q", + requestedProviderType, baseConfig.Type()) + } baseMap := baseConfig.AllAttrs() - fields, err := em.restrictedProviderFields(baseConfig.Type()) + fields, err := modelmanager.RestrictedProviderFields(baseConfig.Type()) if err != nil { return nil, errors.Trace(err) } @@ -161,62 +146,7 @@ return result, nil } -func (em *ModelManagerAPI) checkVersion(cfg map[string]interface{}) error { - // If there is no agent-version specified, use the current version. - // otherwise we need to check for tools - value, found := cfg["agent-version"] - if !found { - cfg["agent-version"] = version.Current.String() - return nil - } - valuestr, ok := value.(string) - if !ok { - return errors.Errorf("agent-version must be a string but has type '%T'", value) - } - num, err := version.Parse(valuestr) - if err != nil { - return errors.Trace(err) - } - if comp := num.Compare(version.Current); comp > 0 { - return errors.Errorf("agent-version cannot be greater than the server: %s", version.Current) - } else if comp < 0 { - // Look to see if we have tools available for that version. - // Obviously if the version is the same, we have the tools available. - list, err := em.toolsFinder.FindTools(params.FindToolsParams{ - Number: num, - }) - if err != nil { - return errors.Trace(err) - } - logger.Tracef("found tools: %#v", list) - if len(list.List) == 0 { - return errors.Errorf("no tools found for version %s", num) - } - } - return nil -} - -func (em *ModelManagerAPI) validConfig(attrs map[string]interface{}) (*config.Config, error) { - cfg, err := config.New(config.UseDefaults, attrs) - if err != nil { - return nil, errors.Annotate(err, "creating config from values failed") - } - provider, err := environs.Provider(cfg.Type()) - if err != nil { - return nil, errors.Trace(err) - } - cfg, err = provider.PrepareForCreateEnvironment(cfg) - if err != nil { - return nil, errors.Trace(err) - } - cfg, err = provider.Validate(cfg, nil) - if err != nil { - return nil, errors.Annotate(err, "provider validation failed") - } - return cfg, nil -} - -func (em *ModelManagerAPI) newModelConfig(args params.ModelCreateArgs, source ConfigSource) (*config.Config, error) { +func (mm *ModelManagerAPI) newModelConfig(args params.ModelCreateArgs, source ConfigSource) (*config.Config, error) { // For now, we just smash to the two maps together as we store // the account values and the model config together in the // *config.Config instance. @@ -228,72 +158,34 @@ for key, value := range args.Account { joint[key] = value } - if _, found := joint["uuid"]; found { + if _, ok := joint["uuid"]; ok { return nil, errors.New("uuid is generated, you cannot specify one") } baseConfig, err := source.Config() if err != nil { return nil, errors.Trace(err) } - baseMap := baseConfig.AllAttrs() - fields, err := em.restrictedProviderFields(baseConfig.Type()) - if err != nil { - return nil, errors.Trace(err) - } - // Before comparing any values, we need to push the config through - // the provider validation code. One of the reasons for this is that - // numbers being serialized through JSON get turned into float64. The - // schema code used in config will convert these back into integers. - // However, before we can create a valid config, we need to make sure - // we copy across fields from the main config that aren't there. - for _, field := range fields { - if _, found := joint[field]; !found { - if baseValue, found := baseMap[field]; found { - joint[field] = baseValue + creator := modelmanager.ModelConfigCreator{ + func(n version.Number) (tools.List, error) { + result, err := mm.toolsFinder.FindTools(params.FindToolsParams{ + Number: n, + }) + if err != nil { + return nil, errors.Trace(err) } - } - } - - // Generate the UUID for the server. - uuid, err := utils.NewUUID() - if err != nil { - return nil, errors.Annotate(err, "failed to generate environment uuid") - } - joint["uuid"] = uuid.String() - - if err := em.checkVersion(joint); err != nil { - return nil, errors.Annotate(err, "failed to create config") - } - - // validConfig must only be called once. - cfg, err := em.validConfig(joint) - if err != nil { - return nil, errors.Trace(err) + return result.List, nil + }, } - attrs := cfg.AllAttrs() - // Any values that would normally be copied from the controller - // config can also be defined, but if they differ from the controller - // values, an error is returned. - for _, field := range fields { - if value, found := attrs[field]; found { - if serverValue := baseMap[field]; value != serverValue { - return nil, errors.Errorf( - "specified %s \"%v\" does not match apiserver \"%v\"", - field, value, serverValue) - } - } - } - - return cfg, nil + return creator.NewModelConfig(mm.isAdmin, baseConfig, joint) } // CreateModel creates a new model using the account and // model config specified in the args. -func (em *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.Model, error) { +func (mm *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.Model, error) { result := params.Model{} // Get the controller model first. We need it both for the state // server owner and the ability to get the config. - controllerEnv, err := em.state.ControllerModel() + controllerModel, err := mm.state.ControllerModel() if err != nil { return result, errors.Trace(err) } @@ -306,19 +198,19 @@ // Any user is able to create themselves an model (until real fine // grain permissions are available), and admins (the creator of the state // server model) are able to create models for other people. - err = em.authCheck(ownerTag) + err = mm.authCheck(ownerTag) if err != nil { return result, errors.Trace(err) } - newConfig, err := em.newModelConfig(args, controllerEnv) + newConfig, err := mm.newModelConfig(args, controllerModel) if err != nil { - return result, errors.Trace(err) + return result, errors.Annotate(err, "failed to create config") } // NOTE: check the agent-version of the config, and if it is > the current // version, it is not supported, also check existing tools, and if we don't // have tools for that version, also die. - model, st, err := em.state.NewModel(newConfig, ownerTag) + model, st, err := mm.state.NewModel(state.ModelArgs{Config: newConfig, Owner: ownerTag}) if err != nil { return result, errors.Annotate(err, "failed to create new model") } @@ -335,7 +227,7 @@ // has access to in the current server. Only that controller owner // can list models for any user (at this stage). Other users // can only ask about their own models. -func (em *ModelManagerAPI) ListModels(user params.Entity) (params.UserModelList, error) { +func (mm *ModelManagerAPI) ListModels(user params.Entity) (params.UserModelList, error) { result := params.UserModelList{} userTag, err := names.ParseUserTag(user.Tag) @@ -343,12 +235,12 @@ return result, errors.Trace(err) } - err = em.authCheck(userTag) + err = mm.authCheck(userTag) if err != nil { return result, errors.Trace(err) } - models, err := em.state.ModelsForUser(userTag) + models, err := mm.state.ModelsForUser(userTag) if err != nil { return result, errors.Trace(err) } @@ -376,3 +268,236 @@ return result, nil } + +// ModelInfo returns information about the specified models. +func (m *ModelManagerAPI) ModelInfo(args params.Entities) (params.ModelInfoResults, error) { + results := params.ModelInfoResults{ + Results: make([]params.ModelInfoResult, len(args.Entities)), + } + + getModelInfo := func(arg params.Entity) (params.ModelInfo, error) { + tag, err := names.ParseModelTag(arg.Tag) + if err != nil { + return params.ModelInfo{}, err + } + model, err := m.state.GetModel(tag) + if errors.IsNotFound(err) { + return params.ModelInfo{}, common.ErrPerm + } else if err != nil { + return params.ModelInfo{}, err + } + + cfg, err := model.Config() + if err != nil { + return params.ModelInfo{}, err + } + users, err := model.Users() + if err != nil { + return params.ModelInfo{}, err + } + + owner := model.Owner() + info := params.ModelInfo{ + Name: cfg.Name(), + UUID: cfg.UUID(), + ControllerUUID: cfg.ControllerUUID(), + OwnerTag: owner.String(), + ProviderType: cfg.Type(), + DefaultSeries: config.PreferredSeries(cfg), + } + + authorizedOwner := m.authCheck(owner) == nil + for _, user := range users { + if !authorizedOwner && m.authCheck(user.UserTag()) != nil { + // The authenticated user is neither the owner + // nor administrator, nor the model user, so + // has no business knowing about the model user. + continue + } + userInfo, err := common.ModelUserInfo(user) + if err != nil { + return params.ModelInfo{}, errors.Trace(err) + } + info.Users = append(info.Users, userInfo) + } + + if len(info.Users) == 0 { + // No users, which means the authenticated user doesn't + // have access to the model. + return params.ModelInfo{}, common.ErrPerm + } + + return info, nil + } + + for i, arg := range args.Entities { + modelInfo, err := getModelInfo(arg) + if err != nil { + results.Results[i].Error = common.ServerError(err) + continue + } + results.Results[i].Result = &modelInfo + } + return results, nil +} + +// ModifyModelAccess changes the model access granted to users. +func (em *ModelManagerAPI) ModifyModelAccess(args params.ModifyModelAccessRequest) (result params.ErrorResults, err error) { + // API user must be a controller admin. + createdBy, _ := em.authorizer.GetAuthTag().(names.UserTag) + isAdmin, err := em.state.IsControllerAdministrator(createdBy) + if err != nil { + return result, errors.Trace(err) + } + if !isAdmin { + return result, errors.New("only controller admins can grant or revoke model access") + } + + result = params.ErrorResults{ + Results: make([]params.ErrorResult, len(args.Changes)), + } + if len(args.Changes) == 0 { + return result, nil + } + + for i, arg := range args.Changes { + modelAccess, err := FromModelAccessParam(arg.Access) + if err != nil { + err = errors.Annotate(err, "could not modify model access") + result.Results[i].Error = common.ServerError(err) + continue + } + + userTagString := arg.UserTag + user, err := names.ParseUserTag(userTagString) + if err != nil { + result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access")) + continue + } + modelTagString := arg.ModelTag + model, err := names.ParseModelTag(modelTagString) + if err != nil { + result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access")) + continue + } + + result.Results[i].Error = common.ServerError( + ChangeModelAccess(em.state, model, createdBy, user, arg.Action, modelAccess)) + } + return result, nil +} + +type stateAccessor interface { + ForModel(tag names.ModelTag) (*state.State, error) +} + +// resolveStateAccess returns the state representation of the logical model +// access type. +func resolveStateAccess(access permission.ModelAccess) (state.ModelAccess, error) { + var fail state.ModelAccess + switch access { + case permission.ModelReadAccess: + return state.ModelReadAccess, nil + case permission.ModelWriteAccess: + // TODO: Initially, we'll map "write" access to admin-level access. + // Post Juju-2.0, support for more nuanced access will be added to the + // permission business logic and state model. + return state.ModelAdminAccess, nil + } + logger.Errorf("invalid access permission: %+v", access) + return fail, errors.Errorf("invalid access permission") +} + +// isGreaterAccess returns whether the new access provides more permissions +// than the current access. +// TODO(cmars): If/when more access types are implemented in state, +// the implementation of this function will certainly need to change, and it +// should be abstracted away to juju/permission as pure business logic +// instead of operating on state values. +func isGreaterAccess(currentAccess, newAccess state.ModelAccess) bool { + if currentAccess == state.ModelReadAccess && newAccess == state.ModelAdminAccess { + return true + } + return false +} + +// ChangeModelAccess performs the requested access grant or revoke action for the +// specified user on the specified model. +func ChangeModelAccess(accessor stateAccessor, modelTag names.ModelTag, createdBy, accessedBy names.UserTag, action params.ModelAction, access permission.ModelAccess) error { + st, err := accessor.ForModel(modelTag) + if err != nil { + return errors.Annotate(err, "could not lookup model") + } + defer st.Close() + + _, err = st.Model() + if err != nil { + return errors.Trace(err) + } + + stateAccess, err := resolveStateAccess(access) + if err != nil { + return errors.Annotate(err, "could not resolve model access") + } + + switch action { + case params.GrantModelAccess: + _, err = st.AddModelUser(state.ModelUserSpec{User: accessedBy, CreatedBy: createdBy, Access: stateAccess}) + if errors.IsAlreadyExists(err) { + modelUser, err := st.ModelUser(accessedBy) + if errors.IsNotFound(err) { + // Conflicts with prior check, must be inconsistent state. + err = txn.ErrExcessiveContention + } + if err != nil { + return errors.Annotate(err, "could not look up model access for user") + } + + // Only set access if greater access is being granted. + if isGreaterAccess(modelUser.Access(), stateAccess) { + err = modelUser.SetAccess(stateAccess) + if err != nil { + return errors.Annotate(err, "could not set model access for user") + } + } else { + return errors.Errorf("user already has %q access", modelUser.Access()) + } + return nil + } + return errors.Annotate(err, "could not grant model access") + + case params.RevokeModelAccess: + if stateAccess == state.ModelReadAccess { + // Revoking read access removes all access. + err := st.RemoveModelUser(accessedBy) + return errors.Annotate(err, "could not revoke model access") + + } else if stateAccess == state.ModelAdminAccess { + // Revoking admin access sets read-only. + modelUser, err := st.ModelUser(accessedBy) + if err != nil { + return errors.Annotate(err, "could not look up model access for user") + } + err = modelUser.SetAccess(state.ModelReadAccess) + return errors.Annotate(err, "could not set model access to read-only") + + } else { + return errors.Errorf("don't know how to revoke %q access", stateAccess) + } + + default: + return errors.Errorf("unknown action %q", action) + } +} + +// FromModelAccessParam returns the logical model access type from the API wireformat type. +func FromModelAccessParam(paramAccess params.ModelAccessPermission) (permission.ModelAccess, error) { + var fail permission.ModelAccess + switch paramAccess { + case params.ModelReadAccess: + return permission.ModelReadAccess, nil + case params.ModelWriteAccess: + return permission.ModelWriteAccess, nil + } + return fail, errors.Errorf("invalid model access permission %q", paramAccess) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/modelmanager_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/modelmanager_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/modelmanager_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/modelmanager_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,6 +4,10 @@ package modelmanager_test import ( + "regexp" + "time" + + "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" jc "github.com/juju/testing/checkers" @@ -16,6 +20,7 @@ "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" jujutesting "github.com/juju/juju/juju/testing" + jujuversion "github.com/juju/juju/version" // Register the providers for the field check test _ "github.com/juju/juju/provider/azure" _ "github.com/juju/juju/provider/ec2" @@ -24,7 +29,7 @@ _ "github.com/juju/juju/provider/openstack" "github.com/juju/juju/state" coretesting "github.com/juju/juju/testing" - "github.com/juju/juju/version" + "github.com/juju/juju/testing/factory" ) type modelManagerBaseSuite struct { @@ -131,56 +136,12 @@ c.Assert(err, gc.ErrorMatches, "permission denied") } -func (s *modelManagerSuite) TestRestrictedProviderFields(c *gc.C) { - s.setAPIUser(c, names.NewUserTag("non-admin@remote")) - for i, test := range []struct { - provider string - expected []string - }{ - { - provider: "azure", - expected: []string{ - "type", "ca-cert", "state-port", "api-port", - "subscription-id", "tenant-id", "application-id", "application-password", "location", - "controller-resource-group", "storage-account-type"}, - }, { - provider: "dummy", - expected: []string{ - "type", "ca-cert", "state-port", "api-port"}, - }, { - provider: "joyent", - expected: []string{ - "type", "ca-cert", "state-port", "api-port"}, - }, { - provider: "maas", - expected: []string{ - "type", "ca-cert", "state-port", "api-port", - "maas-server"}, - }, { - provider: "openstack", - expected: []string{ - "type", "ca-cert", "state-port", "api-port", - "region", "auth-url", "auth-mode"}, - }, { - provider: "ec2", - expected: []string{ - "type", "ca-cert", "state-port", "api-port", - "region"}, - }, - } { - c.Logf("%d: %s provider", i, test.provider) - fields, err := modelmanager.RestrictedProviderFields(s.modelmanager, test.provider) - c.Check(err, jc.ErrorIsNil) - c.Check(fields, jc.SameContents, test.expected) - } -} - func (s *modelManagerSuite) TestConfigSkeleton(c *gc.C) { s.setAPIUser(c, names.NewUserTag("non-admin@remote")) _, err := s.modelmanager.ConfigSkeleton( params.ModelSkeletonConfigArgs{Provider: "ec2"}) - c.Check(err, gc.ErrorMatches, `provider value "ec2" not valid`) + c.Check(err, gc.ErrorMatches, `cannot create new model with credentials for provider type "ec2" on controller with provider type "dummy"`) _, err = s.modelmanager.ConfigSkeleton( params.ModelSkeletonConfigArgs{Region: "the sun"}) c.Check(err, gc.ErrorMatches, `region value "the sun" not valid`) @@ -193,10 +154,11 @@ apiPort := s.Environ.Config().APIPort() c.Assert(skeleton.Config, jc.DeepEquals, params.ModelConfig{ - "type": "dummy", - "ca-cert": coretesting.CACert, - "state-port": 1234, - "api-port": apiPort, + "type": "dummy", + "controller-uuid": coretesting.ModelTag.Id(), + "ca-cert": coretesting.CACert, + "state-port": 1234, + "api-port": apiPort, }) } @@ -206,7 +168,9 @@ args := s.createArgs(c, admin) args.Config["controller"] = "maybe" _, err := s.modelmanager.CreateModel(args) - c.Assert(err, gc.ErrorMatches, "provider validation failed: controller: expected bool, got string\\(\"maybe\"\\)") + c.Assert(err, gc.ErrorMatches, + "failed to create config: provider validation failed: controller: expected bool, got string\\(\"maybe\"\\)", + ) } func (s *modelManagerSuite) TestCreateModelBadConfig(c *gc.C) { @@ -220,24 +184,24 @@ { key: "uuid", value: "anything", - errMatch: `uuid is generated, you cannot specify one`, + errMatch: `failed to create config: uuid is generated, you cannot specify one`, }, { key: "type", value: "fake", - errMatch: `specified type "fake" does not match apiserver "dummy"`, + errMatch: `failed to create config: specified type "fake" does not match controller "dummy"`, }, { key: "ca-cert", value: coretesting.OtherCACert, - errMatch: `(?s)specified ca-cert ".*" does not match apiserver ".*"`, + errMatch: `failed to create config: (?s)specified ca-cert ".*" does not match controller ".*"`, }, { key: "state-port", value: 9876, - errMatch: `specified state-port "9876" does not match apiserver "1234"`, + errMatch: `failed to create config: specified state-port "9876" does not match controller "1234"`, }, { // The api-port is dynamic, but always in user-space, so > 1024. key: "api-port", value: 123, - errMatch: `specified api-port "123" does not match apiserver ".*"`, + errMatch: `failed to create config: specified api-port "123" does not match controller ".*"`, }, } { c.Logf("%d: %s", i, test.key) @@ -252,20 +216,22 @@ func (s *modelManagerSuite) TestCreateModelSameAgentVersion(c *gc.C) { admin := s.AdminUserTag(c) s.setAPIUser(c, admin) - args := s.createArgsForVersion(c, admin, version.Current.String()) + args := s.createArgsForVersion(c, admin, jujuversion.Current.String()) _, err := s.modelmanager.CreateModel(args) c.Assert(err, jc.ErrorIsNil) } func (s *modelManagerSuite) TestCreateModelBadAgentVersion(c *gc.C) { - s.PatchValue(&version.Current, coretesting.FakeVersionNumber) + err := s.BackingState.SetModelAgentVersion(coretesting.FakeVersionNumber) + c.Assert(err, jc.ErrorIsNil) + admin := s.AdminUserTag(c) s.setAPIUser(c, admin) - bigger := version.Current + bigger := coretesting.FakeVersionNumber bigger.Minor += 1 - smaller := version.Current + smaller := coretesting.FakeVersionNumber smaller.Minor -= 1 for i, test := range []struct { @@ -280,7 +246,7 @@ errMatch: `failed to create config: invalid version \"not a number\"`, }, { value: bigger.String(), - errMatch: "failed to create config: agent-version cannot be greater than the server: .*", + errMatch: "failed to create config: agent-version .* cannot be greater than the controller .*", }, { value: smaller.String(), errMatch: "failed to create config: no tools found for version .*", @@ -345,6 +311,402 @@ c.Assert(err, gc.ErrorMatches, "permission denied") } +func (s *modelManagerSuite) TestAdminModelManager(c *gc.C) { + user := s.AdminUserTag(c) + s.setAPIUser(c, user) + c.Assert(modelmanager.AuthCheck(c, s.modelmanager, user), jc.IsTrue) +} + +func (s *modelManagerSuite) TestNonAdminModelManager(c *gc.C) { + user := names.NewUserTag("external@remote") + s.setAPIUser(c, user) + c.Assert(modelmanager.AuthCheck(c, s.modelmanager, user), jc.IsFalse) +} + +func (s *modelManagerSuite) TestGrantMissingUserFails(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: names.NewLocalUserTag("foobar").String(), + Action: params.GrantModelAccess, + Access: params.ModelReadAccess, + ModelTag: names.NewModelTag(model.UUID()).String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + expectedErr := `could not grant model access: user "foobar" does not exist locally: user "foobar" not found` + c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) +} + +func (s *modelManagerSuite) TestGrantMissingModelFails(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + user := s.Factory.MakeModelUser(c, nil) + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.UserTag().String(), + Action: params.GrantModelAccess, + Access: params.ModelReadAccess, + ModelTag: names.NewModelTag("17e4bd2d-3e08-4f3d-b945-087be7ebdce4").String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + expectedErr := `model not found` + c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) +} + +func (s *modelManagerSuite) TestRevokeAdminLeavesReadAccess(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + user := s.Factory.MakeModelUser(c, &factory.ModelUserParams{Access: state.ModelAdminAccess}) + modelUser, err := s.State.ModelUser(user.UserTag()) + c.Assert(err, jc.ErrorIsNil) + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.UserTag().String(), + Action: params.RevokeModelAccess, + Access: params.ModelWriteAccess, + ModelTag: modelUser.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + modelUser, err = s.State.ModelUser(user.UserTag()) + c.Assert(err, jc.ErrorIsNil) + c.Assert(modelUser.ReadOnly(), jc.IsTrue) +} + +func (s *modelManagerSuite) TestRevokeReadRemovesModelUser(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + user := s.Factory.MakeModelUser(c, nil) + modelUser, err := s.State.ModelUser(user.UserTag()) + c.Assert(err, jc.ErrorIsNil) + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.UserTag().String(), + Action: params.RevokeModelAccess, + Access: params.ModelReadAccess, + ModelTag: modelUser.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + _, err = s.State.ModelUser(user.UserTag()) + c.Assert(errors.IsNotFound(err), jc.IsTrue) +} + +func (s *modelManagerSuite) TestRevokeModelMissingUser(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + user := names.NewUserTag("bob") + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.String(), + Action: params.RevokeModelAccess, + Access: params.ModelReadAccess, + ModelTag: model.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.ErrorMatches, `could not revoke model access: model user "bob@local" does not exist`) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.NotNil) + + _, err = st.ModelUser(user) + c.Assert(errors.IsNotFound(err), jc.IsTrue) +} + +func (s *modelManagerSuite) TestGrantOnlyGreaterAccess(c *gc.C) { + user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar"}) + s.setAPIUser(c, s.AdminUserTag(c)) + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.UserTag().String(), + Action: params.GrantModelAccess, + Access: params.ModelReadAccess, + ModelTag: model.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + result, err = s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.ErrorMatches, `user already has "read" access`) +} + +func (s *modelManagerSuite) TestGrantModelAddLocalUser(c *gc.C) { + user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar", NoModelUser: true}) + s.setAPIUser(c, s.AdminUserTag(c)) + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.UserTag().String(), + Action: params.GrantModelAccess, + Access: params.ModelReadAccess, + ModelTag: model.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + modelUser, err := st.ModelUser(user.UserTag()) + c.Assert(err, jc.ErrorIsNil) + c.Assert(modelUser.UserName(), gc.Equals, user.UserTag().Canonical()) + c.Assert(modelUser.CreatedBy(), gc.Equals, "admin@local") + c.Assert(modelUser.ReadOnly(), jc.IsTrue) + lastConn, err := modelUser.LastConnection() + c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) + c.Assert(lastConn, gc.Equals, time.Time{}) +} + +func (s *modelManagerSuite) TestGrantModelAddRemoteUser(c *gc.C) { + user := names.NewUserTag("foobar@ubuntuone") + s.setAPIUser(c, s.AdminUserTag(c)) + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.String(), + Action: params.GrantModelAccess, + Access: params.ModelReadAccess, + ModelTag: model.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + modelUser, err := st.ModelUser(user) + c.Assert(err, jc.ErrorIsNil) + c.Assert(modelUser.UserName(), gc.Equals, user.Canonical()) + c.Assert(modelUser.CreatedBy(), gc.Equals, "admin@local") + c.Assert(modelUser.ReadOnly(), jc.IsTrue) + lastConn, err := modelUser.LastConnection() + c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) + c.Assert(lastConn.IsZero(), jc.IsTrue) +} + +func (s *modelManagerSuite) TestGrantModelAddAdminUser(c *gc.C) { + user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar", NoModelUser: true}) + s.setAPIUser(c, s.AdminUserTag(c)) + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.Tag().String(), + Action: params.GrantModelAccess, + Access: params.ModelWriteAccess, + ModelTag: model.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + modelUser, err := st.ModelUser(user.UserTag()) + c.Assert(err, jc.ErrorIsNil) + c.Assert(modelUser.UserName(), gc.Equals, user.UserTag().Canonical()) + c.Assert(modelUser.CreatedBy(), gc.Equals, "admin@local") + c.Assert(modelUser.ReadOnly(), jc.IsFalse) + lastConn, err := modelUser.LastConnection() + c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) + c.Assert(lastConn, gc.Equals, time.Time{}) +} + +func (s *modelManagerSuite) TestGrantModelIncreaseAccess(c *gc.C) { + user := s.Factory.MakeUser(c, &factory.UserParams{Name: "foobar"}) + s.setAPIUser(c, s.AdminUserTag(c)) + st := s.Factory.MakeModel(c, nil) + defer st.Close() + model, err := st.Model() + c.Assert(err, jc.ErrorIsNil) + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.Tag().String(), + Action: params.GrantModelAccess, + Access: params.ModelReadAccess, + ModelTag: model.ModelTag().String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + args = params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: user.Tag().String(), + Action: params.GrantModelAccess, + Access: params.ModelWriteAccess, + ModelTag: model.ModelTag().String(), + }}} + + result, err = s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.IsNil) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.IsNil) + + modelUser, err := s.State.ModelUser(user.UserTag()) + c.Assert(err, jc.ErrorIsNil) + c.Assert(modelUser.UserName(), gc.Equals, user.UserTag().Canonical()) + c.Assert(modelUser.Access(), gc.Equals, state.ModelAdminAccess) +} + +func (s *modelManagerSuite) TestGrantModelInvalidUserTag(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + for _, testParam := range []struct { + tag string + validTag bool + }{{ + tag: "unit-foo/0", + validTag: true, + }, { + tag: "service-foo", + validTag: true, + }, { + tag: "relation-wordpress:db mysql:db", + validTag: true, + }, { + tag: "machine-0", + validTag: true, + }, { + tag: "user@local", + validTag: false, + }, { + tag: "user-Mua^h^h^h^arh", + validTag: true, + }, { + tag: "user@", + validTag: false, + }, { + tag: "user@ubuntuone", + validTag: false, + }, { + tag: "user@ubuntuone", + validTag: false, + }, { + tag: "@ubuntuone", + validTag: false, + }, { + tag: "in^valid.", + validTag: false, + }, { + tag: "", + validTag: false, + }, + } { + var expectedErr string + errPart := `could not modify model access: "` + regexp.QuoteMeta(testParam.tag) + `" is not a valid ` + + if testParam.validTag { + // The string is a valid tag, but not a user tag. + expectedErr = errPart + `user tag` + } else { + // The string is not a valid tag of any kind. + expectedErr = errPart + `tag` + } + + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: testParam.tag, + Action: params.GrantModelAccess, + Access: params.ModelReadAccess, + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) + } +} + +func (s *modelManagerSuite) TestModifyModelAccessEmptyArgs(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + args := params.ModifyModelAccessRequest{Changes: []params.ModifyModelAccess{{}}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + expectedErr := `could not modify model access: invalid model access permission ""` + c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) +} + +func (s *modelManagerSuite) TestModifyModelAccessInvalidAction(c *gc.C) { + s.setAPIUser(c, s.AdminUserTag(c)) + model, err := s.State.Model() + c.Assert(err, jc.ErrorIsNil) + + var dance params.ModelAction = "dance" + args := params.ModifyModelAccessRequest{ + Changes: []params.ModifyModelAccess{{ + UserTag: "user-user@local", + Action: dance, + Access: params.ModelReadAccess, + ModelTag: names.NewModelTag(model.UUID()).String(), + }}} + + result, err := s.modelmanager.ModifyModelAccess(args) + c.Assert(err, jc.ErrorIsNil) + expectedErr := `unknown action "dance"` + c.Assert(result.OneError(), gc.ErrorMatches, expectedErr) + c.Assert(result.Results, gc.HasLen, 1) + c.Assert(result.Results[0].Error, gc.ErrorMatches, expectedErr) +} + type fakeProvider struct { environs.EnvironProvider } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/state.go charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/state.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/modelmanager/state.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/modelmanager/state.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "github.com/juju/names" + "github.com/juju/juju/apiserver/common" "github.com/juju/juju/environs/config" "github.com/juju/juju/state" ) @@ -17,10 +18,42 @@ type stateInterface interface { ModelsForUser(names.UserTag) ([]*state.UserModel, error) IsControllerAdministrator(user names.UserTag) (bool, error) - NewModel(*config.Config, names.UserTag) (*state.Model, *state.State, error) + NewModel(state.ModelArgs) (*state.Model, *state.State, error) ControllerModel() (*state.Model, error) + ForModel(tag names.ModelTag) (*state.State, error) + GetModel(names.ModelTag) (Model, error) +} + +type Model interface { + Config() (*config.Config, error) + Owner() names.UserTag + Users() ([]common.ModelUser, error) } type stateShim struct { *state.State } + +func (st stateShim) GetModel(tag names.ModelTag) (Model, error) { + m, err := st.State.GetModel(tag) + if err != nil { + return nil, err + } + return modelShim{m}, nil +} + +type modelShim struct { + *state.Model +} + +func (m modelShim) Users() ([]common.ModelUser, error) { + stateUsers, err := m.Model.Users() + if err != nil { + return nil, err + } + users := make([]common.ModelUser, len(stateUsers)) + for i, user := range stateUsers { + users[i] = user + } + return users, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/actions.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/actions.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/actions.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/actions.go 2016-04-28 06:03:34.000000000 +0000 @@ -75,19 +75,6 @@ Error *Error `json:"error,omitempty"` } -// ActionsQueryResults holds a slice of responses from the Actions -// query. -type ActionsQueryResults struct { - Results []ActionsQueryResult `json:"results,omitempty"` -} - -// ActionsQueryResult holds the name and parameters of an query result. -type ActionsQueryResult struct { - Receiver string `json:"receiver,omitempty"` - Action ActionResult `json:"action,omitempty"` - Error *Error `json:"error,omitempty"` -} - // ActionExecutionResults holds a slice of ActionExecutionResult for a // bulk action API call type ActionExecutionResults struct { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/apierror.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/apierror.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/apierror.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/apierror.go 2016-04-28 06:03:34.000000000 +0000 @@ -67,6 +67,7 @@ CodeStopped = "stopped" CodeDead = "dead" CodeHasAssignedUnits = "machine has assigned units" + CodeHasHostedModels = "controller has hosted models" CodeMachineHasAttachedStorage = "machine has attached storage" CodeNotProvisioned = "not provisioned" CodeNoAddressSet = "no address set" @@ -153,6 +154,10 @@ return ErrCode(err) == CodeHasAssignedUnits } +func IsCodeHasHostedModels(err error) bool { + return ErrCode(err) == CodeHasHostedModels +} + func IsCodeMachineHasAttachedStorage(err error) bool { return ErrCode(err) == CodeMachineHasAttachedStorage } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/backups.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/backups.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/backups.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/backups.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,7 +6,7 @@ import ( "time" - "github.com/juju/juju/version" + "github.com/juju/version" ) // BackupsCreateArgs holds the args for the API Create method. @@ -66,6 +66,9 @@ Machine string Hostname string Version version.Number + + CACert string + CAPrivateKey string } // RestoreArgs Holds the backup file or id diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/controller.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/controller.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/controller.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/controller.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,48 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package params + +// DestroyControllerArgs holds the arguments for destroying a controller. +type DestroyControllerArgs struct { + // DestroyModels specifies whether or not the hosted models + // should be destroyed as well. If this is not specified, and there are + // other hosted models, the destruction of the controller will fail. + DestroyModels bool `json:"destroy-models"` +} + +// ModelBlockInfo holds information about an model and its +// current blocks. +type ModelBlockInfo struct { + Name string `json:"name"` + UUID string `json:"model-uuid"` + OwnerTag string `json:"owner-tag"` + Blocks []string `json:"blocks"` +} + +// ModelBlockInfoList holds information about the blocked models +// for a controller. +type ModelBlockInfoList struct { + Models []ModelBlockInfo `json:"models,omitempty"` +} + +// RemoveBlocksArgs holds the arguments for the RemoveBlocks command. It is a +// struct to facilitate the easy addition of being able to remove blocks for +// individual models at a later date. +type RemoveBlocksArgs struct { + All bool `json:"all"` +} + +// ModelStatus holds information about the status of a juju model. +type ModelStatus struct { + ModelTag string `json:"model-tag"` + Life Life `json:"life"` + HostedMachineCount int `json:"hosted-machine-count"` + ServiceCount int `json:"service-count"` + OwnerTag string `json:"owner-tag"` +} + +// ModelStatusResults holds status information about a group of models. +type ModelStatusResults struct { + Results []ModelStatus `json:"models"` +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/internal.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/internal.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/internal.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/internal.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,13 +7,13 @@ "time" "github.com/juju/utils/exec" + "github.com/juju/version" "github.com/juju/juju/constraints" "github.com/juju/juju/instance" "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/status" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) // MachineContainersParams holds the arguments for making a SetSupportedContainers @@ -317,12 +317,16 @@ InstanceId instance.Id Nonce string Characteristics *instance.HardwareCharacteristics - Networks []Network - Interfaces []NetworkInterface Volumes []Volume // VolumeAttachments is a mapping from volume tag to // volume attachment info. VolumeAttachments map[string]VolumeAttachmentInfo + + NetworkConfig []NetworkConfig + + // TODO(dimitern): No longer used, drop at the end of this PoC. + Networks []Network + Interfaces []NetworkInterface } // InstancesInfo holds the parameters for making a SetInstanceInfo @@ -645,3 +649,25 @@ type SingularClaims struct { Claims []SingularClaim `json:"Claims"` } + +// GUIArchiveVersion holds information on a specific GUI archive version. +type GUIArchiveVersion struct { + // Version holds the Juju GUI version number. + Version version.Number `json:"version"` + // SHA256 holds the SHA256 hash of the GUI tar.bz2 archive. + SHA256 string `json:"sha256"` + // Current holds whether this specific version is the current one served + // by the controller. + Current bool `json:"current"` +} + +// GUIArchiveResponse holds the response to /gui-archive GET requests. +type GUIArchiveResponse struct { + Versions []GUIArchiveVersion `json:"versions"` +} + +// GUIVersionRequest holds the body for /gui-version PUT requests. +type GUIVersionRequest struct { + // Version holds the Juju GUI version number. + Version version.Number `json:"version"` +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/migration.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/migration.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/migration.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/migration.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,88 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package params + +// InitiateModelMigrationArgs holds the details required to start one +// or more model migrations. +type InitiateModelMigrationArgs struct { + Specs []ModelMigrationSpec `json:"specs"` +} + +// ModelMigrationSpec holds the details required to start the +// migration of a single model. +type ModelMigrationSpec struct { + ModelTag string `json:"model-tag"` + TargetInfo ModelMigrationTargetInfo `json:"target-info"` +} + +// ModelMigrationTargetInfo holds the details required to connect to +// and authenticate with a remote controller for model migration. +type ModelMigrationTargetInfo struct { + ControllerTag string `json:"controller-tag"` + Addrs []string `json:"addrs"` + CACert string `json:"ca-cert"` + AuthTag string `json:"auth-tag"` + Password string `json:"password"` +} + +// InitiateModelMigrationResults is used to return the result of one +// or more attempts to start model migrations. +type InitiateModelMigrationResults struct { + Results []InitiateModelMigrationResult `json:"results"` +} + +// InitiateModelMigrationResult is used to return the result of one +// model migration initiation attempt. +type InitiateModelMigrationResult struct { + ModelTag string `json:"model-tag"` + Error *Error `json:"error"` + Id string `json:"id"` // the ID for the migration attempt +} + +// SetMigrationPhaseArgs provides a migration phase to the +// migrationmaster.SetPhase API method. +type SetMigrationPhaseArgs struct { + Phase string `json:"phase"` +} + +// SerializedModel wraps a buffer contain a serialised Juju model. +type SerializedModel struct { + Bytes []byte `json:"bytes"` +} + +// ModelArgs wraps a simple model tag. +type ModelArgs struct { + ModelTag string `json:"model-tag"` +} + +// MigrationStatus reports the current status of a model migration. +type MigrationStatus struct { + Attempt int `json:"attempt"` + Phase string `json:"phase"` + + // TODO(mjs): I'm not convinced these Source fields will get used. + SourceAPIAddrs []string `json:"source-api-addrs"` + SourceCACert string `json:"source-ca-cert"` + + TargetAPIAddrs []string `json:"target-api-addrs"` + TargetCACert string `json:"target-ca-cert"` +} + +// FullMigrationStatus reports the current status of a model +// migration, including authentication details for the remote +// controller. +type FullMigrationStatus struct { + Spec ModelMigrationSpec `json:"spec"` + Attempt int `json:"attempt"` + Phase string `json:"phase"` +} + +type PhaseResult struct { + Phase string `json:"phase"` + Error *Error +} + +type PhaseResults struct { + Results []PhaseResult `json:"Results"` +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/model.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/model.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/model.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/model.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,7 +6,7 @@ import ( "time" - "github.com/juju/juju/version" + "github.com/juju/version" ) // ModelConfigResults contains the result of client API calls @@ -27,39 +27,50 @@ Keys []string } -// ModifyModelUsers holds the parameters for making Client ShareModel calls. -type ModifyModelUsers struct { - Changes []ModifyModelUser -} - -// ModelAction is an action that can be preformed on a model. -type ModelAction string - -// Actions that can be preformed on a model. -const ( - AddModelUser ModelAction = "add" - RemoveModelUser ModelAction = "remove" -) - -// ModifyModelUser stores the parameters used for a Client.ShareModel call. -type ModifyModelUser struct { - UserTag string `json:"user-tag"` - Action ModelAction `json:"action"` -} - // SetModelAgentVersion contains the arguments for // SetModelAgentVersion client API call. type SetModelAgentVersion struct { Version version.Number } -// ModelUserInfo holds information on a user. +// ModelInfo holds information about the Juju model. +type ModelInfo struct { + // The json names for the fields below are as per the older + // field names for backward compatability. + Name string `json:"Name"` + UUID string `json:"UUID"` + ControllerUUID string `json:"ServerUUID"` + ProviderType string `json:"ProviderType"` + DefaultSeries string `json:"DefaultSeries"` + + // OwnerTag is the tag of the user that owns the model. + OwnerTag string `json:"owner-tag"` + + // Users contains information about the users that have access + // to the model. Owners and administrators can see all users + // that have access; other users can only see their own details. + Users []ModelUserInfo +} + +// ModelInfoResult holds the result of a ModelInfo call. +type ModelInfoResult struct { + Result *ModelInfo `json:"result,omitempty"` + Error *Error `json:"error,omitempty"` +} + +// ModelInfoResult holds the result of a bulk ModelInfo call. +type ModelInfoResults struct { + Results []ModelInfoResult `json:"results"` +} + +// ModelUserInfo holds information on a user who has access to a +// model. Owners of a model can see this information for all users +// who have access, so it should not include sensitive information. type ModelUserInfo struct { - UserName string `json:"user"` - DisplayName string `json:"displayname"` - CreatedBy string `json:"createdby"` - DateCreated time.Time `json:"datecreated"` - LastConnection *time.Time `json:"lastconnection"` + UserName string `json:"user"` + DisplayName string `json:"displayname"` + LastConnection *time.Time `json:"lastconnection"` + Access ModelAccessPermission `json:"access"` } // ModelUserInfoResult holds the result of an ModelUserInfo call. @@ -72,3 +83,34 @@ type ModelUserInfoResults struct { Results []ModelUserInfoResult `json:"results"` } + +// ModifyModelAccessRequest holds the parameters for making grant and revoke model calls. +type ModifyModelAccessRequest struct { + Changes []ModifyModelAccess `json:"changes"` +} + +type ModifyModelAccess struct { + UserTag string `json:"user-tag"` + Action ModelAction `json:"action"` + Access ModelAccessPermission `json:"access"` + ModelTag string `json:"model-tag"` +} + +// ModelAction is an action that can be performed on a model. +type ModelAction string + +// Actions that can be preformed on a model. +const ( + GrantModelAccess ModelAction = "grant" + RevokeModelAccess ModelAction = "revoke" +) + +// ModelAccessPermission is the type of permission that a user has to access a +// model. +type ModelAccessPermission string + +// Model access permissions that may be set on a user. +const ( + ModelReadAccess ModelAccessPermission = "read" + ModelWriteAccess ModelAccessPermission = "write" +) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/network.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/network.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/network.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/network.go 2016-04-28 06:03:34.000000000 +0000 @@ -52,6 +52,8 @@ } // Network describes a single network available on an instance. +// +// TODO(dimitern): No longer used, drop at the end of this PoC. type Network struct { // Tag is the network's tag. Tag string `json:"Tag"` @@ -69,6 +71,8 @@ // NetworkInterface describes a single network interface available on // an instance. +// +// TODO(dimitern): No longer used, drop at the end of this PoC. type NetworkInterface struct { // MACAddress is the network interface's hardware MAC address // (e.g. "aa:bb:cc:dd:ee:ff"). @@ -107,10 +111,15 @@ CIDR string `json:"CIDR"` // NetworkName is juju-internal name of the network. - // TODO(dimitern) This should be removed or adapted to the model - // once spaces are introduced. + // + // TODO(dimitern): No longer used, drop at the end of this PoC. NetworkName string `json:"NetworkName"` + // MTU is the Maximum Transmission Unit controlling the maximum size of the + // protocol packats that the interface can pass through. It is only used + // when > 0. + MTU int `json:"MTU"` + // ProviderId is a provider-specific network interface id. ProviderId string `json:"ProviderId"` @@ -118,6 +127,18 @@ // interface is attached to. ProviderSubnetId string `json:"ProviderSubnetId"` + // ProviderSpaceId is a provider-specific space id, to which the interface + // is attached to, if known and supported. + ProviderSpaceId string `json:"ProviderSpaceId"` + + // ProviderAddressId is the provider-specific id of the assigned address, if + // supported and known. + ProviderAddressId string `json:"ProviderAddressId"` + + // ProviderVLANId is the provider-specific id of the assigned address's + // VLAN, if supported and known. + ProviderVLANId string `json:"ProviderVLANId"` + // VLANTag needs to be between 1 and 4094 for VLANs and 0 for // normal networks. It's defined by IEEE 802.1Q standard. VLANTag int `json:"VLANTag"` @@ -126,6 +147,12 @@ // "eth1", even for a VLAN eth1.42 virtual interface). InterfaceName string `json:"InterfaceName"` + // ParentInterfaceName is the name of the parent interface to use, if known. + ParentInterfaceName string `json:"ParentInterfaceName"` + + // InterfaceType is the type of the interface. + InterfaceType string `json:"InterfaceType"` + // Disabled is true when the interface needs to be disabled on the // machine, e.g. not to configure it at all or stop it if running. Disabled bool `json:"Disabled"` @@ -151,6 +178,11 @@ // interface. DNSServers []string `json:"DNSServers,omitempty"` + // DNSServers contains an optional list of IP addresses and/or + // hostnames to configure as DNS servers for this network + // interface. + DNSSearchDomains []string `json:"DNSSearchDomains,omitempty"` + // Gateway address, if set, defines the default gateway to // configure for this network interface. For containers this // usually (one of) the host address(es). @@ -159,6 +191,8 @@ // ExtraConfig can contain any valid setting and its value allowed // inside an "iface" section of a interfaces(5) config file, e.g. // "up", "down", "mtu", etc. + // + // TODO(dimitern): Never used, drop at the end of this PoC. ExtraConfig map[string]string `json:"ExtraConfig,omitempty"` } @@ -366,6 +400,18 @@ return nhpm } +// UnitsNetworkConfig holds the parameters for calling Uniter.NetworkConfig() +// API. +type UnitsNetworkConfig struct { + Args []UnitNetworkConfig `json:"Args"` +} + +// UnitNetworkConfig holds a unit tag and an endpoint binding name. +type UnitNetworkConfig struct { + UnitTag string `json:"UnitTag"` + BindingName string `json:"BindingName"` +} + // MachineAddresses holds an machine tag and addresses. type MachineAddresses struct { Tag string `json:"Tag"` @@ -378,6 +424,13 @@ MachineAddresses []MachineAddresses `json:"MachineAddresses"` } +// SetMachineNetworkConfig holds the parameters for making an API call to update +// machine network config. +type SetMachineNetworkConfig struct { + Tag string `json:"Tag"` + Config []NetworkConfig `json:"Config"` +} + // MachineAddressesResult holds a list of machine addresses or an // error. type MachineAddressesResult struct { @@ -600,3 +653,22 @@ Subnets []Subnet `json:"Subnets"` Error *Error `json:"Error,omitempty"` } + +type ProxyConfig struct { + HTTP string `json:"HTTP"` + HTTPS string `json:"HTTPS"` + FTP string `json:"FTP"` + NoProxy string `json:"NoProxy"` +} + +// ProxyConfigResult contains information needed to configure a clients proxy settings +type ProxyConfigResult struct { + ProxySettings ProxyConfig `json:"ProxySettings"` + APTProxySettings ProxyConfig `json:"APTProxySettings"` + Error *Error `json:"Error,omitempty"` +} + +// ProxyConfigResults contains information needed to configure multiple clients proxy settings +type ProxyConfigResults struct { + Results []ProxyConfigResult `json:"Results"` +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/params.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/params.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/params.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/params.go 2016-04-28 06:03:34.000000000 +0000 @@ -13,6 +13,7 @@ "github.com/juju/replicaset" "github.com/juju/utils/proxy" "github.com/juju/utils/ssh" + "github.com/juju/version" "gopkg.in/juju/charm.v6-unstable" "gopkg.in/macaroon.v1" @@ -23,7 +24,6 @@ "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/storage" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) // FindTags wraps a slice of strings that are prefixes to use when @@ -119,9 +119,16 @@ Endpoints []string } +// AddCharm holds the arguments for making an AddCharm API call. +type AddCharm struct { + URL string + Channel string +} + // AddCharmWithAuthorization holds the arguments for making an AddCharmWithAuthorization API call. type AddCharmWithAuthorization struct { URL string + Channel string CharmStoreMacaroon *macaroon.Macaroon } @@ -199,6 +206,7 @@ ServiceName string Series string CharmUrl string + Channel string NumUnits int Config map[string]string ConfigYAML string // Takes precedence over config if both are present. @@ -228,6 +236,8 @@ ServiceName string `json:"servicename"` // CharmUrl is the new url for the charm. CharmUrl string `json:"charmurl"` + // Channel is the charm store channel from which the charm came. + Channel string `json:"cs-channel"` // ForceUnits forces the upgrade on units in an error state. ForceUnits bool `json:"forceunits"` // ForceSeries forces the use of the charm even if it doesn't match the @@ -810,18 +820,6 @@ Members []replicaset.Member } -// ModelInfo holds information about the Juju model. -type ModelInfo struct { - DefaultSeries string `json:"DefaultSeries"` - ProviderType string `json:"ProviderType"` - Name string `json:"Name"` - UUID string `json:"UUID"` - // The json name here is as per the older field name and is required - // for backward compatability. The other fields also have explicit - // matching serialization directives for the benefit of being explicit. - ControllerUUID string `json:"ServerUUID"` -} - // MeterStatusParam holds meter status information to be set for the specified tag. type MeterStatusParam struct { Tag string `json:"tag"` @@ -833,3 +831,14 @@ type MeterStatusParams struct { Statuses []MeterStatusParam `json:"statues"` } + +// MacaroonResults contains a set of MacaroonResults. +type MacaroonResults struct { + Results []MacaroonResult `json:"results"` +} + +// MacaroonResult contains a macaroon or an error. +type MacaroonResult struct { + Result *macaroon.Macaroon `json:"result,omitempty"` + Error *Error `json:"error,omitempty"` +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/status.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/status.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/status.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/status.go 2016-04-28 06:03:34.000000000 +0000 @@ -138,16 +138,6 @@ Err error } -// LegacyStatus holds minimal information on the status of a juju model. -type LegacyStatus struct { - Machines map[string]LegacyMachineStatus -} - -// LegacyMachineStatus holds just the instance-id of a machine. -type LegacyMachineStatus struct { - InstanceId string // Not type instance.Id just to match original api. -} - // StatusHistoryArgs holds the parameters to filter a status history query. type StatusHistoryArgs struct { Kind HistoryKind @@ -160,16 +150,6 @@ Statuses []DetailedStatus } -const ( - // DefaultMaxLogsPerEntity is the default value for logs for each entity - // that should be kept at any given time. - DefaultMaxLogsPerEntity = 100 - - // DefaultPruneInterval is the default interval that should be waited - // between prune calls. - DefaultPruneInterval = 5 * time.Minute -) - // StatusHistoryPruneArgs holds arguments for status history // prunning process. type StatusHistoryPruneArgs struct { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/systemmanager.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/systemmanager.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/systemmanager.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/systemmanager.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -// Copyright 2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package params - -// DestroyControllerArgs holds the arguments for destroying a controller. -type DestroyControllerArgs struct { - // DestroyModels specifies whether or not the hosted models - // should be destroyed as well. If this is not specified, and there are - // other hosted models, the destruction of the controller will fail. - DestroyModels bool `json:"destroy-models"` -} - -// ModelBlockInfo holds information about an model and its -// current blocks. -type ModelBlockInfo struct { - Name string `json:"name"` - UUID string `json:"model-uuid"` - OwnerTag string `json:"owner-tag"` - Blocks []string `json:"blocks"` -} - -// ModelBlockInfoList holds information about the blocked models -// for a controller. -type ModelBlockInfoList struct { - Models []ModelBlockInfo `json:"models,omitempty"` -} - -// RemoveBlocksArgs holds the arguments for the RemoveBlocks command. It is a -// struct to facilitate the easy addition of being able to remove blocks for -// individual models at a later date. -type RemoveBlocksArgs struct { - All bool `json:"all"` -} - -// ModelStatus holds information about the status of a juju model. -type ModelStatus struct { - ModelTag string `json:"model-tag"` - Life Life `json:"life"` - HostedMachineCount int `json:"hosted-machine-count"` - ServiceCount int `json:"service-count"` - OwnerTag string `json:"owner-tag"` -} - -// ModelStatusResults holds status information about a group of models. -type ModelStatusResults struct { - Results []ModelStatus `json:"models"` -} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/params/usermanager.go charm-2.2.0/src/github.com/juju/juju/apiserver/params/usermanager.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/params/usermanager.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/params/usermanager.go 2016-04-28 06:03:34.000000000 +0000 @@ -52,6 +52,9 @@ // be possible to login with a password until // registration with the secret key is completed. Password string `json:"password,omitempty"` + + // ModelAccess is the permission that the user will have to access the models. + ModelAccess ModelAccessPermission `json:"model-access-permission,omitempty"` } // AddUserResults holds the results of the bulk AddUser API call. diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/pinger.go charm-2.2.0/src/github.com/juju/juju/apiserver/pinger.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/pinger.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/pinger.go 2016-04-28 06:03:34.000000000 +0000 @@ -79,6 +79,7 @@ // loop waits for a reset signal, otherwise it performs // the initially passed action. func (pt *pingTimeout) loop() error { + // TODO(fwereade): 2016-03-17 lp:1558657 timer := time.NewTimer(pt.timeout) defer timer.Stop() for { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/pinger_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/pinger_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/pinger_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/pinger_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -187,7 +187,7 @@ s.JujuConnSuite.SetUpSuite(c) // We need to set the ping interval before the server is started in test setup. restore := gitjujutesting.PatchValue(apiserver.MongoPingInterval, coretesting.ShortWait) - s.AddSuiteCleanup(func(*gc.C) { restore() }) + s.AddCleanup(func(*gc.C) { restore() }) } func (s *mongoPingerSuite) TestAgentConnectionsShutDownWhenStateDies(c *gc.C) { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/container_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/container_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/container_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/container_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -16,6 +16,7 @@ "github.com/juju/juju/apiserver/params" "github.com/juju/juju/apiserver/provisioner" apiservertesting "github.com/juju/juju/apiserver/testing" + "github.com/juju/juju/feature" "github.com/juju/juju/instance" "github.com/juju/juju/network" "github.com/juju/juju/state" @@ -32,6 +33,7 @@ const regexpMACAddress = "([a-f0-9]{2}:){5}[a-f0-9]{2}" func (s *containerSuite) SetUpTest(c *gc.C) { + c.Skip("dimitern: test disabled as it needs fixing and/or removal with address-allocation feature flag") s.setUpTest(c, false) // Reset any "broken" dummy provider methods. s.breakEnvironMethods(c) @@ -181,7 +183,7 @@ container := s.newAPI(c, true, true) args := s.makeArgs(container) expectedError := ¶ms.Error{ - Message: `failed to allocate an address for "0/lxc/0": address allocation not supported`, + Message: `container address allocation not supported`, Code: params.CodeNotSupported, } s.assertCall(c, args, ¶ms.MachineNetworkConfigResults{ @@ -191,53 +193,6 @@ }, "") } -func (s *prepareSuite) TestErrorWithNoFeatureFlagAndBrokenAllocate(c *gc.C) { - s.breakEnvironMethods(c, "AllocateAddress") - s.SetFeatureFlags() - // Use the special "i-alloc-" prefix to force the dummy provider to allow - // AllocateAddress to run without the feature flag. - container := s.newCustomAPI(c, "i-alloc-me", true, false) - args := s.makeArgs(container) - expectedError := ¶ms.Error{ - Message: `failed to allocate an address for "0/lxc/0": dummy.AllocateAddress is broken`, - } - s.assertCall(c, args, ¶ms.MachineNetworkConfigResults{ - Results: []params.MachineNetworkConfigResult{ - {Error: expectedError}, - }, - }, "") -} - -func (s *prepareSuite) TestErrorWithNoFeatureFlagAllocateSuccess(c *gc.C) { - s.SetFeatureFlags() - s.breakEnvironMethods(c) - // Use the special "i-alloc-" prefix to force the dummy provider to allow - // AllocateAddress to run without the feature flag, which simulates a MAAS - // 1.8+ environment where without the flag we still try calling - // AllocateAddress for the device we created for the container. - container := s.newCustomAPI(c, "i-alloc-me", true, false) - args := s.makeArgs(container) - _, testLog := s.assertCall(c, args, s.makeResults([]params.NetworkConfig{{ - DeviceIndex: 0, - NetworkName: "juju-private", - ProviderId: "dummy-eth0", - InterfaceName: "eth0", - DNSServers: []string{"ns1.dummy", "ns2.dummy"}, - GatewayAddress: "0.10.0.1", - ConfigType: "static", - MACAddress: "regex:" + regexpMACAddress, - Address: "regex:0.10.0.[0-9]{1,3}", // we don't care about the actual value. - }}), "") - - c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{ - loggo.INFO, - `allocated address ".+" on instance "i-alloc-me" for container "juju-machine-0-lxc-0"`, - }, { - loggo.INFO, - `assigned address ".+" to container "0/lxc/0"`, - }}) -} - func (s *prepareSuite) TestErrorWithNonProvisionedHost(c *gc.C) { container := s.newAPI(c, false, true) args := s.makeArgs(container) @@ -386,6 +341,8 @@ } func (s *prepareSuite) TestRetryingOnAllocateAddressFailure(c *gc.C) { + s.SetFeatureFlags(feature.AddressAllocation) + // This test verifies the retrying logic when AllocateAddress // and/or setAddrState return errors. @@ -575,6 +532,7 @@ CIDR: "0.10.0.0/24", DeviceIndex: 0, InterfaceName: "eth0", + InterfaceType: "ethernet", VLANTag: 0, MACAddress: "regex:" + regexpMACAddress, Disabled: false, @@ -657,6 +615,7 @@ CIDR: "0.10.0.0/24", DeviceIndex: 0, InterfaceName: "eth0", + InterfaceType: "ethernet", VLANTag: 0, MACAddress: "regex:" + regexpMACAddress, Disabled: false, @@ -694,6 +653,7 @@ CIDR: "0.20.0.0/24", DeviceIndex: 1, InterfaceName: "eth1", + InterfaceType: "ethernet", VLANTag: 1, MACAddress: "regex:" + regexpMACAddress, Disabled: false, diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/machineerror.go charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/machineerror.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/machineerror.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/machineerror.go 2016-04-28 06:03:34.000000000 +0000 @@ -70,6 +70,7 @@ select { case <-w.tomb.Dying(): return tomb.ErrDying + // TODO(fwereade): 2016-03-17 lp:1558657 case <-time.After(ErrorRetryWaitDelay): out = w.out case out <- struct{}{}: diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/provisioner.go charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/provisioner.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/provisioner.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/provisioner.go 2016-04-28 06:03:34.000000000 +0000 @@ -13,6 +13,7 @@ "github.com/juju/utils/set" "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/common/networkingcommon" "github.com/juju/juju/apiserver/common/storagecommon" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/constraints" @@ -246,6 +247,11 @@ logger.Debugf("using default MTU %v for all LXC containers NICs", lxcDefaultMTU) cfg[container.ConfigLXCDefaultMTU] = fmt.Sprintf("%d", lxcDefaultMTU) } + case instance.LXD: + // TODO(jam): DefaultMTU needs to be handled here + // TODO(jam): Do we want to handle ImageStream here, or do we + // hide it from them? (all cached images must come from the + // same image stream?) } if !environs.AddressAllocationEnabled() { @@ -300,6 +306,7 @@ result.SSLHostnameVerification = config.SSLHostnameVerification() result.Proxy = config.ProxySettings() result.AptProxy = config.AptProxySettings() + result.AptMirror = config.AptMirror() result.PreferIPv6 = config.PreferIPv6() result.AllowLXCLoopMounts, _ = config.AllowLXCLoopMounts() @@ -487,39 +494,6 @@ return result, nil } -func networkParamsToStateParams(networks []params.Network, ifaces []params.NetworkInterface) ( - []state.NetworkInfo, []state.NetworkInterfaceInfo, error, -) { - stateNetworks := make([]state.NetworkInfo, len(networks)) - for i, net := range networks { - tag, err := names.ParseNetworkTag(net.Tag) - if err != nil { - return nil, nil, err - } - stateNetworks[i] = state.NetworkInfo{ - Name: tag.Id(), - ProviderId: network.Id(net.ProviderId), - CIDR: net.CIDR, - VLANTag: net.VLANTag, - } - } - stateInterfaces := make([]state.NetworkInterfaceInfo, len(ifaces)) - for i, iface := range ifaces { - tag, err := names.ParseNetworkTag(iface.NetworkTag) - if err != nil { - return nil, nil, err - } - stateInterfaces[i] = state.NetworkInterfaceInfo{ - MACAddress: iface.MACAddress, - NetworkName: tag.Id(), - InterfaceName: iface.InterfaceName, - IsVirtual: iface.IsVirtual, - Disabled: iface.Disabled, - } - } - return stateNetworks, stateInterfaces, nil -} - // RequestedNetworks returns the requested networks for each given // machine entity. Each entry in both lists is returned with its // provider specific id. @@ -576,10 +550,6 @@ if err != nil { return err } - networks, interfaces, err := networkParamsToStateParams(arg.Networks, arg.Interfaces) - if err != nil { - return err - } volumes, err := storagecommon.VolumesToState(arg.Volumes) if err != nil { return err @@ -588,14 +558,16 @@ if err != nil { return err } - if err = machine.SetInstanceInfo( + + devicesArgs, devicesAddrs := networkingcommon.NetworkConfigsToStateArgs(arg.NetworkConfig) + + err = machine.SetInstanceInfo( arg.InstanceId, arg.Nonce, arg.Characteristics, - networks, interfaces, volumes, volumeAttachments); err != nil { - return errors.Annotatef( - err, - "cannot record provisioning info for %q", - arg.InstanceId, - ) + devicesArgs, devicesAddrs, + volumes, volumeAttachments, + ) + if err != nil { + return errors.Annotatef(err, "cannot record provisioning info for %q", arg.InstanceId) } return nil } @@ -696,6 +668,10 @@ // is not enabled, it returns a NotSupported error. func (p *ProvisionerAPI) PrepareContainerInterfaceInfo(args params.Entities) ( params.MachineNetworkConfigResults, error) { + if environs.AddressAllocationEnabled() { + logger.Warningf("address allocation enabled - using legacyPrepareOrGetContainerInterfaceInfo(true)") + return p.legacyPrepareOrGetContainerInterfaceInfo(args, true) + } return p.prepareOrGetContainerInterfaceInfo(args, true) } @@ -704,6 +680,10 @@ // allocation feature flag is not enabled, it returns a NotSupported error. func (p *ProvisionerAPI) GetContainerInterfaceInfo(args params.Entities) ( params.MachineNetworkConfigResults, error) { + if environs.AddressAllocationEnabled() { + logger.Warningf("address allocation enabled - using legacyPrepareOrGetContainerInterfaceInfo(false)") + return p.legacyPrepareOrGetContainerInterfaceInfo(args, false) + } return p.prepareOrGetContainerInterfaceInfo(args, false) } @@ -725,9 +705,143 @@ return fmt.Sprintf(MACAddressTemplate, digits...) } -// prepareOrGetContainerInterfaceInfo optionally allocates an address and returns information -// for configuring networking on a container. It accepts container tags as arguments. -func (p *ProvisionerAPI) prepareOrGetContainerInterfaceInfo( +func (p *ProvisionerAPI) prepareOrGetContainerInterfaceInfo(args params.Entities, maintain bool) (params.MachineNetworkConfigResults, error) { + result := params.MachineNetworkConfigResults{ + Results: make([]params.MachineNetworkConfigResult, len(args.Entities)), + } + + netEnviron, hostMachine, canAccess, err := p.prepareContainerAccessEnvironment() + if err != nil { + return result, errors.Trace(err) + } + instId, err := hostMachine.InstanceId() + if errors.IsNotProvisioned(err) { + err = errors.NotProvisionedf("cannot prepare container network config: host machine %q", hostMachine) + return result, err + } else if err != nil { + return result, errors.Trace(err) + } + + for i, entity := range args.Entities { + tag, err := names.ParseMachineTag(entity.Tag) + if err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } + // The auth function (canAccess) checks that the machine is a + // top level machine (we filter those out next) or that the + // machine has the host as a parent. + container, err := p.getMachine(canAccess, tag) + if err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } else if !container.IsContainer() { + err = errors.Errorf("cannot prepare network config for %q: not a container", tag) + result.Results[i].Error = common.ServerError(err) + continue + } else if ciid, cerr := container.InstanceId(); maintain == true && cerr == nil { + // Since we want to configure and create NICs on the + // container before it starts, it must also be not + // provisioned yet. + err = errors.Errorf("container %q already provisioned as %q", container, ciid) + result.Results[i].Error = common.ServerError(err) + continue + } else if cerr != nil && !errors.IsNotProvisioned(cerr) { + // Any other error needs to be reported. + result.Results[i].Error = common.ServerError(cerr) + continue + } + + if err := hostMachine.SetContainerLinkLayerDevices(container); err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } + + containerDevices, err := container.AllLinkLayerDevices() + if err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } + + preparedInfo := make([]network.InterfaceInfo, len(containerDevices)) + preparedOK := true + for j, device := range containerDevices { + parentDevice, err := device.ParentDevice() + if err != nil || parentDevice == nil { + err = errors.Errorf( + "cannot get parent %q of container device %q: %v", + device.ParentName(), device.Name(), err, + ) + result.Results[i].Error = common.ServerError(err) + preparedOK = false + break + } + parentAddrs, err := parentDevice.Addresses() + if err != nil { + result.Results[i].Error = common.ServerError(err) + preparedOK = false + break + } + if len(parentAddrs) == 0 { + err = errors.Errorf("host machine device %q has no addresses", parentDevice.Name()) + result.Results[i].Error = common.ServerError(err) + preparedOK = false + break + } + firstAddress := parentAddrs[0] + parentDeviceSubnet, err := firstAddress.Subnet() + if err != nil || parentDeviceSubnet == nil { + err = errors.Errorf( + "cannot get subnet %q used by address %q of host machine device %q: %v", + firstAddress.SubnetID(), firstAddress.Value(), parentDevice.Name(), err, + ) + result.Results[i].Error = common.ServerError(err) + preparedOK = false + break + } + + info := network.InterfaceInfo{ + InterfaceName: device.Name(), + MACAddress: device.MACAddress(), + ConfigType: network.ConfigStatic, + InterfaceType: network.InterfaceType(device.Type()), + NoAutoStart: !device.IsAutoStart(), + Disabled: !device.IsUp(), + MTU: int(device.MTU()), + CIDR: parentDeviceSubnet.CIDR(), + ProviderSubnetId: parentDeviceSubnet.ProviderId(), + VLANTag: parentDeviceSubnet.VLANTag(), + ParentInterfaceName: parentDevice.Name(), + } + logger.Tracef("prepared info for container interface %q: %+v", info.InterfaceName, info) + preparedOK = true + preparedInfo[j] = info + } + + if !preparedOK { + // Error result is already set. + continue + } + + allocatedInfo, err := netEnviron.AllocateContainerAddresses(instId, preparedInfo) + if err != nil { + result.Results[i].Error = common.ServerError(err) + continue + } + logger.Debugf("got allocated info from provider: %+v", allocatedInfo) + + allocatedConfig := networkingcommon.NetworkConfigFromInterfaceInfo(allocatedInfo) + sortedAllocatedConfig := networkingcommon.SortNetworkConfigsByInterfaceName(allocatedConfig) + logger.Tracef("allocated sorted network config: %+v", sortedAllocatedConfig) + result.Results[i].Config = sortedAllocatedConfig + } + return result, nil +} + +// legacyPrepareOrGetContainerInterfaceInfo optionally allocates an address and +// returns information for configuring networking on a container. It accepts +// container tags as arguments. +func (p *ProvisionerAPI) legacyPrepareOrGetContainerInterfaceInfo( args params.Entities, provisionContainer bool, ) ( @@ -848,6 +962,11 @@ macAddress = interfaceInfo.MACAddress } + interfaceType := string(interfaceInfo.InterfaceType) + if interfaceType == "" { + interfaceType = string(network.EthernetInterface) + } + // TODO(dimitern): Support allocating one address per NIC on // the host, effectively creating the same number of NICs in // the container. @@ -860,6 +979,7 @@ ProviderId: string(interfaceInfo.ProviderId), ProviderSubnetId: string(subnetInfo.ProviderId), VLANTag: interfaceInfo.VLANTag, + InterfaceType: interfaceType, InterfaceName: interfaceInfo.InterfaceName, Disabled: interfaceInfo.Disabled, NoAutoStart: interfaceInfo.NoAutoStart, @@ -874,27 +994,10 @@ return result, nil } -func (p *ProvisionerAPI) maybeGetNetworkingEnviron() (environs.NetworkingEnviron, error) { - cfg, err := p.st.ModelConfig() - if err != nil { - return nil, errors.Annotate(err, "failed to get model config") - } - environ, err := environs.New(cfg) - if err != nil { - return nil, errors.Annotate(err, "failed to construct a model from config") - } - netEnviron, supported := environs.SupportsNetworking(environ) - if !supported { - // " not supported" will be appended to the message below. - return nil, errors.NotSupportedf("model %q networking", cfg.Name()) - } - return netEnviron, nil -} - // prepareContainerAccessEnvironment retrieves the environment, host machine, and access // for working with containers. func (p *ProvisionerAPI) prepareContainerAccessEnvironment() (environs.NetworkingEnviron, *state.Machine, common.AuthFunc, error) { - netEnviron, err := p.maybeGetNetworkingEnviron() + netEnviron, err := networkingcommon.NetworkingEnvironFromModelConfig(p.st) if err != nil { return nil, nil, nil, errors.Trace(err) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/provisioner_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/provisioner_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/provisioner/provisioner_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/provisioner/provisioner_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -965,43 +965,6 @@ gotHardware, err := s.machines[1].HardwareCharacteristics() c.Assert(err, jc.ErrorIsNil) c.Check(gotHardware, gc.DeepEquals, &hwChars) - ifacesMachine1, err := s.machines[1].NetworkInterfaces() - c.Assert(err, jc.ErrorIsNil) - c.Assert(ifacesMachine1, gc.HasLen, 4) - actual := make([]params.NetworkInterface, len(ifacesMachine1)) - for i, iface := range ifacesMachine1 { - actual[i].InterfaceName = iface.InterfaceName() - actual[i].NetworkTag = iface.NetworkTag().String() - actual[i].MACAddress = iface.MACAddress() - actual[i].IsVirtual = iface.IsVirtual() - actual[i].Disabled = iface.IsDisabled() - c.Check(iface.MachineId(), gc.Equals, s.machines[1].Id()) - c.Check(iface.MachineTag(), gc.Equals, s.machines[1].Tag()) - } - c.Assert(actual, jc.SameContents, ifaces[:4]) - ifacesMachine2, err := s.machines[2].NetworkInterfaces() - c.Assert(err, jc.ErrorIsNil) - c.Assert(ifacesMachine2, gc.HasLen, 1) - c.Assert(ifacesMachine2[0].InterfaceName(), gc.Equals, ifaces[5].InterfaceName) - c.Assert(ifacesMachine2[0].MACAddress(), gc.Equals, ifaces[5].MACAddress) - c.Assert(ifacesMachine2[0].NetworkTag().String(), gc.Equals, ifaces[5].NetworkTag) - c.Assert(ifacesMachine2[0].MachineId(), gc.Equals, s.machines[2].Id()) - for i := range networks { - if i == 3 { - // Last one was ignored, so don't check. - break - } - tag, err := names.ParseNetworkTag(networks[i].Tag) - c.Assert(err, jc.ErrorIsNil) - networkName := tag.Id() - nw, err := s.State.Network(networkName) - c.Assert(err, jc.ErrorIsNil) - c.Check(nw.Name(), gc.Equals, networkName) - c.Check(nw.ProviderId(), gc.Equals, network.Id(networks[i].ProviderId)) - c.Check(nw.Tag().String(), gc.Equals, networks[i].Tag) - c.Check(nw.VLANTag(), gc.Equals, networks[i].VLANTag) - c.Check(nw.CIDR(), gc.Equals, networks[i].CIDR) - } // Verify the machine with requested volumes was provisioned, and the // volume information recorded in state. @@ -1131,6 +1094,7 @@ attrs := map[string]interface{}{ "http-proxy": "http://proxy.example.com:9000", "allow-lxc-loop-mounts": true, + "apt-mirror": "http://example.mirror.com", } err := s.State.UpdateModelConfig(attrs, nil, nil) c.Assert(err, jc.ErrorIsNil) @@ -1146,7 +1110,8 @@ c.Check(results.SSLHostnameVerification, jc.IsTrue) c.Check(results.Proxy, gc.DeepEquals, expectedProxy) c.Check(results.AptProxy, gc.DeepEquals, expectedProxy) - c.Check(results.PreferIPv6, jc.IsTrue) + c.Check(results.AptMirror, gc.DeepEquals, "http://example.mirror.com") + c.Check(results.PreferIPv6, jc.IsFalse) c.Check(results.AllowLXCLoopMounts, jc.IsTrue) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/model.go charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/model.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/model.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/model.go 2016-04-28 06:03:34.000000000 +0000 @@ -8,18 +8,7 @@ "github.com/juju/juju/state" ) -func init() { - common.RegisterStandardFacade("ProxyUpdater", 1, NewProxyUpdaterAPI) -} - -// ProxyUpdaterAPI implements the API used by the proxy updater worker. -type ProxyUpdaterAPI struct { - *common.ModelWatcher -} - -// NewProxyUpdaterAPI creates a new instance of the ProxyUpdater API. -func NewProxyUpdaterAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer) (*ProxyUpdaterAPI, error) { - return &ProxyUpdaterAPI{ - ModelWatcher: common.NewModelWatcher(st, resources, authorizer), - }, nil +// NewAPI creates a new API server-side facade with a state.State backing. +func NewAPI(st *state.State, res *common.Resources, auth common.Authorizer) (*ProxyUpdaterAPI, error) { + return NewAPIWithBacking(&stateShim{st: st}, res, auth) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/model_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/model_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/model_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/model_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package proxyupdater_test - -import ( - jc "github.com/juju/testing/checkers" - gc "gopkg.in/check.v1" - - "github.com/juju/juju/apiserver/agent" - "github.com/juju/juju/apiserver/common" - commontesting "github.com/juju/juju/apiserver/common/testing" - apiservertesting "github.com/juju/juju/apiserver/testing" - "github.com/juju/juju/juju/testing" - "github.com/juju/juju/state" -) - -type ProxyUpdaterSuite struct { - testing.JujuConnSuite - *commontesting.ModelWatcherTest - - authorizer apiservertesting.FakeAuthorizer - resources *common.Resources - - machine0 *state.Machine - api *agent.AgentAPIV2 -} - -var _ = gc.Suite(&ProxyUpdaterSuite{}) - -func (s *ProxyUpdaterSuite) SetUpTest(c *gc.C) { - s.JujuConnSuite.SetUpTest(c) - - var err error - s.machine0, err = s.State.AddMachine("quantal", state.JobHostUnits, state.JobManageModel) - c.Assert(err, jc.ErrorIsNil) - - s.authorizer = apiservertesting.FakeAuthorizer{ - Tag: s.machine0.Tag(), - } - s.resources = common.NewResources() - s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() }) - - s.api, err = agent.NewAgentAPIV2( - s.State, - s.resources, - s.authorizer, - ) - c.Assert(err, jc.ErrorIsNil) - s.ModelWatcherTest = commontesting.NewModelWatcherTest( - s.api, s.State, s.resources, commontesting.NoSecrets) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/package_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,11 +4,11 @@ package proxyupdater_test import ( - stdtesting "testing" + "testing" - "github.com/juju/juju/testing" + gc "gopkg.in/check.v1" ) -func TestAll(t *stdtesting.T) { - testing.MgoTestPackage(t) +func TestPackage(t *testing.T) { + gc.TestingT(t) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater.go charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,165 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package proxyupdater + +import ( + "strings" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/environs/config" + "github.com/juju/juju/network" + "github.com/juju/juju/state" + "github.com/juju/juju/state/watcher" + "github.com/juju/names" + "github.com/juju/utils/proxy" + "github.com/juju/utils/set" +) + +// Backend defines the state methods this facade needs, so they can be +// mocked for testing. +type Backend interface { + ModelConfig() (*config.Config, error) + APIHostPorts() ([][]network.HostPort, error) + WatchAPIHostPorts() state.NotifyWatcher + WatchForModelConfigChanges() state.NotifyWatcher +} + +type ProxyUpdaterAPI struct { + backend Backend + resources *common.Resources + authorizer common.Authorizer +} + +// NewAPIWithBacking creates a new server-side API facade with the given Backing. +func NewAPIWithBacking(st Backend, resources *common.Resources, authorizer common.Authorizer) (*ProxyUpdaterAPI, error) { + if !(authorizer.AuthMachineAgent() || authorizer.AuthUnitAgent()) { + return &ProxyUpdaterAPI{}, common.ErrPerm + } + return &ProxyUpdaterAPI{ + backend: st, + resources: resources, + authorizer: authorizer, + }, nil +} + +func (api *ProxyUpdaterAPI) oneWatch() params.NotifyWatchResult { + var result params.NotifyWatchResult + + watch := common.NewMultiNotifyWatcher( + api.backend.WatchForModelConfigChanges(), + api.backend.WatchAPIHostPorts()) + + if _, ok := <-watch.Changes(); ok { + result = params.NotifyWatchResult{ + NotifyWatcherId: api.resources.Register(watch), + } + } + result.Error = common.ServerError(watcher.EnsureErr(watch)) + return result +} + +// WatchChanges watches for cleanups to be perfomed in state +func (api *ProxyUpdaterAPI) WatchForProxyConfigAndAPIHostPortChanges(args params.Entities) params.NotifyWatchResults { + results := params.NotifyWatchResults{ + Results: make([]params.NotifyWatchResult, len(args.Entities)), + } + errors, _ := api.authEntities(args) + + for i := range args.Entities { + if errors.Results[i].Error == nil { + results.Results[i] = api.oneWatch() + } + results.Results[i].Error = errors.Results[i].Error + } + + return results +} + +func proxyUtilsSettingsToProxySettingsParam(settings proxy.Settings) params.ProxyConfig { + return params.ProxyConfig{ + HTTP: settings.Http, + HTTPS: settings.Https, + FTP: settings.Ftp, + NoProxy: settings.NoProxy, + } +} + +func (api *ProxyUpdaterAPI) authEntities(args params.Entities) (params.ErrorResults, bool) { + result := params.ErrorResults{ + Results: make([]params.ErrorResult, len(args.Entities)), + } + + var ok bool + + for i, entity := range args.Entities { + tag, err := names.ParseTag(entity.Tag) + if err != nil { + result.Results[i].Error = common.ServerError(common.ErrPerm) + continue + } + err = common.ErrPerm + if !api.authorizer.AuthOwner(tag) { + result.Results[i].Error = common.ServerError(err) + continue + } + ok = true + } + return result, ok +} + +func (api *ProxyUpdaterAPI) proxyConfig() params.ProxyConfigResult { + var result params.ProxyConfigResult + env, err := api.backend.ModelConfig() + if err != nil { + result.Error = common.ServerError(err) + return result + } + + apiHostPorts, err := api.backend.APIHostPorts() + if err != nil { + result.Error = common.ServerError(err) + return result + } + + result.ProxySettings = proxyUtilsSettingsToProxySettingsParam(env.ProxySettings()) + result.APTProxySettings = proxyUtilsSettingsToProxySettingsParam(env.AptProxySettings()) + + var noProxy []string + if result.ProxySettings.NoProxy != "" { + noProxy = strings.Split(result.ProxySettings.NoProxy, ",") + } + + noProxySet := set.NewStrings(noProxy...) + for _, host := range apiHostPorts { + for _, hp := range host { + noProxySet.Add(hp.Address.Value) + } + } + result.ProxySettings.NoProxy = strings.Join(noProxySet.SortedValues(), ",") + + return result +} + +// ProxyConfig returns the proxy settings for the current environment +func (api *ProxyUpdaterAPI) ProxyConfig(args params.Entities) params.ProxyConfigResults { + var result params.ProxyConfigResult + errors, ok := api.authEntities(args) + + if ok { + result = api.proxyConfig() + } + + results := params.ProxyConfigResults{ + Results: make([]params.ProxyConfigResult, len(args.Entities)), + } + for i := range args.Entities { + if errors.Results[i].Error == nil { + results.Results[i] = result + } + results.Results[i].Error = errors.Results[i].Error + } + + return results +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/proxyupdater_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,213 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package proxyupdater_test + +import ( + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/apiserver/proxyupdater" + apiservertesting "github.com/juju/juju/apiserver/testing" + "github.com/juju/juju/environs/config" + "github.com/juju/juju/network" + "github.com/juju/juju/state" + statetesting "github.com/juju/juju/state/testing" + coretesting "github.com/juju/juju/testing" + "github.com/juju/juju/worker/workertest" + "github.com/juju/testing" +) + +type ProxyUpdaterSuite struct { + coretesting.BaseSuite + apiservertesting.StubNetwork + + state *stubBackend + resources *common.Resources + authorizer apiservertesting.FakeAuthorizer + facade *proxyupdater.ProxyUpdaterAPI + tag names.MachineTag +} + +var _ = gc.Suite(&ProxyUpdaterSuite{}) + +func (s *ProxyUpdaterSuite) SetUpSuite(c *gc.C) { + s.BaseSuite.SetUpSuite(c) + s.StubNetwork.SetUpSuite(c) +} + +func (s *ProxyUpdaterSuite) SetUpTest(c *gc.C) { + s.BaseSuite.SetUpTest(c) + s.resources = common.NewResources() + s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() }) + s.authorizer = apiservertesting.FakeAuthorizer{ + Tag: names.NewMachineTag("1"), + EnvironManager: false, + } + s.tag = names.NewMachineTag("1") + s.state = &stubBackend{} + s.state.SetUp(c) + s.AddCleanup(func(_ *gc.C) { s.state.Kill() }) + + var err error + s.facade, err = proxyupdater.NewAPIWithBacking(s.state, s.resources, s.authorizer) + c.Assert(err, jc.ErrorIsNil) + c.Assert(s.facade, gc.NotNil) + + // Shouldn't have any calls yet + apiservertesting.CheckMethodCalls(c, s.state.Stub) +} + +func (s *ProxyUpdaterSuite) TestWatchForProxyConfigAndAPIHostPortChanges(c *gc.C) { + // WatchForProxyConfigAndAPIHostPortChanges combines WatchForModelConfigChanges + // and WatchAPIHostPorts. Check that they are both called and we get the + s.facade.WatchForProxyConfigAndAPIHostPortChanges(s.oneEntity()) + + // Verify the watcher resource was registered. + c.Assert(s.resources.Count(), gc.Equals, 1) + resource := s.resources.Get("1") + defer statetesting.AssertStop(c, resource) + + s.state.Stub.CheckCallNames(c, + "WatchForModelConfigChanges", + "WatchAPIHostPorts", + ) +} + +func (s *ProxyUpdaterSuite) oneEntity() params.Entities { + entities := params.Entities{ + make([]params.Entity, 1), + } + entities.Entities[0].Tag = s.tag.String() + return entities +} + +func (s *ProxyUpdaterSuite) TestProxyConfig(c *gc.C) { + // Check that the ProxyConfig combines data from ModelConfig and APIHostPorts + cfg := s.facade.ProxyConfig(s.oneEntity()) + + s.state.Stub.CheckCallNames(c, + "ModelConfig", + "APIHostPorts", + ) + + noProxy := "0.1.2.3,0.1.2.4,0.1.2.5" + + r := params.ProxyConfigResult{ + ProxySettings: params.ProxyConfig{ + HTTP: "http proxy", HTTPS: "https proxy", FTP: "", NoProxy: noProxy}, + APTProxySettings: params.ProxyConfig{ + HTTP: "http://http proxy", HTTPS: "https://https proxy", FTP: "", NoProxy: ""}, + } + c.Assert(cfg.Results[0], jc.DeepEquals, r) +} + +func (s *ProxyUpdaterSuite) TestProxyConfigExtendsExisting(c *gc.C) { + // Check that the ProxyConfig combines data from ModelConfig and APIHostPorts + s.state.SetModelConfig(coretesting.Attrs{ + "http-proxy": "http proxy", + "https-proxy": "https proxy", + "no-proxy": "9.9.9.9", + }) + cfg := s.facade.ProxyConfig(s.oneEntity()) + s.state.Stub.CheckCallNames(c, + "ModelConfig", + "APIHostPorts", + ) + + expectedNoProxy := "0.1.2.3,0.1.2.4,0.1.2.5,9.9.9.9" + + c.Assert(cfg.Results[0], jc.DeepEquals, params.ProxyConfigResult{ + ProxySettings: params.ProxyConfig{ + HTTP: "http proxy", HTTPS: "https proxy", FTP: "", NoProxy: expectedNoProxy}, + APTProxySettings: params.ProxyConfig{ + HTTP: "http://http proxy", HTTPS: "https://https proxy", FTP: "", NoProxy: ""}, + }) +} + +func (s *ProxyUpdaterSuite) TestProxyConfigNoDuplicates(c *gc.C) { + // Check that the ProxyConfig combines data from ModelConfig and APIHostPorts + s.state.SetModelConfig(coretesting.Attrs{ + "http-proxy": "http proxy", + "https-proxy": "https proxy", + "no-proxy": "0.1.2.3", + }) + cfg := s.facade.ProxyConfig(s.oneEntity()) + s.state.Stub.CheckCallNames(c, + "ModelConfig", + "APIHostPorts", + ) + + expectedNoProxy := "0.1.2.3,0.1.2.4,0.1.2.5" + + c.Assert(cfg.Results[0], jc.DeepEquals, params.ProxyConfigResult{ + ProxySettings: params.ProxyConfig{ + HTTP: "http proxy", HTTPS: "https proxy", FTP: "", NoProxy: expectedNoProxy}, + APTProxySettings: params.ProxyConfig{ + HTTP: "http://http proxy", HTTPS: "https://https proxy", FTP: "", NoProxy: ""}, + }) +} + +type stubBackend struct { + *testing.Stub + + EnvConfig *config.Config + c *gc.C + configAttrs coretesting.Attrs + hpWatcher workertest.NotAWatcher + confWatcher workertest.NotAWatcher +} + +func (sb *stubBackend) SetUp(c *gc.C) { + sb.Stub = &testing.Stub{} + sb.c = c + sb.configAttrs = coretesting.Attrs{ + "http-proxy": "http proxy", + "https-proxy": "https proxy", + } + sb.hpWatcher = workertest.NewFakeWatcher(2, 2) + sb.confWatcher = workertest.NewFakeWatcher(2, 2) +} + +func (sb *stubBackend) Kill() { + sb.hpWatcher.Kill() + sb.confWatcher.Kill() +} + +func (sb *stubBackend) SetModelConfig(ca coretesting.Attrs) { + sb.configAttrs = ca +} + +func (sb *stubBackend) ModelConfig() (*config.Config, error) { + sb.MethodCall(sb, "ModelConfig") + if err := sb.NextErr(); err != nil { + return nil, err + } + return coretesting.CustomModelConfig(sb.c, sb.configAttrs), nil +} + +func (sb *stubBackend) APIHostPorts() ([][]network.HostPort, error) { + sb.MethodCall(sb, "APIHostPorts") + if err := sb.NextErr(); err != nil { + return nil, err + } + hps := [][]network.HostPort{ + network.NewHostPorts(1234, "0.1.2.3"), + network.NewHostPorts(1234, "0.1.2.4"), + network.NewHostPorts(1234, "0.1.2.5"), + } + return hps, nil +} + +func (sb *stubBackend) WatchAPIHostPorts() state.NotifyWatcher { + sb.MethodCall(sb, "WatchAPIHostPorts") + return sb.hpWatcher +} + +func (sb *stubBackend) WatchForModelConfigChanges() state.NotifyWatcher { + sb.MethodCall(sb, "WatchForModelConfigChanges") + return sb.confWatcher +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/shims.go charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/shims.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/proxyupdater/shims.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/proxyupdater/shims.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,36 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package proxyupdater + +import ( + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/environs/config" + "github.com/juju/juju/network" + "github.com/juju/juju/state" +) + +func init() { + common.RegisterStandardFacade("ProxyUpdater", 1, NewAPI) +} + +// stateShim forwards and adapts state.State methods to Backend +type stateShim struct { + st *state.State +} + +func (s *stateShim) ModelConfig() (*config.Config, error) { + return s.st.ModelConfig() +} + +func (s *stateShim) APIHostPorts() ([][]network.HostPort, error) { + return s.st.APIHostPorts() +} + +func (s *stateShim) WatchAPIHostPorts() state.NotifyWatcher { + return s.st.WatchAPIHostPorts() +} + +func (s *stateShim) WatchForModelConfigChanges() state.NotifyWatcher { + return s.st.WatchForModelConfigChanges() +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/read_only_calls.go charm-2.2.0/src/github.com/juju/juju/apiserver/read_only_calls.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/read_only_calls.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/read_only_calls.go 2016-04-28 06:03:34.000000000 +0000 @@ -45,6 +45,7 @@ "Client.WatchAll", // TODO: add controller work. "KeyManager.ListKeys", + "ModelManager.ModelInfo", "Service.GetConstraints", "Service.CharmRelations", "Service.Get", diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/resource.go charm-2.2.0/src/github.com/juju/juju/apiserver/resource.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/resource.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/resource.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -// TODO(ericsnow) Remove this file once we add a registration mechanism. - -package apiserver - -import ( - "net/http" - - "github.com/juju/errors" - "github.com/juju/names" - - "github.com/juju/juju/resource/api/server" - "github.com/juju/juju/resource/resourceadapters" - "github.com/juju/juju/state" -) - -type resourcesHandlerDeps struct { - httpCtxt httpContext -} - -// ConnectForUser connects to state for an API user. -func (deps resourcesHandlerDeps) ConnectForUser(req *http.Request) (*state.State, state.Entity, error) { - return deps.httpCtxt.stateForRequestAuthenticatedUser(req) -} - -// ConnectForUnitAgent connects to state for a unit agent. -func (deps resourcesHandlerDeps) ConnectForUnitAgent(req *http.Request) (*state.State, *state.Unit, error) { - st, ent, err := deps.httpCtxt.stateForRequestAuthenticatedAgent(req) - if err != nil { - return nil, nil, errors.Trace(err) - } - - unit, ok := ent.(*state.Unit) - if !ok { - logger.Errorf("unexpected type: %T", ent) - return nil, nil, errors.Errorf("unexpected type: %T", ent) - } - return st, unit, nil -} - -// TODO(ericsnow) Move these functions to resourceadapters? - -func newResourceHandler(httpCtxt httpContext) http.Handler { - deps := resourcesHandlerDeps{httpCtxt} - return server.NewResourceHandler( - func(req *http.Request) (server.DataStore, names.Tag, error) { - st, entity, err := deps.ConnectForUser(req) - if err != nil { - return nil, nil, errors.Trace(err) - } - resources, err := st.Resources() - if err != nil { - return nil, nil, errors.Trace(err) - } - ds := resourceadapters.DataStore{ - Resources: resources, - State: st, - } - return ds, entity.Tag(), nil - }, - ) -} - -func newUnitResourceHandler(httpCtxt httpContext) http.Handler { - extractor := resourceadapters.HTTPDownloadRequestExtractor{ - Connector: &resourcesHandlerDeps{httpCtxt}, - } - deps := server.NewUnitResourceHandlerDeps(extractor) - return server.NewUnitResourceHandler(deps) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/restricted_root.go charm-2.2.0/src/github.com/juju/juju/apiserver/restricted_root.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/restricted_root.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/restricted_root.go 2016-04-28 06:03:34.000000000 +0000 @@ -28,6 +28,7 @@ var restrictedRootNames = set.NewStrings( "AllModelWatcher", "Controller", + "MigrationTarget", "ModelManager", "UserManager", ) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/restricted_root_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/restricted_root_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/restricted_root_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/restricted_root_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -50,7 +50,7 @@ } func (r *restrictedRootSuite) TestFindDisallowedMethod(c *gc.C) { - caller, err := r.root.FindMethod("Client", 1, "Status") + caller, err := r.root.FindMethod("Client", 1, "FullStatus") c.Assert(err, gc.ErrorMatches, `logged in to server, no model, "Client" not supported`) c.Assert(errors.IsNotSupported(err), jc.IsTrue) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/root.go charm-2.2.0/src/github.com/juju/juju/apiserver/root.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/root.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/root.go 2016-04-28 06:03:34.000000000 +0000 @@ -74,6 +74,11 @@ if err := r.resources.RegisterNamed("logDir", common.StringResource(srv.logDir)); err != nil { return nil, errors.Trace(err) } + if err := r.resources.RegisterNamed("createLocalLoginMacaroon", common.ValueResource{ + srv.authCtxt.userAuth.CreateLocalLoginMacaroon, + }); err != nil { + return nil, errors.Trace(err) + } return r, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/server_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/server_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/server_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/server_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -25,6 +25,7 @@ "gopkg.in/macaroon-bakery.v1/httpbakery" "github.com/juju/juju/api" + apimachiner "github.com/juju/juju/api/machiner" "github.com/juju/juju/apiserver" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/cert" @@ -72,13 +73,13 @@ c.Assert(err, jc.ErrorIsNil) defer st.Close() - _, err = st.Machiner().Machine(machine.MachineTag()) + _, err = apimachiner.NewState(st).Machine(machine.MachineTag()) c.Assert(err, jc.ErrorIsNil) err = srv.Stop() c.Assert(err, jc.ErrorIsNil) - _, err = st.Machiner().Machine(machine.MachineTag()) + _, err = apimachiner.NewState(st).Machine(machine.MachineTag()) err = errors.Cause(err) // The client has not necessarily seen the server shutdown yet, // so there are two possible errors. @@ -123,7 +124,7 @@ network.NewHostPorts(port, "127.0.0.1"), }) - _, err = ipv4State.Machiner().Machine(machine.MachineTag()) + _, err = apimachiner.NewState(ipv4State).Machine(machine.MachineTag()) c.Assert(err, jc.ErrorIsNil) apiInfo.Addrs = []string{net.JoinHostPort("::1", portString)} @@ -135,7 +136,7 @@ network.NewHostPorts(port, "::1"), }) - _, err = ipv6State.Machiner().Machine(machine.MachineTag()) + _, err = apimachiner.NewState(ipv6State).Machine(machine.MachineTag()) c.Assert(err, jc.ErrorIsNil) } @@ -377,7 +378,7 @@ ms, err := client.DischargeAll(m) c.Assert(err, jc.ErrorIsNil) - err = bsvc.Check(ms, checkers.New()) + err = bsvc.(*bakery.Service).Check(ms, checkers.New()) c.Assert(err, gc.IsNil) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/service/charmstore.go charm-2.2.0/src/github.com/juju/juju/apiserver/service/charmstore.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/service/charmstore.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/service/charmstore.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,25 +11,28 @@ "github.com/juju/errors" "github.com/juju/utils" + "github.com/juju/version" "gopkg.in/juju/charm.v6-unstable" "gopkg.in/juju/charmrepo.v2-unstable" "gopkg.in/juju/charmrepo.v2-unstable/csclient" + csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "gopkg.in/macaroon-bakery.v1/httpbakery" "gopkg.in/macaroon.v1" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/environs/config" "github.com/juju/juju/state" + jujuversion "github.com/juju/juju/version" ) // TODO - we really want to avoid this, which we can do by refactoring code requiring this // to use interfaces. -// NewCharmStore instantiates a new charm store repository. -// It is defined at top level for testing purposes. -var NewCharmStore = newCharmStore +// NewCharmStoreRepo instantiates a new charm store repository. +// It is exported for testing purposes. +var NewCharmStoreRepo = newCharmStoreFromClient -func newCharmStore(p charmrepo.NewCharmStoreParams) charmrepo.Interface { - return charmrepo.NewCharmStore(p) +func newCharmStoreFromClient(csClient *csclient.Client) charmrepo.Interface { + return charmrepo.NewCharmStoreFromClient(csClient) } // AddCharmWithAuthorization adds the given charm URL (which must include revision) to @@ -60,30 +63,18 @@ return nil } - // Get the charm and its information from the store. - envConfig, err := st.ModelConfig() + // Open a charm store client. + repo, err := openCSRepo(args) if err != nil { return err } - csURL, err := url.Parse(csclient.ServerURL) + envConfig, err := st.ModelConfig() if err != nil { return err } - csParams := charmrepo.NewCharmStoreParams{ - URL: csURL.String(), - HTTPClient: httpbakery.NewHTTPClient(), - } - if args.CharmStoreMacaroon != nil { - // Set the provided charmstore authorizing macaroon - // as a cookie in the HTTP client. - // TODO discharge any third party caveats in the macaroon. - ms := []*macaroon.Macaroon{args.CharmStoreMacaroon} - httpbakery.SetCookie(csParams.HTTPClient.Jar, csURL, ms) - } - repo := config.SpecializeCharmRepo( - NewCharmStore(csParams), - envConfig, - ) + repo = config.SpecializeCharmRepo(repo, envConfig).(*charmrepo.CharmStore) + + // Get the charm and its information from the store. downloadedCharm, err := repo.Get(charmURL) if err != nil { cause := errors.Cause(err) @@ -93,6 +84,10 @@ return errors.Trace(err) } + if err := checkMinVersion(downloadedCharm); err != nil { + return errors.Trace(err) + } + // Open it and calculate the SHA256 hash. downloadedBundle, ok := downloadedCharm.(*charm.CharmArchive) if !ok { @@ -111,30 +106,117 @@ return errors.Annotate(err, "cannot rewind charm archive") } + ca := CharmArchive{ + ID: charmURL, + Charm: downloadedCharm, + Data: archive, + Size: size, + SHA256: bundleSHA256, + } + if args.CharmStoreMacaroon != nil { + ca.Macaroon = macaroon.Slice{args.CharmStoreMacaroon} + } + // Store the charm archive in environment storage. - return StoreCharmArchive( - st, - charmURL, - downloadedCharm, - archive, - size, - bundleSHA256, - ) + return StoreCharmArchive(st, ca) +} + +func openCSRepo(args params.AddCharmWithAuthorization) (charmrepo.Interface, error) { + csClient, err := openCSClient(args) + if err != nil { + return nil, err + } + repo := NewCharmStoreRepo(csClient) + return repo, nil +} + +func openCSClient(args params.AddCharmWithAuthorization) (*csclient.Client, error) { + csURL, err := url.Parse(csclient.ServerURL) + if err != nil { + return nil, err + } + csParams := csclient.Params{ + URL: csURL.String(), + HTTPClient: httpbakery.NewHTTPClient(), + } + + if args.CharmStoreMacaroon != nil { + // Set the provided charmstore authorizing macaroon + // as a cookie in the HTTP client. + // TODO(cmars) discharge any third party caveats in the macaroon. + ms := []*macaroon.Macaroon{args.CharmStoreMacaroon} + httpbakery.SetCookie(csParams.HTTPClient.Jar, csURL, ms) + } + csClient := csclient.New(csParams) + channel := csparams.Channel(args.Channel) + if channel != csparams.NoChannel { + csClient = csClient.WithChannel(channel) + } + return csClient, nil +} + +func checkMinVersion(ch charm.Charm) error { + minver := ch.Meta().MinJujuVersion + if minver != version.Zero && minver.Compare(jujuversion.Current) > 0 { + return minVersionError(minver, jujuversion.Current) + } + return nil +} + +type minJujuVersionErr struct { + *errors.Err +} + +func minVersionError(minver, jujuver version.Number) error { + err := errors.NewErr("charm's min version (%s) is higher than this juju environment's version (%s)", + minver, jujuver) + err.SetLocation(1) + return minJujuVersionErr{&err} +} + +// CharmArchive is the data that needs to be stored for a charm archive in +// state. +type CharmArchive struct { + // ID is the charm URL for which we're storing the archive. + ID *charm.URL + + // Charm is the metadata about the charm for the archive. + Charm charm.Charm + + // Data contains the bytes of the archive. + Data io.Reader + + // Size is the number of bytes in Data. + Size int64 + + // SHA256 is the hash of the bytes in Data. + SHA256 string + + // Macaroon is the authorization macaroon for accessing the charmstore. + Macaroon macaroon.Slice } // StoreCharmArchive stores a charm archive in environment storage. -func StoreCharmArchive(st *state.State, curl *charm.URL, ch charm.Charm, r io.Reader, size int64, sha256 string) error { +func StoreCharmArchive(st *state.State, archive CharmArchive) error { storage := newStateStorage(st.ModelUUID(), st.MongoSession()) - storagePath, err := charmArchiveStoragePath(curl) + storagePath, err := charmArchiveStoragePath(archive.ID) if err != nil { return errors.Annotate(err, "cannot generate charm archive name") } - if err := storage.Put(storagePath, r, size); err != nil { + if err := storage.Put(storagePath, archive.Data, archive.Size); err != nil { return errors.Annotate(err, "cannot add charm to storage") } + info := state.CharmInfo{ + Charm: archive.Charm, + ID: archive.ID, + StoragePath: storagePath, + SHA256: archive.SHA256, + Macaroon: archive.Macaroon, + } + // Now update the charm data in state and mark it as no longer pending. - _, err = st.UpdateUploadedCharm(ch, curl, storagePath, sha256) + _, err = st.UpdateUploadedCharm(info) if err != nil { alreadyUploaded := err == state.ErrCharmRevisionAlreadyModified || errors.Cause(err) == state.ErrCharmRevisionAlreadyModified || @@ -176,7 +258,7 @@ return params.ResolveCharmResults{}, err } repo := config.SpecializeCharmRepo( - NewCharmStore(charmrepo.NewCharmStoreParams{}), + NewCharmStoreRepo(csclient.New(csclient.Params{})), envConfig) for _, ref := range args.References { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/service/export_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/service/export_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/service/export_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/service/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -3,7 +3,14 @@ package service +import "github.com/juju/errors" + var ( ParseSettingsCompatible = parseSettingsCompatible NewStateStorage = &newStateStorage ) + +func IsMinJujuVersionError(err error) bool { + _, ok := errors.Cause(err).(minJujuVersionErr) + return ok +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/service/service.go charm-2.2.0/src/github.com/juju/juju/apiserver/service/service.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/service/service.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/service/service.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,6 +11,7 @@ "github.com/juju/loggo" "github.com/juju/names" "gopkg.in/juju/charm.v6-unstable" + csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" goyaml "gopkg.in/yaml.v2" "github.com/juju/juju/apiserver/common" @@ -125,6 +126,7 @@ // Try to find the charm URL in state first. ch, err := st.Charm(curl) + // TODO(wallyworld) - remove for 2.0 beta4 if errors.IsNotFound(err) { // Clients written to expect 1.16 compatibility require this next block. if curl.Schema != "cs" { @@ -140,6 +142,10 @@ return errors.Trace(err) } + if err := checkMinVersion(ch); err != nil { + return errors.Trace(err) + } + var settings charm.Settings if len(args.ConfigYAML) > 0 { settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) @@ -156,6 +162,8 @@ return errors.Trace(err) } + channel := csparams.Channel(args.Channel) + _, err = jjj.DeployService(st, jjj.DeployServiceParams{ ServiceName: args.ServiceName, @@ -163,6 +171,7 @@ // TODO(dfc) ServiceOwner should be a tag ServiceOwner: owner, Charm: ch, + Channel: channel, NumUnits: args.NumUnits, ConfigSettings: settings, Constraints: args.Constraints, @@ -250,7 +259,10 @@ } // Set the charm for the given service. if args.CharmUrl != "" { - if err = api.serviceSetCharm(svc, args.CharmUrl, args.ForceSeries, args.ForceCharmUrl, nil); err != nil { + // For now we do not support changing the channel through Update(). + // TODO(ericsnow) Support it? + channel := svc.Channel() + if err = api.serviceSetCharm(svc, args.CharmUrl, channel, args.ForceSeries, args.ForceCharmUrl, nil); err != nil { return errors.Trace(err) } } @@ -289,11 +301,12 @@ if err != nil { return errors.Trace(err) } - return api.serviceSetCharm(service, args.CharmUrl, args.ForceSeries, args.ForceUnits, args.ResourceIDs) + channel := csparams.Channel(args.Channel) + return api.serviceSetCharm(service, args.CharmUrl, channel, args.ForceSeries, args.ForceUnits, args.ResourceIDs) } // serviceSetCharm sets the charm for the given service. -func (api *API) serviceSetCharm(service *state.Service, url string, forceSeries, forceUnits bool, resourceIDs map[string]string) error { +func (api *API) serviceSetCharm(service *state.Service, url string, channel csparams.Channel, forceSeries, forceUnits bool, resourceIDs map[string]string) error { curl, err := charm.ParseURL(url) if err != nil { return errors.Trace(err) @@ -304,6 +317,7 @@ } cfg := state.SetCharmConfig{ Charm: sch, + Channel: channel, ForceSeries: forceSeries, ForceUnits: forceUnits, ResourceIDs: resourceIDs, diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/service/service_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/service/service_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/service/service_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/service/service_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,6 +6,7 @@ import ( "fmt" "io" + "regexp" "sync" "github.com/juju/errors" @@ -16,6 +17,7 @@ "gopkg.in/juju/charm.v6-unstable" "gopkg.in/juju/charmrepo.v2-unstable" "gopkg.in/juju/charmrepo.v2-unstable/csclient" + csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" "gopkg.in/macaroon.v1" "gopkg.in/mgo.v2" @@ -35,6 +37,7 @@ "github.com/juju/juju/storage/provider/registry" "github.com/juju/juju/testcharms" "github.com/juju/juju/testing/factory" + jujuversion "github.com/juju/juju/version" ) type serviceSuite struct { @@ -63,11 +66,10 @@ func (s *serviceSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) - s.BlockHelper = commontesting.NewBlockHelper(s.APIState) - s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) - s.CharmStoreSuite.Session = s.JujuConnSuite.Session s.CharmStoreSuite.SetUpTest(c) + s.BlockHelper = commontesting.NewBlockHelper(s.APIState) + s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) s.service = s.Factory.MakeService(c, nil) @@ -197,6 +199,10 @@ func (s *serviceSuite) TestServiceDeployWithStorage(c *gc.C) { setupStoragePool(c, s.State) curl, ch := s.UploadCharm(c, "utopic/storage-block-10", "storage-block") + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) + c.Assert(err, jc.ErrorIsNil) storageConstraints := map[string]storage.Constraints{ "data": { Count: 1, @@ -237,9 +243,22 @@ }) } +func (s *serviceSuite) TestMinJujuVersionTooHigh(c *gc.C) { + curl, _ := s.UploadCharm(c, "quantal/minjujuversion-0", "minjujuversion") + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) + match := fmt.Sprintf(`charm's min version (999.999.999) is higher than this juju environment's version (%s)`, jujuversion.Current) + c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(match)) +} + func (s *serviceSuite) TestServiceDeployWithInvalidStoragePool(c *gc.C) { setupStoragePool(c, s.State) curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block") + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) + c.Assert(err, jc.ErrorIsNil) storageConstraints := map[string]storage.Constraints{ "data": storage.Constraints{ Pool: "foo", @@ -271,6 +290,10 @@ c.Assert(err, jc.ErrorIsNil) curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block") + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) + c.Assert(err, jc.ErrorIsNil) storageConstraints := map[string]storage.Constraints{ "data": storage.Constraints{ Pool: "host-loop-pool", @@ -299,6 +322,10 @@ func (s *serviceSuite) TestServiceDeployDefaultFilesystemStorage(c *gc.C) { setupStoragePool(c, s.State) curl, ch := s.UploadCharm(c, "trusty/storage-filesystem-1", "storage-filesystem") + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) + c.Assert(err, jc.ErrorIsNil) var cons constraints.Value args := params.ServiceDeploy{ ServiceName: "service", @@ -327,7 +354,9 @@ func (s *serviceSuite) TestServiceDeploy(c *gc.C) { curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) var cons constraints.Value args := params.ServiceDeploy{ @@ -354,7 +383,9 @@ func (s *serviceSuite) TestServiceDeployWithInvalidPlacement(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) var cons constraints.Value args := params.ServiceDeploy{ @@ -377,6 +408,10 @@ func (s *serviceSuite) testClientServicesDeployWithBindings(c *gc.C, endpointBindings, expected map[string]string) { curl, _ := s.UploadCharm(c, "utopic/riak-42", "riak") + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) + c.Assert(err, jc.ErrorIsNil) var cons constraints.Value args := params.ServiceDeploy{ @@ -436,11 +471,11 @@ client := s.APIState.Client() // First test the sanity checks. - err := client.AddCharm(&charm.URL{Name: "nonsense"}) + err := client.AddCharm(&charm.URL{Name: "nonsense"}, csparams.StableChannel) c.Assert(err, gc.ErrorMatches, `cannot parse charm or bundle URL: ":nonsense-0"`) - err = client.AddCharm(charm.MustParseURL("local:precise/dummy")) + err = client.AddCharm(charm.MustParseURL("local:precise/dummy"), csparams.StableChannel) c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema") - err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress")) + err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"), csparams.StableChannel) c.Assert(err, gc.ErrorMatches, "charm URL must include revision") // Add a charm, without uploading it to storage, to @@ -448,18 +483,24 @@ charmDir := testcharms.Repo.CharmDir("dummy") ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision()) curl := charm.MustParseURL("cs:quantal/" + ident) - sch, err := s.State.AddCharm(charmDir, curl, "", ident+"-sha256") + info := state.CharmInfo{ + Charm: charmDir, + ID: curl, + StoragePath: "", + SHA256: ident + "-sha256", + } + sch, err := s.State.AddCharm(info) c.Assert(err, jc.ErrorIsNil) // AddCharm should see the charm in state and not upload it. - err = client.AddCharm(sch.URL()) + err = client.AddCharm(sch.URL(), csparams.StableChannel) c.Assert(err, jc.ErrorIsNil) c.Assert(blobs.m, gc.HasLen, 0) // Now try adding another charm completely. curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") - err = client.AddCharm(curl) + err = client.AddCharm(curl, csparams.StableChannel) c.Assert(err, jc.ErrorIsNil) // Verify it's in state and it got uploaded. @@ -481,7 +522,7 @@ // Try to add a charm to the environment without authorization. s.DischargeUser = "" - err = s.APIState.Client().AddCharm(curl) + err = s.APIState.Client().AddCharm(curl, csparams.StableChannel) c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: cannot get discharge from "https://.*": third party refused discharge: cannot discharge: discharge denied \(unauthorized access\)`) tryAs := func(user string) error { @@ -493,7 +534,10 @@ err = client.Get("/delegatable-macaroon", &m) c.Assert(err, gc.IsNil) - return service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + return service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + Channel: string(csparams.StableChannel), + }) } // Try again with authorization for the wrong user. err = tryAs("joe") @@ -533,7 +577,7 @@ go func(index int) { defer wg.Done() - c.Assert(client.AddCharm(curl), gc.IsNil, gc.Commentf("goroutine %d", index)) + c.Assert(client.AddCharm(curl, csparams.StableChannel), gc.IsNil, gc.Commentf("goroutine %d", index)) sch, err := s.State.Charm(curl) c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index)) c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index)) @@ -582,7 +626,7 @@ // Now try to add the charm, which will convert the placeholder to // a pending charm. - err = client.AddCharm(curl) + err = client.AddCharm(curl, csparams.StableChannel) c.Assert(err, jc.ErrorIsNil) // Make sure the document's flags were reset as expected. @@ -603,7 +647,9 @@ func (s *serviceSuite) TestServiceSetCharm(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -615,7 +661,9 @@ c.Assert(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) err = s.serviceApi.SetCharm(params.ServiceSetCharm{ ServiceName: "service", @@ -634,7 +682,9 @@ func (s *serviceSuite) setupServiceSetCharm(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -646,7 +696,9 @@ c.Assert(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) } @@ -693,7 +745,9 @@ func (s *serviceSuite) TestServiceSetCharmForceUnits(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -705,7 +759,9 @@ c.Assert(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) err = s.serviceApi.SetCharm(params.ServiceSetCharm{ ServiceName: "service", @@ -761,7 +817,9 @@ func (s *serviceSuite) TestServiceSetCharmLegacy(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -772,7 +830,9 @@ c.Assert(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) curl, _ = s.UploadCharm(c, "trusty/dummy-1", "dummy") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) // Even with forceSeries = true, we can't change a charm where @@ -787,7 +847,9 @@ func (s *serviceSuite) TestServiceSetCharmUnsupportedSeries(c *gc.C) { curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -799,7 +861,9 @@ c.Assert(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series2") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) err = s.serviceApi.SetCharm(params.ServiceSetCharm{ @@ -809,9 +873,11 @@ c.Assert(err, gc.ErrorMatches, "cannot upgrade charm, only these series are supported: trusty, wily") } -func (s *serviceSuite) TestServiceSetCharmUnsupportedSeriesForce(c *gc.C) { +func (s *serviceSuite) assertServiceSetCharmSeries(c *gc.C, upgradeCharm, series string) { curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -822,8 +888,15 @@ c.Assert(err, jc.ErrorIsNil) c.Assert(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) - curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series2", "multi-series2") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + + url := upgradeCharm + if series != "" { + url = series + "/" + upgradeCharm + } + curl, _ = s.UploadCharmMultiSeries(c, "~who/"+url, upgradeCharm) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) err = s.serviceApi.SetCharm(params.ServiceSetCharm{ @@ -836,12 +909,22 @@ c.Assert(err, jc.ErrorIsNil) ch, _, err := svc.Charm() c.Assert(err, jc.ErrorIsNil) - c.Assert(ch.URL().String(), gc.Equals, "cs:~who/multi-series2-0") + c.Assert(ch.URL().String(), gc.Equals, "cs:~who/"+url+"-0") +} + +func (s *serviceSuite) TestServiceSetCharmUnsupportedSeriesForce(c *gc.C) { + s.assertServiceSetCharmSeries(c, "multi-series2", "") +} + +func (s *serviceSuite) TestServiceSetCharmNoExplicitSupportedSeries(c *gc.C) { + s.assertServiceSetCharmSeries(c, "dummy", "precise") } func (s *serviceSuite) TestServiceSetCharmWrongOS(c *gc.C) { curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -853,7 +936,9 @@ c.Assert(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series-windows", "multi-series-windows") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) err = s.serviceApi.SetCharm(params.ServiceSetCharm{ @@ -877,9 +962,10 @@ func (s *serviceSuite) TestSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) { repo := &testModeCharmRepo{} - s.PatchValue(&service.NewCharmStore, func(p charmrepo.NewCharmStoreParams) charmrepo.Interface { - p.URL = s.Srv.URL - repo.CharmStore = charmrepo.NewCharmStore(p) + s.PatchValue(&csclient.ServerURL, s.Srv.URL) + newCharmStoreRepo := service.NewCharmStoreRepo + s.PatchValue(&service.NewCharmStoreRepo, func(c *csclient.Client) charmrepo.Interface { + repo.CharmStore = newCharmStoreRepo(c).(*charmrepo.CharmStore) return repo }) attrs := map[string]interface{}{"test-mode": true} @@ -888,7 +974,9 @@ // Check that the store's test mode is enabled when calling service Deploy. curl, _ := s.UploadCharm(c, "trusty/dummy-1", "dummy") - err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -911,14 +999,16 @@ // Check that the store's test mode is enabled when calling AddCharm. curl, _ = s.UploadCharm(c, "utopic/riak-42", "riak") - err = s.APIState.Client().AddCharm(curl) + err = s.APIState.Client().AddCharm(curl, csparams.StableChannel) c.Assert(err, jc.ErrorIsNil) c.Assert(repo.testMode, jc.IsTrue) } func (s *serviceSuite) setupServiceDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) { curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) cons := constraints.MustParse(args) return curl, ch, cons @@ -969,7 +1059,9 @@ func (s *serviceSuite) TestServiceDeploySubordinate(c *gc.C) { curl, ch := s.UploadCharm(c, "utopic/logging-47", "logging") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -996,7 +1088,9 @@ func (s *serviceSuite) TestServiceDeployConfig(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -1020,7 +1114,9 @@ // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. // Can't be done cleanly until it's extracted similarly to Machiner. curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -1038,7 +1134,9 @@ func (s *serviceSuite) TestServiceDeployToMachine(c *gc.C) { curl, ch := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) machine, err := s.State.AddMachine("precise", state.JobHostUnits) @@ -1094,7 +1192,9 @@ func (s *serviceSuite) TestServiceDeployServiceOwner(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ @@ -1114,7 +1214,9 @@ func (s *serviceSuite) deployServiceForUpdateTests(c *gc.C) { curl, _ := s.UploadCharm(c, "precise/dummy-1", "dummy") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) results, err := s.serviceApi.Deploy(params.ServicesDeploy{ Services: []params.ServiceDeploy{{ @@ -1130,7 +1232,9 @@ func (s *serviceSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmUrl bool) { s.deployServiceForUpdateTests(c) curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) // Update the charm for the service. @@ -1168,7 +1272,9 @@ func (s *serviceSuite) setupServiceUpdate(c *gc.C) string { s.deployServiceForUpdateTests(c) curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) return curl.String() } @@ -1337,7 +1443,9 @@ func (s *serviceSuite) TestServiceUpdateAllParams(c *gc.C) { s.deployServiceForUpdateTests(c) curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") - err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) + err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ + URL: curl.String(), + }) c.Assert(err, jc.ErrorIsNil) // Update all the service attributes. diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/facade.go charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/facade.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/facade.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/facade.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,83 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler + +import ( + "github.com/juju/errors" + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" + "github.com/juju/juju/state/watcher" + "github.com/juju/names" +) + +// Backend exposes functionality required by Facade. +type Backend interface { + + // WatchScaledServices returns a watcher that sends service ids + // that might not have enough units. + WatchScaledServices() state.StringsWatcher + + // RescaleService ensures that the named service has at least its + // configured minimum unit count. + RescaleService(name string) error +} + +// Facade allows model-manager clients to watch and rescale services. +type Facade struct { + backend Backend + resources *common.Resources +} + +// NewFacade creates a new authorized Facade. +func NewFacade(backend Backend, res *common.Resources, auth common.Authorizer) (*Facade, error) { + if !auth.AuthModelManager() { + return nil, common.ErrPerm + } + return &Facade{ + backend: backend, + resources: res, + }, nil +} + +// Watch returns a watcher that sends the names of services whose +// unit count may be below their configured minimum. +func (facade *Facade) Watch() (params.StringsWatchResult, error) { + watch := facade.backend.WatchScaledServices() + if changes, ok := <-watch.Changes(); ok { + id := facade.resources.Register(watch) + return params.StringsWatchResult{ + StringsWatcherId: id, + Changes: changes, + }, nil + } + return params.StringsWatchResult{}, watcher.EnsureErr(watch) +} + +// Rescale causes any supplied services to be scaled up to their +// minimum size. +func (facade *Facade) Rescale(args params.Entities) params.ErrorResults { + result := params.ErrorResults{ + Results: make([]params.ErrorResult, len(args.Entities)), + } + for i, entity := range args.Entities { + err := facade.rescaleOne(entity.Tag) + result.Results[i].Error = common.ServerError(err) + } + return result +} + +// rescaleOne scales up the supplied service, if necessary; or returns a +// suitable error. +func (facade *Facade) rescaleOne(tagString string) error { + tag, err := names.ParseTag(tagString) + if err != nil { + return errors.Trace(err) + } + serviceTag, ok := tag.(names.ServiceTag) + if !ok { + return common.ErrPerm + } + return facade.backend.RescaleService(serviceTag.Id()) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/facade_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/facade_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/facade_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/facade_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,102 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler_test + +import ( + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/apiserver/servicescaler" +) + +type FacadeSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&FacadeSuite{}) + +func (s *FacadeSuite) TestModelManager(c *gc.C) { + facade, err := servicescaler.NewFacade(nil, nil, auth(true)) + c.Check(err, jc.ErrorIsNil) + c.Check(facade, gc.NotNil) +} + +func (s *FacadeSuite) TestNotModelManager(c *gc.C) { + facade, err := servicescaler.NewFacade(nil, nil, auth(false)) + c.Check(err, gc.Equals, common.ErrPerm) + c.Check(facade, gc.IsNil) +} + +func (s *FacadeSuite) TestWatchError(c *gc.C) { + fix := newWatchFixture(c, false) + result, err := fix.Facade.Watch() + c.Check(err, gc.ErrorMatches, "blammo") + c.Check(result, gc.DeepEquals, params.StringsWatchResult{}) + c.Check(fix.Resources.Count(), gc.Equals, 0) +} + +func (s *FacadeSuite) TestWatchSuccess(c *gc.C) { + fix := newWatchFixture(c, true) + result, err := fix.Facade.Watch() + c.Check(err, jc.ErrorIsNil) + c.Check(result.Changes, jc.DeepEquals, []string{"pow", "zap", "kerblooie"}) + c.Check(fix.Resources.Count(), gc.Equals, 1) + resource := fix.Resources.Get(result.StringsWatcherId) + c.Check(resource, gc.NotNil) +} + +func (s *FacadeSuite) TestRescaleNonsense(c *gc.C) { + fix := newRescaleFixture(c) + result := fix.Facade.Rescale(entities("burble plink")) + c.Assert(result.Results, gc.HasLen, 1) + err := result.Results[0].Error + c.Check(err, gc.ErrorMatches, `"burble plink" is not a valid tag`) +} + +func (s *FacadeSuite) TestRescaleUnauthorized(c *gc.C) { + fix := newRescaleFixture(c) + result := fix.Facade.Rescale(entities("unit-foo-27")) + c.Assert(result.Results, gc.HasLen, 1) + err := result.Results[0].Error + c.Check(err, gc.ErrorMatches, "permission denied") + c.Check(err, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (s *FacadeSuite) TestRescaleNotFound(c *gc.C) { + fix := newRescaleFixture(c) + result := fix.Facade.Rescale(entities("service-missing")) + c.Assert(result.Results, gc.HasLen, 1) + err := result.Results[0].Error + c.Check(err, gc.ErrorMatches, "service not found") + c.Check(err, jc.Satisfies, params.IsCodeNotFound) +} + +func (s *FacadeSuite) TestRescaleError(c *gc.C) { + fix := newRescaleFixture(c) + result := fix.Facade.Rescale(entities("service-error")) + c.Assert(result.Results, gc.HasLen, 1) + err := result.Results[0].Error + c.Check(err, gc.ErrorMatches, "blammo") +} + +func (s *FacadeSuite) TestRescaleSuccess(c *gc.C) { + fix := newRescaleFixture(c) + result := fix.Facade.Rescale(entities("service-expected")) + c.Assert(result.Results, gc.HasLen, 1) + err := result.Results[0].Error + c.Check(err, gc.IsNil) +} + +func (s *FacadeSuite) TestRescaleMultiple(c *gc.C) { + fix := newRescaleFixture(c) + result := fix.Facade.Rescale(entities("service-error", "service-expected")) + c.Assert(result.Results, gc.HasLen, 2) + err0 := result.Results[0].Error + c.Check(err0, gc.ErrorMatches, "blammo") + err1 := result.Results[1].Error + c.Check(err1, gc.IsNil) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/package_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/package_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/shim.go charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/shim.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/shim.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/shim.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,50 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler + +import ( + "github.com/juju/errors" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/state" +) + +// This file contains untested shims to let us wrap state in a sensible +// interface and avoid writing tests that depend on mongodb. If you were +// to change any part of it so that it were no longer *obviously* and +// *trivially* correct, you would be Doing It Wrong. + +func init() { + common.RegisterStandardFacade("ServiceScaler", 1, newFacade) +} + +// newFacade wraps the supplied *state.State for the use of the Facade. +func newFacade(st *state.State, res *common.Resources, auth common.Authorizer) (*Facade, error) { + return NewFacade(backendShim{st}, res, auth) +} + +// backendShim wraps a *State to implement Backend without pulling in direct +// mongodb dependencies. It would be awesome if we were to put this in state +// and test it properly there, where we have no choice but to test against +// mongodb anyway, but that's relatively low priority... +// +// ...so long as it stays simple, and the full functionality remains tested +// elsewhere. +type backendShim struct { + st *state.State +} + +// WatchScaledServices is part of the Backend interface. +func (shim backendShim) WatchScaledServices() state.StringsWatcher { + return shim.st.WatchMinUnits() +} + +// RescaleService is part of the Backend interface. +func (shim backendShim) RescaleService(name string) error { + service, err := shim.st.Service(name) + if err != nil { + return errors.Trace(err) + } + return service.EnsureMinUnits() +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/util_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/util_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/servicescaler/util_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/servicescaler/util_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,112 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package servicescaler_test + +import ( + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/apiserver/servicescaler" + "github.com/juju/juju/state" +) + +// mockAuth implements common.Authorizer for the tests' convenience. +type mockAuth struct { + common.Authorizer + modelManager bool +} + +func (mock mockAuth) AuthModelManager() bool { + return mock.modelManager +} + +// auth is a convenience constructor for a mockAuth. +func auth(modelManager bool) common.Authorizer { + return mockAuth{modelManager: modelManager} +} + +// mockWatcher implements state.StringsWatcher for the tests' convenience. +type mockWatcher struct { + state.StringsWatcher + working bool +} + +func (mock *mockWatcher) Changes() <-chan []string { + ch := make(chan []string, 1) + if mock.working { + ch <- []string{"pow", "zap", "kerblooie"} + } else { + close(ch) + } + return ch +} + +func (mock *mockWatcher) Err() error { + return errors.New("blammo") +} + +// watchBackend implements servicescaler.Backend for the convenience of +// the tests for the Watch method. +type watchBackend struct { + servicescaler.Backend + working bool +} + +func (backend *watchBackend) WatchScaledServices() state.StringsWatcher { + return &mockWatcher{working: backend.working} +} + +// watchFixture collects components needed to test the Watch method. +type watchFixture struct { + Facade *servicescaler.Facade + Resources *common.Resources +} + +func newWatchFixture(c *gc.C, working bool) *watchFixture { + backend := &watchBackend{working: working} + resources := common.NewResources() + facade, err := servicescaler.NewFacade(backend, resources, auth(true)) + c.Assert(err, jc.ErrorIsNil) + return &watchFixture{facade, resources} +} + +// rescaleBackend implements servicescaler.Backend for the convenience of +// the tests for the Rescale method. +type rescaleBackend struct { + servicescaler.Backend +} + +func (rescaleBackend) RescaleService(name string) error { + switch name { + case "expected": + return nil + case "missing": + return errors.NotFoundf("service") + default: + return errors.New("blammo") + } +} + +// rescaleFixture collects components needed to test the Rescale method. +type rescaleFixture struct { + Facade *servicescaler.Facade +} + +func newRescaleFixture(c *gc.C) *rescaleFixture { + facade, err := servicescaler.NewFacade(rescaleBackend{}, nil, auth(true)) + c.Assert(err, jc.ErrorIsNil) + return &rescaleFixture{facade} +} + +// entities is a convenience constructor for params.Entities. +func entities(tags ...string) params.Entities { + entities := params.Entities{Entities: make([]params.Entity, len(tags))} + for i, tag := range tags { + entities.Entities[i].Tag = tag + } + return entities +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/storageprovisioner/storageprovisioner_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/storageprovisioner/storageprovisioner_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/storageprovisioner/storageprovisioner_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/storageprovisioner/storageprovisioner_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -51,7 +51,7 @@ registry.RegisterEnvironStorageProviders( "dummy", "environscoped", "machinescoped", ) - s.AddSuiteCleanup(func(c *gc.C) { + s.AddCleanup(func(c *gc.C) { registry.RegisterProvider("environscoped", nil) registry.RegisterProvider("machinescoped", nil) }) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/testing/fakecharmstore.go charm-2.2.0/src/github.com/juju/juju/apiserver/testing/fakecharmstore.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/testing/fakecharmstore.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/testing/fakecharmstore.go 2016-04-28 06:03:34.000000000 +0000 @@ -19,7 +19,6 @@ "gopkg.in/macaroon-bakery.v1/bakerytest" "gopkg.in/mgo.v2" - "github.com/juju/juju/apiserver/service" "github.com/juju/juju/testcharms" ) @@ -56,7 +55,7 @@ IdentityLocation: s.discharger.Location(), PublicKeyLocator: s.discharger, } - handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4) + handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5) c.Assert(err, jc.ErrorIsNil) s.handler = handler s.Srv = httptest.NewServer(handler) @@ -67,10 +66,7 @@ }) s.PatchValue(&charmrepo.CacheDir, c.MkDir()) - s.PatchValue(&service.NewCharmStore, func(p charmrepo.NewCharmStoreParams) charmrepo.Interface { - p.URL = s.Srv.URL - return charmrepo.NewCharmStore(p) - }) + s.PatchValue(&csclient.ServerURL, s.Srv.URL) } func (s *CharmStoreSuite) TearDownTest(c *gc.C) { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/testing/fakenotifywatcher.go charm-2.2.0/src/github.com/juju/juju/apiserver/testing/fakenotifywatcher.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/testing/fakenotifywatcher.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/testing/fakenotifywatcher.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,47 @@ +// Copyright 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package testing + +import ( + "launchpad.net/tomb" + + "github.com/juju/juju/state" +) + +// FakeNotifyWatcher is an implementation of state.NotifyWatcher which +// is useful in tests. +type FakeNotifyWatcher struct { + tomb tomb.Tomb + C chan struct{} +} + +var _ state.NotifyWatcher = (*FakeNotifyWatcher)(nil) + +func NewFakeNotifyWatcher() *FakeNotifyWatcher { + return &FakeNotifyWatcher{ + C: make(chan struct{}, 1), + } +} + +func (w *FakeNotifyWatcher) Stop() error { + w.Kill() + return w.Wait() +} + +func (w *FakeNotifyWatcher) Kill() { + w.tomb.Kill(nil) + w.tomb.Done() +} + +func (w *FakeNotifyWatcher) Wait() error { + return w.tomb.Wait() +} + +func (w *FakeNotifyWatcher) Err() error { + return w.tomb.Err() +} + +func (w *FakeNotifyWatcher) Changes() <-chan struct{} { + return w.C +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/tools.go charm-2.2.0/src/github.com/juju/juju/apiserver/tools.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/tools.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/tools.go 2016-04-28 06:03:34.000000000 +0000 @@ -15,6 +15,7 @@ "github.com/juju/errors" "github.com/juju/utils" + "github.com/juju/version" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" @@ -23,7 +24,6 @@ "github.com/juju/juju/state" "github.com/juju/juju/state/binarystorage" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) // toolsHandler handles tool upload through HTTPS in the API server. diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/tools_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/tools_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/tools_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/tools_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,6 +5,7 @@ import ( "crypto/sha256" + "encoding/base64" "encoding/json" "fmt" "io/ioutil" @@ -18,8 +19,12 @@ "github.com/juju/utils" "github.com/juju/utils/arch" "github.com/juju/utils/series" + "github.com/juju/version" gc "gopkg.in/check.v1" + "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" + "github.com/juju/juju/api/usermanager" commontesting "github.com/juju/juju/apiserver/common/testing" "github.com/juju/juju/apiserver/params" envtesting "github.com/juju/juju/environs/testing" @@ -28,8 +33,9 @@ "github.com/juju/juju/state" "github.com/juju/juju/state/binarystorage" "github.com/juju/juju/testing" + "github.com/juju/juju/testing/factory" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) // charmsCommonSuite wraps authHttpSuite and adds @@ -297,7 +303,7 @@ func (s *toolsSuite) TestDownloadModelUUIDPath(c *gc.C) { v := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -312,7 +318,7 @@ func (s *toolsSuite) TestDownloadOtherModelUUIDPath(c *gc.C) { envState := s.setupOtherModel(c) v := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -326,7 +332,7 @@ func (s *toolsSuite) TestDownloadTopLevelPath(c *gc.C) { v := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -356,11 +362,11 @@ func (s *toolsSuite) TestDownloadFetchesAndVerifiesSize(c *gc.C) { // Upload fake tools, then upload over the top so the SHA256 hash does not match. - s.PatchValue(&version.Current, testing.FakeVersionNumber) + s.PatchValue(&jujuversion.Current, testing.FakeVersionNumber) stor := s.DefaultToolsStorage envtesting.RemoveTools(c, stor, "released") current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -375,11 +381,11 @@ func (s *toolsSuite) TestDownloadFetchesAndVerifiesHash(c *gc.C) { // Upload fake tools, then upload over the top so the SHA256 hash does not match. - s.PatchValue(&version.Current, testing.FakeVersionNumber) + s.PatchValue(&jujuversion.Current, testing.FakeVersionNumber) stor := s.DefaultToolsStorage envtesting.RemoveTools(c, stor, "released") current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -450,7 +456,7 @@ func (s *toolsSuite) TestDownloadRejectsWrongModelUUIDPath(c *gc.C) { current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -498,6 +504,40 @@ c.Assert(checkCount, gc.Equals, 1) } +func (s *toolsWithMacaroonsSuite) TestCanPostWithLocalLogin(c *gc.C) { + // Create a new user, and a local login macaroon for it. + user := s.Factory.MakeUser(c, &factory.UserParams{Password: "hunter2"}) + conn := s.OpenAPIAs(c, user.Tag(), "hunter2") + defer conn.Close() + mac, err := usermanager.NewClient(conn).CreateLocalLoginMacaroon(user.UserTag()) + c.Assert(err, jc.ErrorIsNil) + + checkCount := 0 + s.DischargerLogin = func() string { + checkCount++ + return user.UserTag().Id() + } + do := func(req *http.Request) (*http.Response, error) { + data, err := json.Marshal(macaroon.Slice{mac}) + if err != nil { + return nil, err + } + req.Header.Add(httpbakery.MacaroonsHeader, base64.StdEncoding.EncodeToString(data)) + return utils.GetNonValidatingHTTPClient().Do(req) + } + // send without using bakeryDo, so we don't pass any macaroon cookies + // along. + resp := s.sendRequest(c, httpRequestParams{ + method: "POST", + url: s.toolsURI(c, ""), + tag: user.UserTag().String(), + password: "", // no password forces macaroon usage + do: do, + }) + s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument") + c.Assert(checkCount, gc.Equals, 0) +} + // doer returns a Do function that can make a bakery request // appropriate for a charms endpoint. func (s *toolsWithMacaroonsSuite) doer() func(*http.Request) (*http.Response, error) { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/undertaker/undertaker_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/undertaker/undertaker_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/undertaker/undertaker_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/undertaker/undertaker_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -48,7 +48,7 @@ EnvironManager: true, }, } { - st := newMockState(names.NewUserTag("admin"), "dummymodel", true) + st := newMockState(names.NewUserTag("admin"), "admin", true) _, err := undertaker.NewUndertaker( st, nil, @@ -60,7 +60,7 @@ func (s *undertakerSuite) TestEnvironInfo(c *gc.C) { otherSt, hostedAPI := s.setupStateAndAPI(c, false, "hostedenv") - st, api := s.setupStateAndAPI(c, true, "dummymodel") + st, api := s.setupStateAndAPI(c, true, "admin") for _, test := range []struct { st *mockState api *undertaker.UndertakerAPI @@ -68,7 +68,7 @@ envName string }{ {otherSt, hostedAPI, false, "hostedenv"}, - {st, api, true, "dummymodel"}, + {st, api, true, "admin"}, } { env, err := test.st.Model() c.Assert(err, jc.ErrorIsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/uniter/uniter.go charm-2.2.0/src/github.com/juju/juju/apiserver/uniter/uniter.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/uniter/uniter.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/uniter/uniter.go 2016-04-28 06:03:34.000000000 +0000 @@ -766,27 +766,13 @@ // Unit.WatchActionNotifications(). This method is called from // api/uniter/uniter.go WatchActionNotifications(). func (u *UniterAPIV3) WatchActionNotifications(args params.Entities) (params.StringsWatchResults, error) { - nothing := params.StringsWatchResults{} - - result := params.StringsWatchResults{ - Results: make([]params.StringsWatchResult, len(args.Entities)), - } + tagToActionReceiver := common.TagToActionReceiverFn(u.st.FindEntity) + watchOne := common.WatchOneActionReceiverNotifications(tagToActionReceiver, u.resources.Register) canAccess, err := u.accessUnit() if err != nil { - return nothing, err - } - for i, entity := range args.Entities { - tag, err := names.ParseUnitTag(entity.Tag) - if err != nil { - return nothing, err - } - err = common.ErrPerm - if canAccess(tag) { - result.Results[i], err = u.watchOneUnitActionNotifications(tag) - } - result.Results[i].Error = common.ServerError(err) + return params.StringsWatchResults{}, err } - return result, nil + return common.WatchActionNotifications(args, canAccess, watchOne), nil } // ConfigSettings returns the complete set of service charm config @@ -934,119 +920,36 @@ // Actions returns the Actions by Tags passed and ensures that the Unit asking // for them is the same Unit that has the Actions. -func (u *UniterAPIV3) Actions(args params.Entities) (params.ActionsQueryResults, error) { - nothing := params.ActionsQueryResults{} - - actionFn, err := u.authAndActionFromTagFn() +func (u *UniterAPIV3) Actions(args params.Entities) (params.ActionResults, error) { + canAccess, err := u.accessUnit() if err != nil { - return nothing, err - } - - results := params.ActionsQueryResults{ - Results: make([]params.ActionsQueryResult, len(args.Entities)), + return params.ActionResults{}, err } - for i, arg := range args.Entities { - action, err := actionFn(arg.Tag) - if err != nil { - results.Results[i].Error = common.ServerError(err) - continue - } - if action.Status() != state.ActionPending { - results.Results[i].Error = common.ServerError(common.ErrActionNotAvailable) - continue - } - results.Results[i].Action.Action = ¶ms.Action{ - Name: action.Name(), - Parameters: action.Parameters(), - } - } - - return results, nil + actionFn := common.AuthAndActionFromTagFn(canAccess, u.st.ActionByTag) + return common.Actions(args, actionFn), nil } // BeginActions marks the actions represented by the passed in Tags as running. func (u *UniterAPIV3) BeginActions(args params.Entities) (params.ErrorResults, error) { - nothing := params.ErrorResults{} - - actionFn, err := u.authAndActionFromTagFn() + canAccess, err := u.accessUnit() if err != nil { - return nothing, err - } - - results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))} - - for i, arg := range args.Entities { - action, err := actionFn(arg.Tag) - if err != nil { - results.Results[i].Error = common.ServerError(err) - continue - } - - _, err = action.Begin() - if err != nil { - results.Results[i].Error = common.ServerError(err) - continue - } + return params.ErrorResults{}, err } - return results, nil + actionFn := common.AuthAndActionFromTagFn(canAccess, u.st.ActionByTag) + return common.BeginActions(args, actionFn), nil } // FinishActions saves the result of a completed Action func (u *UniterAPIV3) FinishActions(args params.ActionExecutionResults) (params.ErrorResults, error) { - nothing := params.ErrorResults{} - - actionFn, err := u.authAndActionFromTagFn() + canAccess, err := u.accessUnit() if err != nil { - return nothing, err - } - - results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))} - - for i, arg := range args.Results { - action, err := actionFn(arg.ActionTag) - if err != nil { - results.Results[i].Error = common.ServerError(err) - continue - } - actionResults, err := paramsActionExecutionResultsToStateActionResults(arg) - if err != nil { - results.Results[i].Error = common.ServerError(err) - continue - } - - _, err = action.Finish(actionResults) - if err != nil { - results.Results[i].Error = common.ServerError(err) - continue - } + return params.ErrorResults{}, err } - return results, nil -} - -// paramsActionExecutionResultsToStateActionResults does exactly what -// the name implies. -func paramsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) { - var status state.ActionStatus - switch arg.Status { - case params.ActionCancelled: - status = state.ActionCancelled - case params.ActionCompleted: - status = state.ActionCompleted - case params.ActionFailed: - status = state.ActionFailed - case params.ActionPending: - status = state.ActionPending - default: - return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status) - } - return state.ActionResults{ - Status: status, - Results: arg.Results, - Message: arg.Message, - }, nil + actionFn := common.AuthAndActionFromTagFn(canAccess, u.st.ActionByTag) + return common.FinishActions(args, actionFn), nil } // RelationById returns information about all given relations, @@ -1483,23 +1386,6 @@ return "", watcher.EnsureErr(watch) } -func (u *UniterAPIV3) watchOneUnitActionNotifications(tag names.UnitTag) (params.StringsWatchResult, error) { - nothing := params.StringsWatchResult{} - unit, err := u.getUnit(tag) - if err != nil { - return nothing, err - } - watch := unit.WatchActionNotifications() - - if changes, ok := <-watch.Changes(); ok { - return params.StringsWatchResult{ - StringsWatcherId: u.resources.Register(watch), - Changes: changes, - }, nil - } - return nothing, watcher.EnsureErr(watch) -} - func (u *UniterAPIV3) watchOneUnitAddresses(tag names.UnitTag) (string, error) { unit, err := u.getUnit(tag) if err != nil { @@ -1562,43 +1448,6 @@ return remoteUnitName, nil } -// authAndActionFromTagFn first authenticates the request, and then returns -// a function with which to authenticate and retrieve each action in the -// request. -func (u *UniterAPIV3) authAndActionFromTagFn() (func(string) (*state.Action, error), error) { - canAccess, err := u.accessUnit() - if err != nil { - return nil, err - } - unit, ok := u.auth.GetAuthTag().(names.UnitTag) - if !ok { - return nil, fmt.Errorf("calling entity is not a unit") - } - - return func(tag string) (*state.Action, error) { - actionTag, err := names.ParseActionTag(tag) - if err != nil { - return nil, err - } - action, err := u.st.ActionByTag(actionTag) - if err != nil { - return nil, err - } - receiverTag, err := names.ActionReceiverTag(action.Receiver()) - if err != nil { - return nil, err - } - if unit != receiverTag { - return nil, common.ErrPerm - } - - if !canAccess(receiverTag) { - return nil, common.ErrPerm - } - return action, nil - }, nil -} - func convertRelationSettings(settings map[string]interface{}) (params.Settings, error) { result := make(params.Settings) for k, v := range settings { @@ -1705,9 +1554,9 @@ // NetworkConfig returns information about all given relation/unit pairs, // including their id, key and the local endpoint. -func (u *UniterAPIV3) NetworkConfig(args params.RelationUnits) (params.UnitNetworkConfigResults, error) { +func (u *UniterAPIV3) NetworkConfig(args params.UnitsNetworkConfig) (params.UnitNetworkConfigResults, error) { result := params.UnitNetworkConfigResults{ - Results: make([]params.UnitNetworkConfigResult, len(args.RelationUnits)), + Results: make([]params.UnitNetworkConfigResult, len(args.Args)), } canAccess, err := u.accessUnit() @@ -1715,10 +1564,9 @@ return params.UnitNetworkConfigResults{}, err } - for i, rel := range args.RelationUnits { - netConfig, err := u.getOneNetworkConfig(canAccess, rel.Relation, rel.Unit) + for i, arg := range args.Args { + netConfig, err := u.getOneNetworkConfig(canAccess, arg.UnitTag, arg.BindingName) if err == nil { - result.Results[i].Error = nil result.Results[i].Config = netConfig } else { result.Results[i].Error = common.ServerError(err) @@ -1727,19 +1575,18 @@ return result, nil } -func (u *UniterAPIV3) getOneNetworkConfig(canAccess common.AuthFunc, tagRel, tagUnit string) ([]params.NetworkConfig, error) { - unitTag, err := names.ParseUnitTag(tagUnit) +func (u *UniterAPIV3) getOneNetworkConfig(canAccess common.AuthFunc, unitTagArg, bindingName string) ([]params.NetworkConfig, error) { + unitTag, err := names.ParseUnitTag(unitTagArg) if err != nil { return nil, errors.Trace(err) } - if !canAccess(unitTag) { - return nil, common.ErrPerm + if bindingName == "" { + return nil, errors.Errorf("binding name cannot be empty") } - relTag, err := names.ParseRelationTag(tagRel) - if err != nil { - return nil, errors.Trace(err) + if !canAccess(unitTag) { + return nil, common.ErrPerm } unit, err := u.getUnit(unitTag) @@ -1756,15 +1603,9 @@ if err != nil { return nil, errors.Trace(err) } - - rel, err := u.st.KeyRelation(relTag.Id()) - if err != nil { - return nil, errors.Trace(err) - } - - endpoint, err := rel.Endpoint(service.Name()) - if err != nil { - return nil, errors.Trace(err) + boundSpace, known := bindings[bindingName] + if !known { + return nil, errors.Errorf("binding name %q not defined by the unit's charm", bindingName) } machineID, err := unit.AssignedMachineId() @@ -1778,11 +1619,11 @@ } var results []params.NetworkConfig - - boundSpace, isBound := bindings[endpoint.Name] - if !isBound || boundSpace == "" { - name := endpoint.Name - logger.Debugf("endpoint %q not explicitly bound to a space, using preferred private address for machine %q", name, machineID) + if boundSpace == "" { + logger.Debugf( + "endpoint %q not explicitly bound to a space, using preferred private address for machine %q", + bindingName, machineID, + ) privateAddress, err := machine.PrivateAddress() if err != nil && !network.IsNoAddress(err) { @@ -1793,31 +1634,42 @@ Address: privateAddress.Value, }) return results, nil + } else { + logger.Debugf("endpoint %q is explicitly bound to space %q", bindingName, boundSpace) } - logger.Debugf("endpoint %q is explicitly bound to space %q", endpoint.Name, boundSpace) // TODO(dimitern): Use NetworkInterfaces() instead later, this is just for // the PoC to enable minimal network-get implementation returning just the // primary address. // // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119258804 - addresses := machine.ProviderAddresses() + addresses, err := machine.AllAddresses() + if err != nil { + return nil, errors.Annotate(err, "cannot get devices addresses") + } logger.Infof( "geting network config for machine %q with addresses %+v, hosting unit %q of service %q, with bindings %+v", machineID, addresses, unit.Name(), service.Name(), bindings, ) for _, addr := range addresses { - space := string(addr.SpaceName) - if space != boundSpace { - logger.Debugf("skipping address %q: want bound to space %q, got space %q", addr.Value, boundSpace, space) + subnet, err := addr.Subnet() + if err != nil { + return nil, errors.Annotatef(err, "cannot get subnet for address %q", addr) + } + if subnet == nil { + logger.Debugf("skipping %s: not linked to a known subnet", addr) + continue + } + if space := subnet.SpaceName(); space != boundSpace { + logger.Debugf("skipping %s: want bound to space %q, got space %q", addr, boundSpace, space) continue } - logger.Debugf("endpoint %q bound to space %q has address %q", endpoint.Name, boundSpace, addr.Value) + logger.Debugf("endpoint %q bound to space %q has address %q", bindingName, boundSpace, addr) // TODO(dimitern): Fill in the rest later (see linked LKK card above). results = append(results, params.NetworkConfig{ - Address: addr.Value, + Address: addr.Value(), }) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/uniter/uniter_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/uniter/uniter_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/uniter/uniter_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/uniter/uniter_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -936,18 +936,26 @@ args := params.Entities{Entities: []params.Entity{ {Tag: "ewenit-mysql-0"}, }} - _, err := s.uniter.WatchActionNotifications(args) - c.Assert(err, gc.NotNil) - c.Assert(err.Error(), gc.Equals, `"ewenit-mysql-0" is not a valid tag`) + results, err := s.uniter.WatchActionNotifications(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(results, gc.NotNil) + c.Assert(len(results.Results), gc.Equals, 1) + result := results.Results[0] + c.Assert(result.Error, gc.NotNil) + c.Assert(result.Error.Message, gc.Equals, `invalid actionreceiver tag "ewenit-mysql-0"`) } func (s *uniterSuite) TestWatchActionNotificationsMalformedUnitName(c *gc.C) { args := params.Entities{Entities: []params.Entity{ {Tag: "unit-mysql-01"}, }} - _, err := s.uniter.WatchActionNotifications(args) - c.Assert(err, gc.NotNil) - c.Assert(err.Error(), gc.Equals, `"unit-mysql-01" is not a valid unit tag`) + results, err := s.uniter.WatchActionNotifications(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(results, gc.NotNil) + c.Assert(len(results.Results), gc.Equals, 1) + result := results.Results[0] + c.Assert(result.Error, gc.NotNil) + c.Assert(result.Error.Message, gc.Equals, `invalid actionreceiver tag "unit-mysql-01"`) } func (s *uniterSuite) TestWatchActionNotificationsNotUnit(c *gc.C) { @@ -956,9 +964,13 @@ args := params.Entities{Entities: []params.Entity{ {Tag: action.Tag().String()}, }} - _, err = s.uniter.WatchActionNotifications(args) - c.Assert(err, gc.NotNil) - c.Assert(err.Error(), gc.Equals, `"action-`+action.Id()+`" is not a valid unit tag`) + results, err := s.uniter.WatchActionNotifications(args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(results, gc.NotNil) + c.Assert(len(results.Results), gc.Equals, 1) + result := results.Results[0] + c.Assert(result.Error, gc.NotNil) + c.Assert(result.Error.Message, gc.Equals, `invalid actionreceiver tag "action-`+action.Id()+`"`) } func (s *uniterSuite) TestWatchActionNotificationsPermissionDenied(c *gc.C) { @@ -1132,8 +1144,7 @@ actionsQueryResult := results.Results[0] - c.Assert(actionsQueryResult.Error, gc.IsNil) - c.Assert(actionsQueryResult.Action, jc.DeepEquals, actionTest.action) + c.Assert(actionsQueryResult, jc.DeepEquals, actionTest.action) } } @@ -1195,7 +1206,7 @@ results, err := s.wordpressUnit.CompletedActions() c.Assert(err, jc.ErrorIsNil) - c.Assert(results, gc.DeepEquals, ([]*state.Action)(nil)) + c.Assert(results, gc.DeepEquals, ([]state.Action)(nil)) action, err := s.wordpressUnit.AddAction(testName, nil) c.Assert(err, jc.ErrorIsNil) @@ -1227,7 +1238,7 @@ results, err := s.wordpressUnit.CompletedActions() c.Assert(err, jc.ErrorIsNil) - c.Assert(results, gc.DeepEquals, ([]*state.Action)(nil)) + c.Assert(results, gc.DeepEquals, ([]state.Action)(nil)) action, err := s.wordpressUnit.AddAction(testName, nil) c.Assert(err, jc.ErrorIsNil) @@ -2367,43 +2378,47 @@ var _ = gc.Suite(&uniterNetworkConfigSuite{}) -func (s *uniterNetworkConfigSuite) SetUpTest(c *gc.C) { - s.base.JujuConnSuite.SetUpTest(c) +func (s *uniterNetworkConfigSuite) SetUpSuite(c *gc.C) { + s.base.SetUpSuite(c) +} - var err error - s.base.machine0, err = s.base.State.AddMachine("quantal", state.JobHostUnits) - c.Assert(err, jc.ErrorIsNil) +func (s *uniterNetworkConfigSuite) TearDownSuite(c *gc.C) { + s.base.TearDownSuite(c) +} - _, err = s.base.State.AddSpace("internal", "", nil, false) - c.Assert(err, jc.ErrorIsNil) - _, err = s.base.State.AddSpace("public", "", nil, false) - c.Assert(err, jc.ErrorIsNil) +func (s *uniterNetworkConfigSuite) SetUpTest(c *gc.C) { + s.base.JujuConnSuite.SetUpTest(c) - providerAddresses := []network.Address{ - network.NewAddressOnSpace("public", "8.8.8.8"), - network.NewAddressOnSpace("", "8.8.4.4"), - network.NewAddressOnSpace("internal", "10.0.0.1"), - network.NewAddressOnSpace("internal", "10.0.0.2"), - network.NewAddressOnSpace("", "fc00::1"), + // Add the spaces and subnets used by the test. + subnetInfos := []state.SubnetInfo{{ + CIDR: "8.8.0.0/16", + SpaceName: "public", + }, { + CIDR: "10.0.0.0/24", + SpaceName: "internal", + }} + for _, info := range subnetInfos { + _, err := s.base.State.AddSpace(info.SpaceName, "", nil, false) + c.Assert(err, jc.ErrorIsNil) + _, err = s.base.State.AddSubnet(info) + c.Assert(err, jc.ErrorIsNil) } - err = s.base.machine0.SetProviderAddresses(providerAddresses...) - c.Assert(err, jc.ErrorIsNil) - - err = s.base.machine0.SetInstanceInfo("i-am", "fake_nonce", nil, nil, nil, nil, nil) - c.Assert(err, jc.ErrorIsNil) + s.base.machine0 = s.addProvisionedMachineWithDevicesAndAddresses(c, 10) factory := jujuFactory.NewFactory(s.base.State) s.base.wpCharm = factory.MakeCharm(c, &jujuFactory.CharmParams{ - Name: "wordpress", - URL: "cs:quantal/wordpress-3", + Name: "wordpress-extra-bindings", + URL: "cs:quantal/wordpress-extra-bindings-4", }) + var err error s.base.wordpress, err = s.base.State.AddService(state.AddServiceArgs{ Name: "wordpress", Charm: s.base.wpCharm, Owner: s.base.AdminUserTag(c).String(), EndpointBindings: map[string]string{ - "db": "internal", + "db": "internal", // relation name + "admin-api": "public", // extra-binding name }, }) c.Assert(err, jc.ErrorIsNil) @@ -2412,13 +2427,7 @@ Machine: s.base.machine0, }) - s.base.machine1 = factory.MakeMachine(c, &jujuFactory.MachineParams{ - Series: "quantal", - Jobs: []state.MachineJob{state.JobHostUnits}, - }) - - err = s.base.machine1.SetProviderAddresses(providerAddresses...) - c.Assert(err, jc.ErrorIsNil) + s.base.machine1 = s.addProvisionedMachineWithDevicesAndAddresses(c, 20) mysqlCharm := factory.MakeCharm(c, &jujuFactory.CharmParams{ Name: "mysql", @@ -2445,6 +2454,61 @@ s.setupUniterAPIForUnit(c, s.base.wordpressUnit) } +func (s *uniterNetworkConfigSuite) addProvisionedMachineWithDevicesAndAddresses(c *gc.C, addrSuffix int) *state.Machine { + machine, err := s.base.State.AddMachine("quantal", state.JobHostUnits) + c.Assert(err, jc.ErrorIsNil) + devicesArgs, devicesAddrs := s.makeMachineDevicesAndAddressesArgs(addrSuffix) + err = machine.SetInstanceInfo("i-am", "fake_nonce", nil, devicesArgs, devicesAddrs, nil, nil) + c.Assert(err, jc.ErrorIsNil) + + machineAddrs, err := machine.AllAddresses() + c.Assert(err, jc.ErrorIsNil) + + netAddrs := make([]network.Address, len(machineAddrs)) + for i, addr := range machineAddrs { + netAddrs[i] = network.NewAddress(addr.Value()) + } + err = machine.SetProviderAddresses(netAddrs...) + c.Assert(err, jc.ErrorIsNil) + + return machine +} + +func (s *uniterNetworkConfigSuite) makeMachineDevicesAndAddressesArgs(addrSuffix int) ([]state.LinkLayerDeviceArgs, []state.LinkLayerDeviceAddress) { + return []state.LinkLayerDeviceArgs{{ + Name: "eth0", + Type: state.EthernetDevice, + }, { + Name: "eth0.100", + Type: state.VLAN_8021QDevice, + ParentName: "eth0", + }, { + Name: "eth1", + Type: state.EthernetDevice, + }, { + Name: "eth1.100", + Type: state.VLAN_8021QDevice, + ParentName: "eth1", + }}, + []state.LinkLayerDeviceAddress{{ + DeviceName: "eth0", + ConfigMethod: state.StaticAddress, + CIDRAddress: fmt.Sprintf("8.8.8.%d/16", addrSuffix), + }, { + DeviceName: "eth0.100", + ConfigMethod: state.StaticAddress, + CIDRAddress: fmt.Sprintf("10.0.0.%d/24", addrSuffix), + }, { + DeviceName: "eth1", + ConfigMethod: state.StaticAddress, + CIDRAddress: fmt.Sprintf("8.8.4.%d/16", addrSuffix), + }, { + DeviceName: "eth1.100", + ConfigMethod: state.StaticAddress, + CIDRAddress: fmt.Sprintf("10.0.0.%d/24", addrSuffix+1), + }} +} + func (s *uniterNetworkConfigSuite) TearDownTest(c *gc.C) { s.base.JujuConnSuite.TearDownTest(c) } @@ -2466,13 +2530,14 @@ } func (s *uniterNetworkConfigSuite) TestNetworkConfigPermissions(c *gc.C) { - rel := s.addRelationAndAssertInScope(c) + s.addRelationAndAssertInScope(c) - args := params.RelationUnits{RelationUnits: []params.RelationUnit{ - {Relation: "relation-42", Unit: "unit-foo-0"}, - {Relation: rel.Tag().String(), Unit: "invalid"}, - {Relation: rel.Tag().String(), Unit: "unit-mysql-0"}, - {Relation: "relation-42", Unit: s.base.wordpressUnit.Tag().String()}, + args := params.UnitsNetworkConfig{Args: []params.UnitNetworkConfig{ + {BindingName: "foo", UnitTag: "unit-foo-0"}, + {BindingName: "db-client", UnitTag: "invalid"}, + {BindingName: "juju-info", UnitTag: "unit-mysql-0"}, + {BindingName: "", UnitTag: s.base.wordpressUnit.Tag().String()}, + {BindingName: "unknown", UnitTag: s.base.wordpressUnit.Tag().String()}, }} result, err := s.base.uniter.NetworkConfig(args) @@ -2482,12 +2547,13 @@ {Error: apiservertesting.ErrUnauthorized}, {Error: apiservertesting.ServerError(`"invalid" is not a valid tag`)}, {Error: apiservertesting.ErrUnauthorized}, - {Error: apiservertesting.ServerError(`"relation-42" is not a valid relation tag`)}, + {Error: apiservertesting.ServerError(`binding name cannot be empty`)}, + {Error: apiservertesting.ServerError(`binding name "unknown" not defined by the unit's charm`)}, }, }) } -func (s *uniterNetworkConfigSuite) addRelationAndAssertInScope(c *gc.C) *state.Relation { +func (s *uniterNetworkConfigSuite) addRelationAndAssertInScope(c *gc.C) { // Add a relation between wordpress and mysql and enter scope with // mysqlUnit. rel := s.base.addRelation(c, "wordpress", "mysql") @@ -2496,36 +2562,42 @@ err = wpRelUnit.EnterScope(nil) c.Assert(err, jc.ErrorIsNil) s.base.assertInScope(c, wpRelUnit, true) - return rel } func (s *uniterNetworkConfigSuite) TestNetworkConfigForExplicitlyBoundEndpoint(c *gc.C) { - rel := s.addRelationAndAssertInScope(c) + s.addRelationAndAssertInScope(c) - args := params.RelationUnits{RelationUnits: []params.RelationUnit{ - {Relation: rel.Tag().String(), Unit: s.base.wordpressUnit.Tag().String()}, + args := params.UnitsNetworkConfig{Args: []params.UnitNetworkConfig{ + {BindingName: "db", UnitTag: s.base.wordpressUnit.Tag().String()}, + {BindingName: "admin-api", UnitTag: s.base.wordpressUnit.Tag().String()}, }} // For the relation "wordpress:db mysql:server" we expect to see only // addresses bound to the "internal" space, where the "db" endpoint itself // is bound to. - expectedConfig := []params.NetworkConfig{{ - Address: "10.0.0.1", - }, { - Address: "10.0.0.2", - }} + expectedConfigWithRelationName := []params.NetworkConfig{ + {Address: "10.0.0.10"}, + {Address: "10.0.0.11"}, + } + // For the "admin-api" extra-binding we expect to see only addresses from + // the "public" space. + expectedConfigWithExtraBindingName := []params.NetworkConfig{ + {Address: "8.8.8.10"}, + {Address: "8.8.4.10"}, + } result, err := s.base.uniter.NetworkConfig(args) c.Assert(err, jc.ErrorIsNil) c.Assert(result, jc.DeepEquals, params.UnitNetworkConfigResults{ Results: []params.UnitNetworkConfigResult{ - {Config: expectedConfig}, + {Config: expectedConfigWithRelationName}, + {Config: expectedConfigWithExtraBindingName}, }, }) } func (s *uniterNetworkConfigSuite) TestNetworkConfigForImplicitlyBoundEndpoint(c *gc.C) { - // Since wordpressUnit as explicit binding for "db", switch the API to + // Since wordpressUnit has explicit binding for "db", switch the API to // mysqlUnit and check "mysql:server" uses the machine preferred private // address. s.setupUniterAPIForUnit(c, s.base.mysqlUnit) @@ -2536,8 +2608,8 @@ c.Assert(err, jc.ErrorIsNil) s.base.assertInScope(c, mysqlRelUnit, true) - args := params.RelationUnits{RelationUnits: []params.RelationUnit{ - {Relation: rel.Tag().String(), Unit: s.base.mysqlUnit.Tag().String()}, + args := params.UnitsNetworkConfig{Args: []params.UnitNetworkConfig{ + {BindingName: "server", UnitTag: s.base.mysqlUnit.Tag().String()}, }} privateAddress, err := s.base.machine1.PrivateAddress() diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/unitupgrader.go charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/unitupgrader.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/unitupgrader.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/unitupgrader.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,12 +5,12 @@ import ( "github.com/juju/names" + "github.com/juju/version" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/state" "github.com/juju/juju/state/watcher" - "github.com/juju/juju/version" ) // UnitUpgraderAPI provides access to the UnitUpgrader API facade. diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/unitupgrader_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/unitupgrader_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/unitupgrader_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/unitupgrader_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -9,6 +9,7 @@ jc "github.com/juju/testing/checkers" "github.com/juju/utils/arch" "github.com/juju/utils/series" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/apiserver/common" @@ -19,7 +20,7 @@ "github.com/juju/juju/state" statetesting "github.com/juju/juju/state/testing" "github.com/juju/juju/tools" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type unitUpgraderSuite struct { @@ -40,7 +41,7 @@ var _ = gc.Suite(&unitUpgraderSuite{}) var current = version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -171,7 +172,7 @@ c.Check(results.Results, gc.HasLen, 1) c.Assert(results.Results[0].Error, gc.IsNil) agentTools := results.Results[0].Tools - c.Check(agentTools.Version.Number, gc.DeepEquals, version.Current) + c.Check(agentTools.Version.Number, gc.DeepEquals, jujuversion.Current) c.Assert(agentTools.URL, gc.NotNil) } assertTools() @@ -194,7 +195,7 @@ Tag: s.rawUnit.Tag().String(), Tools: ¶ms.Version{ Version: version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), }, @@ -209,7 +210,7 @@ func (s *unitUpgraderSuite) TestSetTools(c *gc.C) { cur := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -267,7 +268,7 @@ func (s *unitUpgraderSuite) TestDesiredVersionNoticesMixedAgents(c *gc.C) { current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -283,7 +284,7 @@ c.Assert(results.Results[0].Error, gc.IsNil) agentVersion := results.Results[0].Version c.Assert(agentVersion, gc.NotNil) - c.Check(*agentVersion, gc.DeepEquals, version.Current) + c.Check(*agentVersion, gc.DeepEquals, jujuversion.Current) c.Assert(results.Results[1].Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) c.Assert(results.Results[1].Version, gc.IsNil) @@ -292,7 +293,7 @@ func (s *unitUpgraderSuite) TestDesiredVersionForAgent(c *gc.C) { current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -305,5 +306,5 @@ c.Assert(results.Results[0].Error, gc.IsNil) agentVersion := results.Results[0].Version c.Assert(agentVersion, gc.NotNil) - c.Check(*agentVersion, gc.DeepEquals, version.Current) + c.Check(*agentVersion, gc.DeepEquals, jujuversion.Current) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/upgrader.go charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/upgrader.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/upgrader.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/upgrader.go 2016-04-28 06:03:34.000000000 +0000 @@ -7,13 +7,14 @@ "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" + "github.com/juju/version" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/environs/config" "github.com/juju/juju/state" "github.com/juju/juju/state/watcher" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) var logger = loggo.GetLogger("juju.apiserver.upgrader") @@ -160,7 +161,7 @@ return params.VersionResults{}, common.ServerError(err) } // Is the desired version greater than the current API server version? - isNewerVersion := agentVersion.Compare(version.Current) > 0 + isNewerVersion := agentVersion.Compare(jujuversion.Current) > 0 for i, entity := range args.Entities { tag, err := names.ParseTag(entity.Tag) if err != nil { @@ -182,8 +183,8 @@ if !isNewerVersion || u.entityIsManager(tag) { results[i].Version = &agentVersion } else { - logger.Debugf("desired version is %s, but current version is %s and agent is not a manager node", agentVersion, version.Current) - results[i].Version = &version.Current + logger.Debugf("desired version is %s, but current version is %s and agent is not a manager node", agentVersion, jujuversion.Current) + results[i].Version = &jujuversion.Current } err = nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/upgrader_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/upgrader_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/upgrader/upgrader_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/upgrader/upgrader_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,6 +11,7 @@ jc "github.com/juju/testing/checkers" "github.com/juju/utils/arch" "github.com/juju/utils/series" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/apiserver/common" @@ -21,7 +22,7 @@ "github.com/juju/juju/state" statetesting "github.com/juju/juju/state/testing" coretesting "github.com/juju/juju/testing" - "github.com/juju/juju/version" + jujuversion "github.com/juju/juju/version" ) type upgraderSuite struct { @@ -149,7 +150,7 @@ func (s *upgraderSuite) TestToolsForAgent(c *gc.C) { current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -193,7 +194,7 @@ Tag: s.rawMachine.Tag().String(), Tools: ¶ms.Version{ Version: version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), }, @@ -208,7 +209,7 @@ func (s *upgraderSuite) TestSetTools(c *gc.C) { current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -270,7 +271,7 @@ c.Assert(results.Results[0].Error, gc.IsNil) agentVersion := results.Results[0].Version c.Assert(agentVersion, gc.NotNil) - c.Check(*agentVersion, gc.DeepEquals, version.Current) + c.Check(*agentVersion, gc.DeepEquals, jujuversion.Current) c.Assert(results.Results[1].Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) c.Assert(results.Results[1].Version, gc.IsNil) @@ -285,14 +286,14 @@ c.Assert(results.Results[0].Error, gc.IsNil) agentVersion := results.Results[0].Version c.Assert(agentVersion, gc.NotNil) - c.Check(*agentVersion, gc.DeepEquals, version.Current) + c.Check(*agentVersion, gc.DeepEquals, jujuversion.Current) } func (s *upgraderSuite) bumpDesiredAgentVersion(c *gc.C) version.Number { // In order to call SetModelAgentVersion we have to first SetTools on // all the existing machines current := version.Binary{ - Number: version.Current, + Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries(), } @@ -330,7 +331,7 @@ func (s *upgraderSuite) TestDesiredVersionRestrictedForNonAPIAgents(c *gc.C) { newVersion := s.bumpDesiredAgentVersion(c) - c.Assert(newVersion, gc.Not(gc.Equals), version.Current) + c.Assert(newVersion, gc.Not(gc.Equals), jujuversion.Current) args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} results, err := s.upgrader.DesiredVersion(args) c.Assert(err, jc.ErrorIsNil) @@ -338,5 +339,5 @@ c.Assert(results.Results[0].Error, gc.IsNil) agentVersion := results.Results[0].Version c.Assert(agentVersion, gc.NotNil) - c.Check(*agentVersion, gc.DeepEquals, version.Current) + c.Check(*agentVersion, gc.DeepEquals, jujuversion.Current) } diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/upgrading_root_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/upgrading_root_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/upgrading_root_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/upgrading_root_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -54,7 +54,7 @@ func (r *upgradingRootSuite) TestFindMethodNonExistentVersion(c *gc.C) { root := apiserver.TestingUpgradingRoot(nil) - caller, err := root.FindMethod("Client", 99999999, "Status") + caller, err := root.FindMethod("Client", 99999999, "FullStatus") c.Assert(err, gc.ErrorMatches, "unknown version \\(99999999\\) of interface \"Client\"") c.Assert(caller, gc.IsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/usermanager/usermanager.go charm-2.2.0/src/github.com/juju/juju/apiserver/usermanager/usermanager.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/usermanager/usermanager.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/usermanager/usermanager.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,11 +6,14 @@ import ( "time" + "gopkg.in/macaroon.v1" + "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/modelmanager" "github.com/juju/juju/apiserver/params" "github.com/juju/juju/state" ) @@ -24,9 +27,10 @@ // UserManagerAPI implements the user manager interface and is the concrete // implementation of the api end point. type UserManagerAPI struct { - state *state.State - authorizer common.Authorizer - check *common.BlockChecker + state *state.State + authorizer common.Authorizer + createLocalLoginMacaroon func(names.UserTag) (*macaroon.Macaroon, error) + check *common.BlockChecker } func NewUserManagerAPI( @@ -38,10 +42,20 @@ return nil, common.ErrPerm } + resource, ok := resources.Get("createLocalLoginMacaroon").(common.ValueResource) + if !ok { + return nil, errors.NotFoundf("userAuth resource") + } + createLocalLoginMacaroon, ok := resource.Value.(func(names.UserTag) (*macaroon.Macaroon, error)) + if !ok { + return nil, errors.NotValidf("userAuth resource") + } + return &UserManagerAPI{ - state: st, - authorizer: authorizer, - check: common.NewBlockChecker(st), + state: st, + authorizer: authorizer, + createLocalLoginMacaroon: createLocalLoginMacaroon, + check: common.NewBlockChecker(st), }, nil } @@ -84,7 +98,6 @@ } for i, arg := range args.Users { var user *state.User - var err error if arg.Password != "" { user, err = api.state.AddUser(arg.Username, arg.DisplayName, arg.Password, loggedInUser.Id()) } else { @@ -100,49 +113,34 @@ SecretKey: user.SecretKey(), } } - userTag := user.Tag().(names.UserTag) - for _, modelTagStr := range arg.SharedModelTags { - modelTag, err := names.ParseModelTag(modelTagStr) + + if len(arg.SharedModelTags) > 0 { + modelAccess, err := modelmanager.FromModelAccessParam(arg.ModelAccess) if err != nil { - err = errors.Annotatef(err, "user %q created but model %q not shared", arg.Username, modelTagStr) + err = errors.Annotatef(err, "user %q created but models not shared", arg.Username) result.Results[i].Error = common.ServerError(err) - break + continue } - err = ShareModelAction(api.state, modelTag, loggedInUser, userTag, params.AddModelUser) - if err != nil { - err = errors.Annotatef(err, "user %q created but model %q not shared", arg.Username, modelTagStr) - result.Results[i].Error = common.ServerError(err) - break + userTag := user.Tag().(names.UserTag) + for _, modelTagStr := range arg.SharedModelTags { + modelTag, err := names.ParseModelTag(modelTagStr) + if err != nil { + err = errors.Annotatef(err, "user %q created but model %q not shared", arg.Username, modelTagStr) + result.Results[i].Error = common.ServerError(err) + break + } + err = modelmanager.ChangeModelAccess(api.state, modelTag, loggedInUser, userTag, params.GrantModelAccess, modelAccess) + if err != nil { + err = errors.Annotatef(err, "user %q created but model %q not shared", arg.Username, modelTagStr) + result.Results[i].Error = common.ServerError(err) + break + } } } } return result, nil } -type stateAccessor interface { - ForModel(tag names.ModelTag) (*state.State, error) -} - -// ShareModelAction performs the requested share action (add/remove) for the specified -// sharedWith user on the specified model. -func ShareModelAction(stateAccess stateAccessor, modelTag names.ModelTag, createdBy, sharedWith names.UserTag, action params.ModelAction) error { - st, err := stateAccess.ForModel(modelTag) - if err != nil { - return errors.Annotate(err, "could lookup model") - } - defer st.Close() - switch action { - case params.AddModelUser: - _, err = st.AddModelUser(state.ModelUserSpec{User: sharedWith, CreatedBy: createdBy}) - return errors.Annotate(err, "could not share model") - case params.RemoveModelUser: - err := st.RemoveModelUser(sharedWith) - return errors.Annotate(err, "could not unshare model") - default: - return errors.Errorf("unknown action %q", action) - } -} - func (api *UserManagerAPI) getUser(tag string) (*state.User, error) { userTag, err := names.ParseUserTag(tag) if err != nil { @@ -255,6 +253,33 @@ return results, nil } +// SetPassword changes the stored password for the specified users. +func (api *UserManagerAPI) SetPassword(args params.EntityPasswords) (params.ErrorResults, error) { + if err := api.check.ChangeAllowed(); err != nil { + return params.ErrorResults{}, errors.Trace(err) + } + result := params.ErrorResults{ + Results: make([]params.ErrorResult, len(args.Changes)), + } + if len(args.Changes) == 0 { + return result, nil + } + loggedInUser, err := api.getLoggedInUser() + if err != nil { + return result, common.ErrPerm + } + adminUser, err := api.checkAdminUser(loggedInUser) + if err != nil { + return result, common.ServerError(err) + } + for i, arg := range args.Changes { + if err := api.setPassword(loggedInUser, arg, adminUser); err != nil { + result.Results[i].Error = common.ServerError(err) + } + } + return result, nil +} + func (api *UserManagerAPI) setPassword(loggedInUser names.UserTag, arg params.EntityPassword, adminUser bool) error { user, err := api.getUser(arg.Tag) if err != nil { @@ -272,29 +297,50 @@ return nil } -// SetPassword changes the stored password for the specified users. -func (api *UserManagerAPI) SetPassword(args params.EntityPasswords) (params.ErrorResults, error) { - if err := api.check.ChangeAllowed(); err != nil { - return params.ErrorResults{}, errors.Trace(err) - } - result := params.ErrorResults{ - Results: make([]params.ErrorResult, len(args.Changes)), - } - if len(args.Changes) == 0 { - return result, nil +// CreateLocalLoginMacaroon creates a macaroon for the specified users to use +// for future logins. +func (api *UserManagerAPI) CreateLocalLoginMacaroon(args params.Entities) (params.MacaroonResults, error) { + results := params.MacaroonResults{ + Results: make([]params.MacaroonResult, len(args.Entities)), } loggedInUser, err := api.getLoggedInUser() if err != nil { - return result, common.ErrPerm + return results, common.ErrPerm } - permErr := api.permissionCheck(loggedInUser) - adminUser := permErr == nil - for i, arg := range args.Changes { - if err := api.setPassword(loggedInUser, arg, adminUser); err != nil { - result.Results[i].Error = common.ServerError(err) + adminUser, err := api.checkAdminUser(loggedInUser) + if err != nil { + return results, common.ServerError(err) + } + createLocalLoginMacaroon := func(arg params.Entity) (*macaroon.Macaroon, error) { + user, err := api.getUser(arg.Tag) + if err != nil { + return nil, errors.Trace(err) } + if loggedInUser != user.UserTag() && !adminUser { + return nil, errors.Trace(common.ErrPerm) + } + return api.createLocalLoginMacaroon(user.UserTag()) } - return result, nil + for i, arg := range args.Entities { + m, err := createLocalLoginMacaroon(arg) + if err != nil { + results.Results[i].Error = common.ServerError(err) + continue + } + results.Results[i].Result = m + } + return results, nil +} + +func (api *UserManagerAPI) checkAdminUser(userTag names.UserTag) (bool, error) { + err := api.permissionCheck(userTag) + switch errors.Cause(err) { + case nil: + return true, nil + case common.ErrPerm: + return false, nil + } + return false, errors.Trace(err) } func (api *UserManagerAPI) getLoggedInUser() (names.UserTag, error) { diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/usermanager/usermanager_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/usermanager/usermanager_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/usermanager/usermanager_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/usermanager/usermanager_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -10,7 +10,9 @@ "github.com/juju/names" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "gopkg.in/macaroon.v1" + "github.com/juju/juju/apiserver/common" commontesting "github.com/juju/juju/apiserver/common/testing" "github.com/juju/juju/apiserver/params" apiservertesting "github.com/juju/juju/apiserver/testing" @@ -23,9 +25,11 @@ type userManagerSuite struct { jujutesting.JujuConnSuite - usermanager *usermanager.UserManagerAPI - authorizer apiservertesting.FakeAuthorizer - adminName string + usermanager *usermanager.UserManagerAPI + authorizer apiservertesting.FakeAuthorizer + adminName string + resources *common.Resources + createLocalLoginMacaroon func(names.UserTag) (*macaroon.Macaroon, error) commontesting.BlockHelper } @@ -35,13 +39,23 @@ func (s *userManagerSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) + s.createLocalLoginMacaroon = func(tag names.UserTag) (*macaroon.Macaroon, error) { + return nil, errors.NotSupportedf("CreateLocalLoginMacaroon") + } + s.resources = common.NewResources() + s.resources.RegisterNamed("createLocalLoginMacaroon", common.ValueResource{ + func(tag names.UserTag) (*macaroon.Macaroon, error) { + return s.createLocalLoginMacaroon(tag) + }, + }) + adminTag := s.AdminUserTag(c) s.adminName = adminTag.Name() s.authorizer = apiservertesting.FakeAuthorizer{ Tag: adminTag, } var err error - s.usermanager, err = usermanager.NewUserManagerAPI(s.State, nil, s.authorizer) + s.usermanager, err = usermanager.NewUserManagerAPI(s.State, s.resources, s.authorizer) c.Assert(err, jc.ErrorIsNil) s.BlockHelper = commontesting.NewBlockHelper(s.APIState) @@ -51,12 +65,12 @@ func (s *userManagerSuite) TestNewUserManagerAPIRefusesNonClient(c *gc.C) { anAuthoriser := s.authorizer anAuthoriser.Tag = names.NewMachineTag("1") - endPoint, err := usermanager.NewUserManagerAPI(s.State, nil, anAuthoriser) + endPoint, err := usermanager.NewUserManagerAPI(s.State, s.resources, anAuthoriser) c.Assert(endPoint, gc.IsNil) c.Assert(err, gc.ErrorMatches, "permission denied") } -func (s *userManagerSuite) assertAddUser(c *gc.C, sharedModelTags []string) { +func (s *userManagerSuite) assertAddUser(c *gc.C, access params.ModelAccessPermission, sharedModelTags []string) { sharedModelState := s.Factory.MakeModel(c, nil) defer sharedModelState.Close() @@ -66,6 +80,7 @@ DisplayName: "Foo Bar", Password: "password", SharedModelTags: sharedModelTags, + ModelAccess: access, }}} result, err := s.usermanager.AddUser(args) @@ -84,7 +99,7 @@ } func (s *userManagerSuite) TestAddUser(c *gc.C) { - s.assertAddUser(c, nil) + s.assertAddUser(c, params.ModelAccessPermission(""), nil) } func (s *userManagerSuite) TestAddUserWithSecretKey(c *gc.C) { @@ -118,11 +133,19 @@ }) } -func (s *userManagerSuite) TestAddUserWithSharedModel(c *gc.C) { +func (s *userManagerSuite) TestAddReadAccessUser(c *gc.C) { + s.addUserWithSharedModel(c, params.ModelReadAccess) +} + +func (s *userManagerSuite) TestAddWriteAccessUser(c *gc.C) { + s.addUserWithSharedModel(c, params.ModelWriteAccess) +} + +func (s *userManagerSuite) addUserWithSharedModel(c *gc.C, access params.ModelAccessPermission) { sharedModelState := s.Factory.MakeModel(c, nil) defer sharedModelState.Close() - s.assertAddUser(c, []string{sharedModelState.ModelTag().String()}) + s.assertAddUser(c, access, []string{sharedModelState.ModelTag().String()}) // Check that the model has been shared. sharedModel, err := sharedModelState.Model() @@ -132,6 +155,11 @@ var modelUserTags = make([]names.UserTag, len(users)) for i, u := range users { modelUserTags[i] = u.UserTag() + if u.UserName() == "foobar" { + c.Assert(u.ReadOnly(), gc.Equals, access == params.ModelReadAccess) + } else if u.UserName() == "admin" { + c.Assert(u.ReadOnly(), gc.Equals, false) + } } c.Assert(modelUserTags, jc.SameContents, []names.UserTag{ names.NewLocalUserTag("foobar"), @@ -163,7 +191,7 @@ func (s *userManagerSuite) TestAddUserAsNormalUser(c *gc.C) { alex := s.Factory.MakeUser(c, &factory.UserParams{Name: "alex"}) usermanager, err := usermanager.NewUserManagerAPI( - s.State, nil, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) + s.State, s.resources, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) c.Assert(err, jc.ErrorIsNil) args := params.AddUsers{ @@ -315,7 +343,7 @@ func (s *userManagerSuite) TestDisableUserAsNormalUser(c *gc.C) { alex := s.Factory.MakeUser(c, &factory.UserParams{Name: "alex"}) usermanager, err := usermanager.NewUserManagerAPI( - s.State, nil, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) + s.State, s.resources, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) c.Assert(err, jc.ErrorIsNil) barb := s.Factory.MakeUser(c, &factory.UserParams{Name: "barb"}) @@ -334,7 +362,7 @@ func (s *userManagerSuite) TestEnableUserAsNormalUser(c *gc.C) { alex := s.Factory.MakeUser(c, &factory.UserParams{Name: "alex"}) usermanager, err := usermanager.NewUserManagerAPI( - s.State, nil, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) + s.State, s.resources, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) c.Assert(err, jc.ErrorIsNil) barb := s.Factory.MakeUser(c, &factory.UserParams{Name: "barb", Disabled: true}) @@ -517,7 +545,7 @@ func (s *userManagerSuite) TestSetPasswordForSelf(c *gc.C) { alex := s.Factory.MakeUser(c, &factory.UserParams{Name: "alex"}) usermanager, err := usermanager.NewUserManagerAPI( - s.State, nil, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) + s.State, s.resources, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) c.Assert(err, jc.ErrorIsNil) args := params.EntityPasswords{ @@ -540,7 +568,7 @@ alex := s.Factory.MakeUser(c, &factory.UserParams{Name: "alex"}) barb := s.Factory.MakeUser(c, &factory.UserParams{Name: "barb"}) usermanager, err := usermanager.NewUserManagerAPI( - s.State, nil, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) + s.State, s.resources, apiservertesting.FakeAuthorizer{Tag: alex.Tag()}) c.Assert(err, jc.ErrorIsNil) args := params.EntityPasswords{ diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/watcher.go charm-2.2.0/src/github.com/juju/juju/apiserver/watcher.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/watcher.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/watcher.go 2016-04-28 06:03:34.000000000 +0000 @@ -11,6 +11,8 @@ "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/common/storagecommon" "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/network" "github.com/juju/juju/state" ) @@ -51,9 +53,13 @@ "EntityWatcher", 2, newEntitiesWatcher, reflect.TypeOf((*srvEntitiesWatcher)(nil)), ) + common.RegisterFacade( + "MigrationStatusWatcher", 1, newMigrationStatusWatcher, + reflect.TypeOf((*srvMigrationStatusWatcher)(nil)), + ) } -// NewAllModelWatcher returns a new API server endpoint for interacting +// NewAllWatcher returns a new API server endpoint for interacting // with a watcher created by the WatchAll and WatchAllModels API calls. func NewAllWatcher(st *state.State, resources *common.Resources, auth common.Authorizer, id string) (interface{}, error) { if !auth.AuthClient() { @@ -371,3 +377,140 @@ func (w *srvEntitiesWatcher) Stop() error { return w.resources.Stop(w.id) } + +var getMigrationBackend = func(st *state.State) migrationBackend { + return st +} + +// migrationBackend defines State functionality required by the +// migration watchers. +type migrationBackend interface { + GetModelMigration() (state.ModelMigration, error) + APIHostPorts() ([][]network.HostPort, error) + ControllerModel() (*state.Model, error) +} + +func newMigrationStatusWatcher( + st *state.State, + resources *common.Resources, + auth common.Authorizer, + id string, +) (interface{}, error) { + if !(auth.AuthMachineAgent() || auth.AuthUnitAgent()) { + return nil, common.ErrPerm + } + w, ok := resources.Get(id).(state.NotifyWatcher) + if !ok { + return nil, common.ErrUnknownWatcher + } + return &srvMigrationStatusWatcher{ + watcher: w, + id: id, + resources: resources, + st: getMigrationBackend(st), + }, nil +} + +type srvMigrationStatusWatcher struct { + watcher state.NotifyWatcher + id string + resources *common.Resources + st migrationBackend +} + +// Next returns when the status for a model migration for the +// associated model changes. The current details for the active +// migration are returned. +func (w *srvMigrationStatusWatcher) Next() (params.MigrationStatus, error) { + empty := params.MigrationStatus{} + + if _, ok := <-w.watcher.Changes(); !ok { + err := w.watcher.Err() + if err == nil { + err = common.ErrStoppedWatcher + } + return empty, err + } + + mig, err := w.st.GetModelMigration() + if errors.IsNotFound(err) { + return params.MigrationStatus{ + Phase: migration.NONE.String(), + }, nil + } else if err != nil { + return empty, errors.Annotate(err, "migration lookup") + } + + attempt, err := mig.Attempt() + if err != nil { + return empty, errors.Annotate(err, "retrieving migration attempt") + } + + phase, err := mig.Phase() + if err != nil { + return empty, errors.Annotate(err, "retrieving migration phase") + } + + sourceAddrs, err := w.getLocalHostPorts() + if err != nil { + return empty, errors.Annotate(err, "retrieving source addresses") + } + + sourceCACert, err := getControllerCACert(w.st) + if err != nil { + return empty, errors.Annotate(err, "retrieving source CA cert") + } + + target, err := mig.TargetInfo() + if err != nil { + return empty, errors.Annotate(err, "retrieving target info") + } + + return params.MigrationStatus{ + Attempt: attempt, + Phase: phase.String(), + SourceAPIAddrs: sourceAddrs, + SourceCACert: sourceCACert, + TargetAPIAddrs: target.Addrs, + TargetCACert: target.CACert, + }, nil +} + +func (w *srvMigrationStatusWatcher) getLocalHostPorts() ([]string, error) { + hostports, err := w.st.APIHostPorts() + if err != nil { + return nil, errors.Trace(err) + } + var out []string + for _, section := range hostports { + for _, hostport := range section { + out = append(out, hostport.String()) + } + } + return out, nil +} + +// Stop stops the watcher. +func (w *srvMigrationStatusWatcher) Stop() error { + return w.resources.Stop(w.id) +} + +// This is a shim to avoid the need to use a working State into the +// unit tests. It is tested as part of the client side API tests. +var getControllerCACert = func(st migrationBackend) (string, error) { + model, err := st.ControllerModel() + if err != nil { + return "", errors.Trace(err) + } + + config, err := model.Config() + if err != nil { + return "", errors.Trace(err) + } + + cacert, ok := config.CACert() + if !ok { + return "", errors.New("missing CA cert for controller model") + } + return cacert, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/apiserver/watcher_test.go charm-2.2.0/src/github.com/juju/juju/apiserver/watcher_test.go --- charm-2.1.1/src/github.com/juju/juju/apiserver/watcher_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/apiserver/watcher_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,13 +4,17 @@ package apiserver_test import ( + "github.com/juju/errors" "github.com/juju/names" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/apiserver" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/apiserver/params" apiservertesting "github.com/juju/juju/apiserver/testing" + "github.com/juju/juju/core/migration" + "github.com/juju/juju/network" "github.com/juju/juju/state" "github.com/juju/juju/testing" ) @@ -75,6 +79,54 @@ }) } +func (s *watcherSuite) TestMigrationStatusWatcher(c *gc.C) { + w := apiservertesting.NewFakeNotifyWatcher() + id := s.resources.Register(w) + s.authorizer.Tag = names.NewMachineTag("12") + apiserver.PatchGetMigrationBackend(s, new(fakeMigrationBackend)) + apiserver.PatchGetControllerCACert(s, "no worries") + + w.C <- struct{}{} + facade := s.getFacade(c, "MigrationStatusWatcher", 1, id).(migrationStatusWatcher) + defer c.Check(facade.Stop(), jc.ErrorIsNil) + result, err := facade.Next() + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, jc.DeepEquals, params.MigrationStatus{ + Attempt: 2, + Phase: "READONLY", + SourceAPIAddrs: []string{"1.2.3.4:5", "2.3.4.5:6", "3.4.5.6:7"}, + SourceCACert: "no worries", + TargetAPIAddrs: []string{"1.2.3.4:5555"}, + TargetCACert: "trust me", + }) +} + +func (s *watcherSuite) TestMigrationStatusWatcherNoMigration(c *gc.C) { + w := apiservertesting.NewFakeNotifyWatcher() + id := s.resources.Register(w) + s.authorizer.Tag = names.NewMachineTag("12") + apiserver.PatchGetMigrationBackend(s, &fakeMigrationBackend{noMigration: true}) + + w.C <- struct{}{} + facade := s.getFacade(c, "MigrationStatusWatcher", 1, id).(migrationStatusWatcher) + defer c.Check(facade.Stop(), jc.ErrorIsNil) + result, err := facade.Next() + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, jc.DeepEquals, params.MigrationStatus{ + Phase: "NONE", + }) +} + +func (s *watcherSuite) TestMigrationStatusWatcherNotAgent(c *gc.C) { + id := s.resources.Register(apiservertesting.NewFakeNotifyWatcher()) + s.authorizer.Tag = names.NewUserTag("frogdog") + + factory, err := common.Facades.GetFactory("MigrationStatusWatcher", 1) + c.Assert(err, jc.ErrorIsNil) + _, err = factory(s.st, s.resources, s.authorizer, id) + c.Assert(err, gc.Equals, common.ErrPerm) +} + type machineStorageIdsWatcher interface { Next() (params.MachineStorageIdsWatchResult, error) } @@ -87,3 +139,60 @@ func (w *fakeStringsWatcher) Changes() <-chan []string { return w.ch } + +type fakeMigrationBackend struct { + noMigration bool +} + +func (b *fakeMigrationBackend) GetModelMigration() (state.ModelMigration, error) { + if b.noMigration { + return nil, errors.NotFoundf("migration") + } + return new(fakeModelMigration), nil +} + +func (b *fakeMigrationBackend) APIHostPorts() ([][]network.HostPort, error) { + return [][]network.HostPort{ + MustParseHostPorts("1.2.3.4:5", "2.3.4.5:6"), + MustParseHostPorts("3.4.5.6:7"), + }, nil +} + +func (b *fakeMigrationBackend) ControllerModel() (*state.Model, error) { + return nil, nil +} + +func MustParseHostPorts(hostports ...string) []network.HostPort { + out, err := network.ParseHostPorts(hostports...) + if err != nil { + panic(err) + } + return out +} + +type fakeModelMigration struct { + state.ModelMigration +} + +func (m *fakeModelMigration) Attempt() (int, error) { + return 2, nil +} + +func (m *fakeModelMigration) Phase() (migration.Phase, error) { + return migration.READONLY, nil +} + +func (m *fakeModelMigration) TargetInfo() (*migration.TargetInfo, error) { + return &migration.TargetInfo{ + ControllerTag: names.NewModelTag("uuid"), + Addrs: []string{"1.2.3.4:5555"}, + CACert: "trust me", + AuthTag: names.NewUserTag("admin"), + Password: "sekret", + }, nil +} + +type migrationStatusWatcher interface { + Next() (params.MigrationStatus, error) + Stop() error +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cert/cert.go charm-2.2.0/src/github.com/juju/juju/cert/cert.go --- charm-2.1.1/src/github.com/juju/juju/cert/cert.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cert/cert.go 2016-04-28 06:03:34.000000000 +0000 @@ -82,17 +82,26 @@ // NewCA generates a CA certificate/key pair suitable for signing server // keys for an environment with the given name. -func NewCA(envName string, expiry time.Time) (certPEM, keyPEM string, err error) { +func NewCA(envName, UUID string, expiry time.Time) (certPEM, keyPEM string, err error) { key, err := rsa.GenerateKey(rand.Reader, KeyBits) if err != nil { return "", "", err } now := time.Now() + + // A serial number can be up to 20 octets in size. + // https://tools.ietf.org/html/rfc5280#section-4.1.2.2 + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 8*20)) + if err != nil { + return "", "", fmt.Errorf("failed to generate serial number: %s", err) + } + template := &x509.Certificate{ - SerialNumber: new(big.Int), + SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: fmt.Sprintf("juju-generated CA for model %q", envName), Organization: []string{"juju"}, + SerialNumber: UUID, }, NotBefore: now.UTC().AddDate(0, 0, -7), NotAfter: expiry.UTC(), diff -Nru charm-2.1.1/src/github.com/juju/juju/cert/cert_test.go charm-2.2.0/src/github.com/juju/juju/cert/cert_test.go --- charm-2.1.1/src/github.com/juju/juju/cert/cert_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cert/cert_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -68,7 +68,7 @@ func (certSuite) TestNewCA(c *gc.C) { now := time.Now() expiry := roundTime(now.AddDate(0, 0, 1)) - caCertPEM, caKeyPEM, err := cert.NewCA("foo", expiry) + caCertPEM, caKeyPEM, err := cert.NewCA("foo", "1", expiry) c.Assert(err, jc.ErrorIsNil) caCert, caKey, err := cert.ParseCertAndKey(caCertPEM, caKeyPEM) @@ -86,7 +86,7 @@ func (certSuite) TestNewServer(c *gc.C) { now := time.Now() expiry := roundTime(now.AddDate(1, 0, 0)) - caCertPEM, caKeyPEM, err := cert.NewCA("foo", expiry) + caCertPEM, caKeyPEM, err := cert.NewCA("foo", "1", expiry) c.Assert(err, jc.ErrorIsNil) caCert, _, err := cert.ParseCertAndKey(caCertPEM, caKeyPEM) @@ -100,7 +100,7 @@ func (certSuite) TestNewDefaultServer(c *gc.C) { now := time.Now() expiry := roundTime(now.AddDate(1, 0, 0)) - caCertPEM, caKeyPEM, err := cert.NewCA("foo", expiry) + caCertPEM, caKeyPEM, err := cert.NewCA("foo", "1", expiry) c.Assert(err, jc.ErrorIsNil) caCert, _, err := cert.ParseCertAndKey(caCertPEM, caKeyPEM) @@ -163,7 +163,7 @@ func (certSuite) TestWithNonUTCExpiry(c *gc.C) { expiry, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", "2012-11-28 15:53:57 +0100 CET") c.Assert(err, jc.ErrorIsNil) - certPEM, keyPEM, err := cert.NewCA("foo", expiry) + certPEM, keyPEM, err := cert.NewCA("foo", "1", expiry) xcert, err := cert.ParseCert(certPEM) c.Assert(err, jc.ErrorIsNil) checkNotAfter(c, xcert, expiry) @@ -185,7 +185,7 @@ func (certSuite) TestVerify(c *gc.C) { now := time.Now() - caCert, caKey, err := cert.NewCA("foo", now.Add(1*time.Minute)) + caCert, caKey, err := cert.NewCA("foo", "1", now.Add(1*time.Minute)) c.Assert(err, jc.ErrorIsNil) var noHostnames []string @@ -204,7 +204,7 @@ err = cert.Verify(srvCert, caCert, now.Add(2*time.Minute)) c.Check(err, gc.ErrorMatches, "x509: certificate has expired or is not yet valid") - caCert2, caKey2, err := cert.NewCA("bar", now.Add(1*time.Minute)) + caCert2, caKey2, err := cert.NewCA("bar", "1", now.Add(1*time.Minute)) c.Assert(err, jc.ErrorIsNil) // Check original server certificate against wrong CA. diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/charmid.go charm-2.2.0/src/github.com/juju/juju/charmstore/charmid.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/charmid.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/charmid.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,16 @@ +package charmstore + +import ( + "gopkg.in/juju/charm.v6-unstable" + csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" +) + +// CharmID is a type that encapsulates all the data required to interact with a +// unique charm from the charmstore. +type CharmID struct { + // URL is the url of the charm. + URL *charm.URL + + // Channel is the channel in which the charm was published. + Channel csparams.Channel +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/client.go charm-2.2.0/src/github.com/juju/juju/charmstore/client.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/client.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/client.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,41 +5,300 @@ import ( "io" + "net/http" + "net/url" + "github.com/juju/errors" + "github.com/juju/loggo" "gopkg.in/juju/charm.v6-unstable" charmresource "gopkg.in/juju/charm.v6-unstable/resource" + "gopkg.in/juju/charmrepo.v2-unstable/csclient" + csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" ) -// Client exposes the functionality of the charm store, as provided -// by github.com/juju/charmrepo/csclient.Client. -// -// Note that the following csclient.Client methods are used as well, -// but only in tests: -// - Put(path string, val interface{}) error -// - UploadCharm(id *charm.URL, ch charm.Charm) (*charm.URL, error) -// - UploadCharmWithRevision(id *charm.URL, ch charm.Charm, promulgatedRevision int) error -// - UploadBundleWithRevision() -type Client interface { - // TODO(ericsnow) Replace use of Get with use of more specific API methods? - - // Get makes a GET request to the given path in the charm store. The - // path must have a leading slash, but must not include the host - // name or version prefix. The result is parsed as JSON into the - // given result value, which should be a pointer to the expected - // data, but may be nil if no result is desired. - Get(path string, result interface{}) error - - // TODO(ericsnow) Just embed resource/charmstore.BaseClient? - - // ListResources composes, for each of the identified charms, the - // list of details for each of the charm's resources. Those details - // are those associated with the specific charm revision. They - // include the resource's metadata and revision. - ListResources(charmURLs []*charm.URL) ([][]charmresource.Resource, error) - - // GetResource returns a reader for the resource's data. That data - // is streamed from the charm store. The charm's revision, if any, - // is ignored. If the identified resource is not in the charm store - // then errors.NotFound is returned. - GetResource(cURL *charm.URL, resourceName string, revision int) (io.ReadCloser, error) +var logger = loggo.GetLogger("juju.charmstore") + +// TODO(natefinch): Ideally, this whole package would live in the +// charmstore-client repo, so as to keep it near the API it wraps (and make it +// more available to tools outside juju-core). + +// MacaroonCache represents a value that can store and retrieve macaroons for +// charms. It is used when we are requesting data from the charmstore for +// private charms. +type MacaroonCache interface { + Set(*charm.URL, macaroon.Slice) error + Get(*charm.URL) (macaroon.Slice, error) +} + +// NewCachingClient returns a Juju charm store client that stores and retrieves +// macaroons for calls in the given cache. If not nil, the client will use server +// as the charmstore url, otherwise it will default to the standard juju +// charmstore url. +func NewCachingClient(cache MacaroonCache, server *url.URL) (Client, error) { + return newCachingClient(cache, server, makeWrapper) +} + +func newCachingClient( + cache MacaroonCache, + server *url.URL, + makeWrapper func(*httpbakery.Client, *url.URL) csWrapper, +) (Client, error) { + bakeryClient := &httpbakery.Client{ + Client: httpbakery.NewHTTPClient(), + } + client := makeWrapper(bakeryClient, server) + server, err := url.Parse(client.ServerURL()) + if err != nil { + return Client{}, errors.Trace(err) + } + jar, err := newMacaroonJar(cache, server) + if err != nil { + return Client{}, errors.Trace(err) + } + bakeryClient.Jar = jar + return Client{client, jar}, nil +} + +// TODO(natefinch): we really shouldn't let something like a bakeryclient +// leak out of our abstraction like this. Instead, pass more salient details. + +// NewCustomClient returns a juju charmstore client that relies on the passed-in +// httpbakery.Client to store and retrieve macaroons. If not nil, the client +// will use server as the charmstore url, otherwise it will default to the +// standard juju charmstore url. +func NewCustomClient(bakeryClient *httpbakery.Client, server *url.URL) (Client, error) { + return newCustomClient(bakeryClient, server, makeWrapper) +} + +func newCustomClient( + bakeryClient *httpbakery.Client, + server *url.URL, + makeWrapper func(*httpbakery.Client, *url.URL) csWrapper, +) (Client, error) { + client := makeWrapper(bakeryClient, server) + return Client{csWrapper: client}, nil +} + +func makeWrapper(bakeryClient *httpbakery.Client, server *url.URL) csWrapper { + p := csclient.Params{ + BakeryClient: bakeryClient, + } + if server != nil { + p.URL = server.String() + } + return csclientImpl{csclient.New(p)} +} + +// Client wraps charmrepo/csclient (the charm store's API client +// library) in a higher level API. +type Client struct { + csWrapper + jar *macaroonJar +} + +// CharmRevision holds the data returned from the charmstore about the latest +// revision of a charm. Notet hat this may be different per channel. +type CharmRevision struct { + // Revision is newest revision for the charm. + Revision int + + // Err holds any error that occurred while making the request. + Err error +} + +// LatestRevisions returns the latest revisions of the given charms, using the given metadata. +func (c Client) LatestRevisions(charms []CharmID, metadata map[string][]string) ([]CharmRevision, error) { + // Due to the fact that we cannot use multiple macaroons per API call, + // we need to perform one call at a time, rather than making bulk calls. + // We could bulk the calls that use non-private charms, but we'd still need + // to do one bulk call per channel, due to how channels are used by the + // underlying csclient. + results := make([]CharmRevision, len(charms)) + for i, cid := range charms { + revisions, err := c.csWrapper.Latest(cid.Channel, []*charm.URL{cid.URL}, metadata) + if err != nil { + return nil, errors.Trace(err) + } + rev := revisions[0] + results[i] = CharmRevision{Revision: rev.Revision, Err: rev.Err} + } + return results, nil +} + +func (c Client) latestRevisions(channel csparams.Channel, cid CharmID, metadata map[string][]string) (CharmRevision, error) { + if err := c.jar.Activate(cid.URL); err != nil { + return CharmRevision{}, errors.Trace(err) + } + defer c.jar.Deactivate() + revisions, err := c.csWrapper.Latest(cid.Channel, []*charm.URL{cid.URL}, metadata) + if err != nil { + return CharmRevision{}, errors.Trace(err) + } + rev := revisions[0] + return CharmRevision{Revision: rev.Revision, Err: rev.Err}, nil +} + +// ResourceRequest is the data needed to request a resource from the charmstore. +type ResourceRequest struct { + // Charm is the URL of the charm for which we're requesting a resource. + Charm *charm.URL + + // Channel is the channel from which to request the resource info. + Channel csparams.Channel + + // Name is the name of the resource we're asking about. + Name string + + // Revision is the specific revision of the resource we're asking about. + Revision int +} + +// ResourceData represents the response from the charmstore about a request for +// resource bytes. +type ResourceData struct { + // ReadCloser holds the bytes for the resource. + io.ReadCloser + + // Resource holds the metadata for the resource. + Resource charmresource.Resource +} + +// GetResource returns the data (bytes) and metadata for a resource from the charmstore. +func (c Client) GetResource(req ResourceRequest) (data ResourceData, err error) { + if err := c.jar.Activate(req.Charm); err != nil { + return ResourceData{}, errors.Trace(err) + } + defer c.jar.Deactivate() + meta, err := c.csWrapper.ResourceMeta(req.Channel, req.Charm, req.Name, req.Revision) + + if err != nil { + return ResourceData{}, errors.Trace(err) + } + data.Resource, err = csparams.API2Resource(meta) + if err != nil { + return ResourceData{}, errors.Trace(err) + } + resData, err := c.csWrapper.GetResource(req.Channel, req.Charm, req.Name, req.Revision) + if err != nil { + return ResourceData{}, errors.Trace(err) + } + defer func() { + if err != nil { + resData.Close() + } + }() + data.ReadCloser = resData.ReadCloser + fpHash := data.Resource.Fingerprint.String() + if resData.Hash != fpHash { + return ResourceData{}, + errors.Errorf("fingerprint for data (%s) does not match fingerprint in metadata (%s)", resData.Hash, fpHash) + } + if resData.Size != data.Resource.Size { + return ResourceData{}, + errors.Errorf("size for data (%d) does not match size in metadata (%d)", resData.Size, data.Resource.Size) + } + return data, nil +} + +// ResourceInfo returns the metadata for the given resource from the charmstore. +func (c Client) ResourceInfo(req ResourceRequest) (charmresource.Resource, error) { + if err := c.jar.Activate(req.Charm); err != nil { + return charmresource.Resource{}, errors.Trace(err) + } + defer c.jar.Deactivate() + meta, err := c.csWrapper.ResourceMeta(req.Channel, req.Charm, req.Name, req.Revision) + if err != nil { + return charmresource.Resource{}, errors.Trace(err) + } + res, err := csparams.API2Resource(meta) + if err != nil { + return charmresource.Resource{}, errors.Trace(err) + } + return res, nil +} + +// ListResources returns a list of resources for each of the given charms. +func (c Client) ListResources(charms []CharmID) ([][]charmresource.Resource, error) { + results := make([][]charmresource.Resource, len(charms)) + for i, ch := range charms { + res, err := c.listResources(ch) + if err != nil { + if csclient.IsAuthorizationError(err) || errors.Cause(err) == csparams.ErrNotFound { + // Ignore authorization errors and not-found errors so we get some results + // even if others fail. + continue + } + return nil, errors.Trace(err) + } + results[i] = res + } + return results, nil +} + +func (c Client) listResources(ch CharmID) ([]charmresource.Resource, error) { + if err := c.jar.Activate(ch.URL); err != nil { + return nil, errors.Trace(err) + } + defer c.jar.Deactivate() + resources, err := c.csWrapper.ListResources(ch.Channel, ch.URL) + if err != nil { + return nil, errors.Trace(err) + } + return api2resources(resources) +} + +// csWrapper is a type that abstracts away the low-level implementation details +// of the charmstore client. +type csWrapper interface { + Latest(channel csparams.Channel, ids []*charm.URL, headers map[string][]string) ([]csparams.CharmRevision, error) + ListResources(channel csparams.Channel, id *charm.URL) ([]csparams.Resource, error) + GetResource(channel csparams.Channel, id *charm.URL, name string, revision int) (csclient.ResourceData, error) + ResourceMeta(channel csparams.Channel, id *charm.URL, name string, revision int) (csparams.Resource, error) + ServerURL() string +} + +// csclientImpl is an implementation of csWrapper that uses csclient.Client. +// It exists for testing purposes to hide away the hard-to-mock parts of +// csclient.Client. +type csclientImpl struct { + *csclient.Client +} + +// Latest gets the latest CharmRevisions for the charm URLs on the channel. +func (c csclientImpl) Latest(channel csparams.Channel, ids []*charm.URL, metadata map[string][]string) ([]csparams.CharmRevision, error) { + client := c.WithChannel(channel) + client.SetHTTPHeader(http.Header(metadata)) + return client.Latest(ids) +} + +// ListResources gets the latest resources for the charm URL on the channel. +func (c csclientImpl) ListResources(channel csparams.Channel, id *charm.URL) ([]csparams.Resource, error) { + client := c.WithChannel(channel) + return client.ListResources(id) +} + +// Getresource downloads the bytes and some metadata about the bytes for the revisioned resource. +func (c csclientImpl) GetResource(channel csparams.Channel, id *charm.URL, name string, revision int) (csclient.ResourceData, error) { + client := c.WithChannel(channel) + return client.GetResource(id, name, revision) +} + +// ResourceInfo gets the full metadata for the revisioned resource. +func (c csclientImpl) ResourceMeta(channel csparams.Channel, id *charm.URL, name string, revision int) (csparams.Resource, error) { + client := c.WithChannel(channel) + return client.ResourceMeta(id, name, revision) +} + +func api2resources(res []csparams.Resource) ([]charmresource.Resource, error) { + result := make([]charmresource.Resource, len(res)) + for i, r := range res { + var err error + result[i], err = csparams.API2Resource(r) + if err != nil { + return nil, errors.Trace(err) + } + } + return result, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/client_test.go charm-2.2.0/src/github.com/juju/juju/charmstore/client_test.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/client_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/client_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,244 @@ +package charmstore + +import ( + "io/ioutil" + "strings" + + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/juju/charm.v6-unstable" + "gopkg.in/juju/charm.v6-unstable/resource" + "gopkg.in/juju/charmrepo.v2-unstable/csclient" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" +) + +var _ = gc.Suite(&ClientSuite{}) + +type ClientSuite struct { + testing.IsolationSuite + wrapper *fakeWrapper + cache *fakeMacCache +} + +func (s *ClientSuite) SetUpTest(c *gc.C) { + s.IsolationSuite.SetUpTest(c) + + s.wrapper = &fakeWrapper{ + stub: &testing.Stub{}, + stableStub: &testing.Stub{}, + devStub: &testing.Stub{}, + } + + s.cache = &fakeMacCache{ + stub: &testing.Stub{}, + } +} + +func (s *ClientSuite) TestLatestRevisions(c *gc.C) { + s.wrapper.ReturnLatestStable = [][]params.CharmRevision{{{ + Revision: 1, + Sha256: "abc", + }}} + s.wrapper.ReturnLatestDev = [][]params.CharmRevision{{{ + Revision: 2, + Sha256: "cde", + }}, {{ + Revision: 3, + Sha256: "fgh", + }}} + + client, err := newCachingClient(s.cache, nil, s.wrapper.makeWrapper) + c.Assert(err, jc.ErrorIsNil) + + foo := charm.MustParseURL("cs:quantal/foo-1") + bar := charm.MustParseURL("cs:quantal/bar-1") + baz := charm.MustParseURL("cs:quantal/baz-1") + + ret, err := client.LatestRevisions([]CharmID{{ + URL: foo, + Channel: params.StableChannel, + }, { + URL: bar, + Channel: params.DevelopmentChannel, + }, { + URL: baz, + Channel: params.DevelopmentChannel, + }}, nil) + c.Assert(err, jc.ErrorIsNil) + expected := []CharmRevision{{ + Revision: 1, + }, { + Revision: 2, + }, { + Revision: 3, + }} + c.Check(ret, jc.SameContents, expected) + s.wrapper.stableStub.CheckCall(c, 0, "Latest", params.StableChannel, []*charm.URL{foo}, map[string][]string(nil)) + s.wrapper.devStub.CheckCall(c, 0, "Latest", params.DevelopmentChannel, []*charm.URL{bar}, map[string][]string(nil)) + s.wrapper.devStub.CheckCall(c, 1, "Latest", params.DevelopmentChannel, []*charm.URL{baz}, map[string][]string(nil)) +} + +func (s *ClientSuite) TestListResources(c *gc.C) { + fp, err := resource.GenerateFingerprint(strings.NewReader("data")) + c.Assert(err, jc.ErrorIsNil) + + stable := params.Resource{ + Name: "name", + Type: "file", + Path: "foo.zip", + Description: "something", + Origin: "store", + Revision: 5, + Fingerprint: fp.Bytes(), + Size: 4, + } + dev := params.Resource{ + Name: "name2", + Type: "file", + Path: "bar.zip", + Description: "something", + Origin: "store", + Revision: 7, + Fingerprint: fp.Bytes(), + Size: 4, + } + dev2 := params.Resource{ + Name: "name3", + Type: "file", + Path: "bar.zip", + Description: "something", + Origin: "store", + Revision: 8, + Fingerprint: fp.Bytes(), + Size: 4, + } + + s.wrapper.ReturnListResourcesStable = []resourceResult{oneResourceResult(stable), resourceResult{err: params.ErrNotFound}} + s.wrapper.ReturnListResourcesDev = []resourceResult{oneResourceResult(dev), oneResourceResult(dev2)} + + client, err := newCachingClient(s.cache, nil, s.wrapper.makeWrapper) + c.Assert(err, jc.ErrorIsNil) + + foo := charm.MustParseURL("cs:quantal/foo-1") + bar := charm.MustParseURL("cs:quantal/bar-1") + baz := charm.MustParseURL("cs:quantal/baz-1") + + ret, err := client.ListResources([]CharmID{{ + URL: foo, + Channel: params.StableChannel, + }, { + URL: bar, + Channel: params.DevelopmentChannel, + }, { + URL: baz, + Channel: params.DevelopmentChannel, + }}) + c.Assert(err, jc.ErrorIsNil) + + stableOut, err := params.API2Resource(stable) + c.Assert(err, jc.ErrorIsNil) + + devOut, err := params.API2Resource(dev) + c.Assert(err, jc.ErrorIsNil) + + dev2Out, err := params.API2Resource(dev2) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(ret, gc.DeepEquals, [][]resource.Resource{ + {stableOut}, + {devOut}, + {dev2Out}, + }) + s.wrapper.stableStub.CheckCall(c, 0, "ListResources", params.StableChannel, foo) + s.wrapper.devStub.CheckCall(c, 0, "ListResources", params.DevelopmentChannel, bar) + s.wrapper.devStub.CheckCall(c, 1, "ListResources", params.DevelopmentChannel, baz) +} + +func (s *ClientSuite) TestListResourcesError(c *gc.C) { + s.wrapper.ReturnListResourcesStable = []resourceResult{resourceResult{err: errors.NotFoundf("another error")}} + client, err := newCachingClient(s.cache, nil, s.wrapper.makeWrapper) + c.Assert(err, jc.ErrorIsNil) + + ret, err := client.ListResources([]CharmID{{ + URL: charm.MustParseURL("cs:quantal/foo-1"), + Channel: params.StableChannel, + }}) + c.Assert(err, gc.ErrorMatches, `another error not found`) + c.Assert(ret, gc.IsNil) +} + +func (s *ClientSuite) TestGetResource(c *gc.C) { + fp, err := resource.GenerateFingerprint(strings.NewReader("data")) + c.Assert(err, jc.ErrorIsNil) + rc := ioutil.NopCloser(strings.NewReader("data")) + s.wrapper.ReturnGetResource = csclient.ResourceData{ + ReadCloser: rc, + Hash: fp.String(), + Size: 4, + } + apiRes := params.Resource{ + Name: "name", + Type: "file", + Path: "foo.zip", + Description: "something", + Origin: "store", + Revision: 5, + Fingerprint: fp.Bytes(), + Size: 4, + } + s.wrapper.ReturnResourceMeta = apiRes + + client, err := newCachingClient(s.cache, nil, s.wrapper.makeWrapper) + c.Assert(err, jc.ErrorIsNil) + + req := ResourceRequest{ + Charm: charm.MustParseURL("cs:mysql"), + Channel: params.DevelopmentChannel, + Name: "name", + Revision: 5, + } + data, err := client.GetResource(req) + c.Assert(err, jc.ErrorIsNil) + expected, err := params.API2Resource(apiRes) + c.Assert(err, jc.ErrorIsNil) + c.Check(data.Resource, gc.DeepEquals, expected) + c.Check(data.ReadCloser, gc.DeepEquals, rc) + // call #0 is a call to makeWrapper + s.wrapper.stub.CheckCall(c, 1, "ResourceMeta", params.DevelopmentChannel, req.Charm, req.Name, req.Revision) + s.wrapper.stub.CheckCall(c, 2, "GetResource", params.DevelopmentChannel, req.Charm, req.Name, req.Revision) +} + +func (s *ClientSuite) TestResourceInfo(c *gc.C) { + fp, err := resource.GenerateFingerprint(strings.NewReader("data")) + c.Assert(err, jc.ErrorIsNil) + apiRes := params.Resource{ + Name: "name", + Type: "file", + Path: "foo.zip", + Description: "something", + Origin: "store", + Revision: 5, + Fingerprint: fp.Bytes(), + Size: 4, + } + s.wrapper.ReturnResourceMeta = apiRes + + client, err := newCachingClient(s.cache, nil, s.wrapper.makeWrapper) + c.Assert(err, jc.ErrorIsNil) + + req := ResourceRequest{ + Charm: charm.MustParseURL("cs:mysql"), + Channel: params.StableChannel, + Name: "name", + Revision: 5, + } + res, err := client.ResourceInfo(req) + c.Assert(err, jc.ErrorIsNil) + expected, err := params.API2Resource(apiRes) + c.Assert(err, jc.ErrorIsNil) + c.Check(res, gc.DeepEquals, expected) + // call #0 is a call to makeWrapper + s.wrapper.stub.CheckCall(c, 1, "ResourceMeta", params.StableChannel, req.Charm, req.Name, req.Revision) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/info.go charm-2.2.0/src/github.com/juju/juju/charmstore/info.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/info.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/info.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,50 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charmstore + +import ( + "time" + + "gopkg.in/juju/charm.v6-unstable" + charmresource "gopkg.in/juju/charm.v6-unstable/resource" +) + +// CharmInfo holds the information about a charm from the charm store. +// The info relates to the charm at a particular revision at the time +// the charm store handled the request. The resource revisions +// associated with the charm at that revision may change at any time. +// Note, however, that the set of resource names remains fixed for any +// given charm revision. +type CharmInfo struct { + // OriginalURL is charm URL, including its revision, for which we + // queried the charm store. + OriginalURL *charm.URL + + // Timestamp indicates when the info came from the charm store. + Timestamp time.Time + + // LatestRevision identifies the most recent revision of the charm + // that is available in the charm store. + LatestRevision int + + // LatestResources is the list of resource info for each of the + // charm's resources. This list is accurate as of the time that the + // charm store handled the request for the charm info. + LatestResources []charmresource.Resource +} + +// LatestURL returns the charm URL for the latest revision of the charm. +func (info CharmInfo) LatestURL() *charm.URL { + return info.OriginalURL.WithRevision(info.LatestRevision) +} + +// CharmInfoResult holds the result of a charm store request for info +// about a charm. +type CharmInfoResult struct { + CharmInfo + + // Error indicates a problem retrieving or processing the info + // for this charm. + Error error +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/info_test.go charm-2.2.0/src/github.com/juju/juju/charmstore/info_test.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/info_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/info_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,30 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charmstore_test + +import ( + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/juju/charm.v6-unstable" + + "github.com/juju/juju/charmstore" +) + +type CharmInfoSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&CharmInfoSuite{}) + +func (CharmInfoSuite) TestLatestURL(c *gc.C) { + info := charmstore.CharmInfo{ + OriginalURL: charm.MustParseURL("cs:quantal/mysql-3"), + LatestRevision: 17, + } + + latestURL := info.LatestURL() + + c.Check(latestURL, jc.DeepEquals, charm.MustParseURL("cs:quantal/mysql-17")) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/jar.go charm-2.2.0/src/github.com/juju/juju/charmstore/jar.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/jar.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/jar.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,166 @@ +package charmstore + +import ( + "net/http" + "net/http/cookiejar" + "net/url" + + "github.com/juju/errors" + "gopkg.in/juju/charm.v6-unstable" + "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" +) + +// newMacaroonJar returns a new macaroonJar wrapping the given cache and +// expecting to be used against the given URL. Both the cache and url must +// be non-nil. +func newMacaroonJar(cache MacaroonCache, serverURL *url.URL) (*macaroonJar, error) { + jar, err := cookiejar.New(nil) + if err != nil { + return nil, errors.Trace(err) + } + return &macaroonJar{ + underlying: jar, + cache: cache, + serverURL: serverURL, + }, nil +} + +// macaroonJar is a special form of http.CookieJar that uses a backing +// MacaroonCache to populate the jar and store updated macaroons. +// This is a fairly specifically crafted type in order to deal with the fact +// that gopkg.in/juju/charmrepo.v2-unstable/csclient.Client does all the work +// of handling updated macaroons. If a request with a macaroon returns with a +// DischargeRequiredError, csclient.Client will discharge the returned +// macaroon's caveats, and then save the final macaroon in the cookiejar, then +// retry the request. This type handles populating the macaroon from the +// macaroon cache (which for Juju's purposes will be a wrapper around state), +// and then responds to csclient's setcookies call to save the new macaroon +// into state for the appropriate charm. +// +// Note that Activate and Deactivate are not safe to call concurrently. +type macaroonJar struct { + underlying http.CookieJar + cache MacaroonCache + serverURL *url.URL + currentCharm *charm.URL + err error +} + +// Activate empties the cookiejar and loads the macaroon for the given charm +// (if any) into the cookiejar, avoiding the mechanism in SetCookies +// that records new macaroons. This also enables the functionality of storing +// macaroons in SetCookies. +// If the macaroonJar is nil, this is NOP. +func (j *macaroonJar) Activate(cURL *charm.URL) error { + if j == nil { + return nil + } + if err := j.reset(); err != nil { + return errors.Trace(err) + } + j.currentCharm = cURL + + m, err := j.cache.Get(cURL) + if err != nil { + return errors.Trace(err) + } + if m != nil { + httpbakery.SetCookie(j.underlying, j.serverURL, m) + } + return nil +} + +// Deactivate empties the cookiejar and disables the functionality of storing +// macaroons in SetCookies. +// If the macaroonJar is nil, this is NOP. +func (j *macaroonJar) Deactivate() error { + if j == nil { + return nil + } + return j.reset() +} + +// reset empties the cookiejar and disables the functionality of storing +// macaroons in SetCookies. +func (j *macaroonJar) reset() error { + j.err = nil + j.currentCharm = nil + + // clear out the cookie jar to ensure we don't have any cruft left over. + underlying, err := cookiejar.New(nil) + if err != nil { + // currently this is impossible, since the above never actually + // returns an error + return errors.Trace(err) + } + j.underlying = underlying + return nil +} + +// SetCookies handles the receipt of the cookies in a reply for the +// given URL. Cookies do not persist past a successive call to Activate or +// Deactivate. If the jar is currently activated, macaroons set via this method +// will be stored in the underlying MacaroonCache for the currently activated +// charm. +func (j *macaroonJar) SetCookies(u *url.URL, cookies []*http.Cookie) { + j.underlying.SetCookies(u, cookies) + + if j.currentCharm == nil { + // nothing else to do + return + } + + mac, err := extractMacaroon(cookies) + if err != nil { + j.err = errors.Trace(err) + logger.Errorf(err.Error()) + return + } + if mac == nil { + return + } + if err := j.cache.Set(j.currentCharm, mac); err != nil { + j.err = errors.Trace(err) + logger.Errorf("Failed to store macaroon for %s: %s", j.currentCharm, err) + } +} + +// Cookies returns the cookies stored in the underlying cookiejar. +func (j macaroonJar) Cookies(u *url.URL) []*http.Cookie { + return j.underlying.Cookies(u) +} + +// Error returns any error encountered during SetCookies. +func (j *macaroonJar) Error() error { + if j == nil { + return nil + } + return j.err +} + +func extractMacaroon(cookies []*http.Cookie) (macaroon.Slice, error) { + macs := httpbakery.MacaroonsForURL(jarFromCookies(cookies), nil) + switch len(macs) { + case 0: + // no macaroons in cookies, that's ok. + return nil, nil + case 1: + // hurray! + return macs[0], nil + default: + return nil, errors.Errorf("Expected exactly one macaroon, received %d", len(macs)) + } +} + +// jarFromCookies is a bit of sleight of hand to get the cookies we already +// have to be in the form of a http.CookieJar that is suitable for using with +// the httpbakery's MacaroonsForURL function, which expects to extract +// the cookies from a cookiejar first. +type jarFromCookies []*http.Cookie + +func (jarFromCookies) SetCookies(_ *url.URL, _ []*http.Cookie) {} + +func (j jarFromCookies) Cookies(_ *url.URL) []*http.Cookie { + return []*http.Cookie(j) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/jar_test.go charm-2.2.0/src/github.com/juju/juju/charmstore/jar_test.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/jar_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/jar_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,64 @@ +package charmstore + +import ( + "net/url" + + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/juju/charm.v6-unstable" + "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" +) + +var _ = gc.Suite(&MacaroonJarSuite{}) + +type MacaroonJarSuite struct { + testing.IsolationSuite +} + +func (MacaroonJarSuite) TestActivate(c *gc.C) { + cache := fakeCache{} + u, err := url.Parse("http://charmstore.com") + c.Assert(err, jc.ErrorIsNil) + jar, err := newMacaroonJar(cache, u) + c.Assert(err, jc.ErrorIsNil) + ch := charm.MustParseURL("cs:mysql") + err = jar.Activate(ch) + c.Assert(err, jc.ErrorIsNil) + m, err := macaroon.New([]byte("key"), "id", "loc") + c.Assert(err, jc.ErrorIsNil) + ms := macaroon.Slice{m} + httpbakery.SetCookie(jar, u, ms) + c.Assert(cache[ch], gc.DeepEquals, ms) +} + +func (MacaroonJarSuite) TestDeactivate(c *gc.C) { + cache := fakeCache{} + u, err := url.Parse("http://charmstore.com") + c.Assert(err, jc.ErrorIsNil) + jar, err := newMacaroonJar(cache, u) + c.Assert(err, jc.ErrorIsNil) + ch := charm.MustParseURL("cs:mysql") + err = jar.Activate(ch) + c.Assert(err, jc.ErrorIsNil) + m, err := macaroon.New([]byte("key"), "id", "loc") + c.Assert(err, jc.ErrorIsNil) + ms := macaroon.Slice{m} + err = jar.Deactivate() + c.Assert(err, jc.ErrorIsNil) + httpbakery.SetCookie(jar, u, ms) + c.Assert(cache, gc.HasLen, 0) + c.Assert(jar.Cookies(u), gc.HasLen, 1) +} + +type fakeCache map[*charm.URL]macaroon.Slice + +func (f fakeCache) Set(u *charm.URL, m macaroon.Slice) error { + f[u] = m + return nil +} + +func (f fakeCache) Get(u *charm.URL) (macaroon.Slice, error) { + return f[u], nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/latest.go charm-2.2.0/src/github.com/juju/juju/charmstore/latest.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/latest.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/latest.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,58 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charmstore + +import ( + "time" + + "github.com/juju/errors" +) + +const jujuMetadataHTTPHeader = "Juju-Metadata" + +// LatestCharmInfo returns the most up-to-date information about each +// of the identified charms at their latest revision. The revisions in +// the provided URLs are ignored. The returned map indicates charm URLs where +// the macaroon has been updated. This updated macaroon should be stored for +// use in any further requests. Note that this map may be non-empty even if +// this method returns an error (and the macaroons should be stored). +func LatestCharmInfo(client Client, charms []CharmID, modelUUID string) ([]CharmInfoResult, error) { + now := time.Now().UTC() + // Do a bulk call to get the revision info for all charms. + logger.Infof("retrieving revision information for %d charms", len(charms)) + revResults, err := client.LatestRevisions(charms, map[string][]string{ + jujuMetadataHTTPHeader: []string{"environment_uuid=" + modelUUID}, + }) + if err != nil { + err = errors.Annotate(err, "while getting latest charm revision info") + logger.Infof(err.Error()) + return nil, err + } + + // Do a bulk call to get the latest info for each charm's resources. + // TODO(ericsnow) Only do this for charms that *have* resources. + chResources, err := client.ListResources(charms) + if err != nil { + return nil, errors.Trace(err) + } + + // Extract the results. + var results []CharmInfoResult + for i, ch := range charms { + revResult := revResults[i] + resources := chResources[i] + + var result CharmInfoResult + result.OriginalURL = ch.URL + result.Timestamp = now + if revResult.Err != nil { + result.Error = errors.Trace(revResult.Err) + } else { + result.LatestRevision = revResult.Revision + result.LatestResources = resources + } + results = append(results, result) + } + return results, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/latest_test.go charm-2.2.0/src/github.com/juju/juju/charmstore/latest_test.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/latest_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/latest_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,116 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charmstore + +import ( + "sort" + + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/juju/charm.v6-unstable" + charmresource "gopkg.in/juju/charm.v6-unstable/resource" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" +) + +type LatestCharmInfoSuite struct { + testing.IsolationSuite + + lowLevel *fakeWrapper + cache *fakeMacCache +} + +var _ = gc.Suite(&LatestCharmInfoSuite{}) + +func (s *LatestCharmInfoSuite) SetUpTest(c *gc.C) { + s.IsolationSuite.SetUpTest(c) + + s.lowLevel = &fakeWrapper{ + stub: &testing.Stub{}, + stableStub: &testing.Stub{}, + devStub: &testing.Stub{}, + } + + s.cache = &fakeMacCache{ + stub: &testing.Stub{}, + } +} + +func (s *LatestCharmInfoSuite) TestSuccess(c *gc.C) { + spam := charm.MustParseURL("cs:quantal/spam-17") + eggs := charm.MustParseURL("cs:quantal/eggs-2") + ham := charm.MustParseURL("cs:quantal/ham-1") + charms := []CharmID{ + {URL: spam, Channel: "stable"}, + {URL: eggs, Channel: "stable"}, + {URL: ham, Channel: "stable"}, + } + notFound := errors.New("not found") + s.lowLevel.ReturnLatestStable = [][]params.CharmRevision{{{ + Revision: 17, + }}, {{ + Revision: 3, + }}, {{ + Err: notFound, + }}} + + fakeRes := fakeParamsResource("foo", nil) + + s.lowLevel.ReturnListResourcesStable = []resourceResult{ + oneResourceResult(fakeRes), + resourceResult{err: params.ErrNotFound}, + resourceResult{err: params.ErrUnauthorized}, + } + + client, err := newCachingClient(s.cache, nil, s.lowLevel.makeWrapper) + c.Assert(err, jc.ErrorIsNil) + + results, err := LatestCharmInfo(client, charms, "foobar") + c.Assert(err, jc.ErrorIsNil) + + s.lowLevel.stableStub.CheckCall(c, 0, "Latest", params.StableChannel, []*charm.URL{spam}, map[string][]string{"Juju-Metadata": []string{"environment_uuid=foobar"}}) + s.lowLevel.stableStub.CheckCall(c, 1, "Latest", params.StableChannel, []*charm.URL{eggs}, map[string][]string{"Juju-Metadata": []string{"environment_uuid=foobar"}}) + s.lowLevel.stableStub.CheckCall(c, 2, "Latest", params.StableChannel, []*charm.URL{ham}, map[string][]string{"Juju-Metadata": []string{"environment_uuid=foobar"}}) + s.lowLevel.stableStub.CheckCall(c, 3, "ListResources", params.StableChannel, spam) + s.lowLevel.stableStub.CheckCall(c, 4, "ListResources", params.StableChannel, eggs) + s.lowLevel.stableStub.CheckCall(c, 5, "ListResources", params.StableChannel, ham) + + expectedRes, err := params.API2Resource(fakeRes) + c.Assert(err, jc.ErrorIsNil) + + timestamp := results[0].Timestamp + results[2].Error = errors.Cause(results[2].Error) + expected := []CharmInfoResult{{ + CharmInfo: CharmInfo{ + OriginalURL: charm.MustParseURL("cs:quantal/spam-17"), + Timestamp: timestamp, + LatestRevision: 17, + LatestResources: []charmresource.Resource{ + expectedRes, + }, + }, + }, { + CharmInfo: CharmInfo{ + OriginalURL: charm.MustParseURL("cs:quantal/eggs-2"), + Timestamp: timestamp, + LatestRevision: 3, + }, + }, { + CharmInfo: CharmInfo{ + OriginalURL: charm.MustParseURL("cs:quantal/ham-1"), + Timestamp: timestamp, + }, + Error: notFound, + }} + sort.Sort(byURL(results)) + sort.Sort(byURL(expected)) + c.Check(results, jc.DeepEquals, expected) +} + +type byURL []CharmInfoResult + +func (b byURL) Len() int { return len(b) } +func (b byURL) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b byURL) Less(i, j int) bool { return b[i].OriginalURL.String() < b[j].OriginalURL.String() } diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/package_test.go charm-2.2.0/src/github.com/juju/juju/charmstore/package_test.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/package_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charmstore_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *testing.T) { + gc.TestingT(t) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/charmstore/stub_test.go charm-2.2.0/src/github.com/juju/juju/charmstore/stub_test.go --- charm-2.1.1/src/github.com/juju/juju/charmstore/stub_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/charmstore/stub_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,136 @@ +package charmstore + +import ( + "bytes" + "net/url" + + "github.com/juju/testing" + "gopkg.in/juju/charm.v6-unstable" + "gopkg.in/juju/charm.v6-unstable/resource" + "gopkg.in/juju/charmrepo.v2-unstable/csclient" + "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" + "gopkg.in/macaroon-bakery.v1/httpbakery" + "gopkg.in/macaroon.v1" +) + +var _ csWrapper = (*fakeWrapper)(nil) + +type resourceResult struct { + resources []params.Resource + err error +} + +func oneResourceResult(r params.Resource) resourceResult { + return resourceResult{ + resources: []params.Resource{r}, + } +} + +// fakeWrapper is an implementation of the csWrapper interface for use with +// testing the Client. +// We store the args & returns per channel, since we're running off +// map iteration and don't know the order they'll be requested in. +// The code assumes there are only two channels used - "stable" and +// "development". +type fakeWrapper struct { + server *url.URL + + stub *testing.Stub + stableStub *testing.Stub + devStub *testing.Stub + + ReturnLatestStable [][]params.CharmRevision + ReturnLatestDev [][]params.CharmRevision + + ReturnListResourcesStable []resourceResult + ReturnListResourcesDev []resourceResult + + ReturnGetResource csclient.ResourceData + + ReturnResourceMeta params.Resource +} + +func (f *fakeWrapper) makeWrapper(bakeryClient *httpbakery.Client, server *url.URL) csWrapper { + f.stub.AddCall("makeWrapper", bakeryClient, server) + return f +} + +func (f *fakeWrapper) ServerURL() string { + if f.server != nil { + return f.server.String() + } + return csclient.ServerURL +} + +// this code only returns the first value the return slices to support +func (f *fakeWrapper) Latest(channel params.Channel, ids []*charm.URL, headers map[string][]string) ([]params.CharmRevision, error) { + if channel == "stable" { + f.stableStub.AddCall("Latest", channel, ids, headers) + ret := f.ReturnLatestStable[0] + f.ReturnLatestStable = f.ReturnLatestStable[1:] + return ret, nil + } + f.devStub.AddCall("Latest", channel, ids, headers) + ret := f.ReturnLatestDev[0] + f.ReturnLatestDev = f.ReturnLatestDev[1:] + return ret, nil +} + +func (f *fakeWrapper) ListResources(channel params.Channel, id *charm.URL) ([]params.Resource, error) { + if channel == "stable" { + f.stableStub.AddCall("ListResources", channel, id) + ret := f.ReturnListResourcesStable[0] + f.ReturnListResourcesStable = f.ReturnListResourcesStable[1:] + return ret.resources, ret.err + } + f.devStub.AddCall("ListResources", channel, id) + ret := f.ReturnListResourcesDev[0] + f.ReturnListResourcesDev = f.ReturnListResourcesDev[1:] + return ret.resources, ret.err +} + +func (f *fakeWrapper) GetResource(channel params.Channel, id *charm.URL, name string, revision int) (csclient.ResourceData, error) { + f.stub.AddCall("GetResource", channel, id, name, revision) + return f.ReturnGetResource, nil +} + +func (f *fakeWrapper) ResourceMeta(channel params.Channel, id *charm.URL, name string, revision int) (params.Resource, error) { + f.stub.AddCall("ResourceMeta", channel, id, name, revision) + return f.ReturnResourceMeta, nil +} + +func fakeParamsResource(name string, data []byte) params.Resource { + fp, err := resource.GenerateFingerprint(bytes.NewReader(data)) + if err != nil { + panic(err) + } + return params.Resource{ + Name: name, + Type: "file", + Path: name + ".tgz", + Description: "something about " + name, + Origin: "store", + Revision: len(name), + Fingerprint: fp.Bytes(), + Size: int64(len(data)), + } +} + +type fakeMacCache struct { + stub *testing.Stub + + ReturnGet macaroon.Slice +} + +func (f *fakeMacCache) Set(u *charm.URL, m macaroon.Slice) error { + f.stub.AddCall("Set", u, m) + return f.stub.NextErr() +} + +func (f *fakeMacCache) Get(u *charm.URL) (macaroon.Slice, error) { + f.stub.AddCall("Get", u) + if err := f.stub.NextErr(); err != nil { + return nil, err + } + return f.ReturnGet, nil +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cloud/clouds_test.go charm-2.2.0/src/github.com/juju/juju/cloud/clouds_test.go --- charm-2.1.1/src/github.com/juju/juju/cloud/clouds_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloud/clouds_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -49,13 +49,13 @@ var regionNames []string for _, region := range rackspace.Regions { regionNames = append(regionNames, region.Name) - if region.Name == "LON" { + if region.Name == "lon" { c.Assert(region.Endpoint, gc.Equals, "https://lon.identity.api.rackspacecloud.com/v2.0") } else { c.Assert(region.Endpoint, gc.Equals, "https://identity.api.rackspacecloud.com/v2.0") } } - c.Assert(regionNames, jc.SameContents, []string{"DFW", "ORD", "IAD", "LON", "SYD", "HKG"}) + c.Assert(regionNames, jc.SameContents, []string{"dfw", "ord", "iad", "lon", "syd", "hkg"}) } func (s *cloudSuite) TestParseCloudsAuthTypes(c *gc.C) { diff -Nru charm-2.1.1/src/github.com/juju/juju/cloud/credentials.go charm-2.2.0/src/github.com/juju/juju/cloud/credentials.go --- charm-2.1.1/src/github.com/juju/juju/cloud/credentials.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloud/credentials.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,10 +5,13 @@ import ( "fmt" + "os" + "path/filepath" "strings" "github.com/juju/errors" "github.com/juju/schema" + "github.com/juju/utils" "gopkg.in/juju/environschema.v1" "gopkg.in/yaml.v2" ) @@ -30,8 +33,7 @@ authType AuthType attributes map[string]string - // Label is optionally set to describe the credentials - // to a user. + // Label is optionally set to describe the credentials to a user. Label string } @@ -79,9 +81,29 @@ return &CloudCredential{AuthCredentials: map[string]Credential{"default": NewEmptyCredential()}} } +// NamedCredentialAttr describes the properties of a named credential attribute. +type NamedCredentialAttr struct { + // Name is the name of the credential value. + Name string + + // CredentialAttr holds the properties of the credential value. + CredentialAttr +} + // CredentialSchema describes the schema of a credential. Credential schemas // are specific to cloud providers. -type CredentialSchema map[string]CredentialAttr +type CredentialSchema []NamedCredentialAttr + +// Attribute returns the named CredentialAttr value. +func (s CredentialSchema) Attribute(name string) (*CredentialAttr, bool) { + for _, value := range s { + if value.Name == name { + result := value.CredentialAttr + return &result, true + } + } + return nil, false +} // FinalizeCredential finalizes a credential by matching it with one of the // provided credential schemas, and reading any file attributes into their @@ -132,13 +154,25 @@ newAttrs := make(map[string]string) // Construct the final credential attributes map, reading values from files as necessary. - for name, field := range s { + for _, field := range s { if field.FileAttr != "" { - if err := s.processFileAttrValue(name, field, resultMap, newAttrs, readFile); err != nil { + if err := s.processFileAttrValue(field, resultMap, newAttrs, readFile); err != nil { return nil, errors.Trace(err) } continue } + name := field.Name + if field.FilePath { + pathValue, ok := resultMap[name] + if ok && pathValue != "" { + if absPath, err := ValidateFileAttrValue(pathValue.(string)); err != nil { + return nil, errors.Trace(err) + } else { + newAttrs[name] = absPath + continue + } + } + } if val, ok := resultMap[name]; ok { newAttrs[name] = val.(string) } @@ -146,10 +180,31 @@ return newAttrs, nil } +// ValidateFileAttrValue returns the normalised file path, so +// long as the specified path is valid and not a directory. +func ValidateFileAttrValue(path string) (string, error) { + if !filepath.IsAbs(path) && !strings.HasPrefix(path, "~") { + return "", errors.Errorf("file path must be an absolute path: %s", path) + } + absPath, err := utils.NormalizePath(path) + if err != nil { + return "", err + } + info, err := os.Stat(absPath) + if err != nil { + return "", errors.Errorf("invalid file path: %s", absPath) + } + if info.IsDir() { + return "", errors.Errorf("file path must be a file: %s", absPath) + } + return absPath, nil +} + func (s CredentialSchema) processFileAttrValue( - name string, field CredentialAttr, resultMap map[string]interface{}, newAttrs map[string]string, + field NamedCredentialAttr, resultMap map[string]interface{}, newAttrs map[string]string, readFile func(string) ([]byte, error), ) error { + name := field.Name if fieldVal, ok := resultMap[name]; ok { if _, ok := resultMap[field.FileAttr]; ok { return errors.NotValidf( @@ -180,8 +235,8 @@ func (s CredentialSchema) schemaChecker() (schema.Checker, error) { fields := make(environschema.Fields) - for name, field := range s { - fields[name] = environschema.Attr{ + for _, field := range s { + fields[field.Name] = environschema.Attr{ Description: field.Description, Type: environschema.Tstring, Group: environschema.AccountGroup, @@ -231,6 +286,9 @@ // value used for this attribute. FileAttr string + // FilePath is true is the value of this attribute is a file path. + FilePath bool + // Optional controls whether the attribute is required to have a non-empty // value or not. Attributes default to mandatory. Optional bool @@ -323,9 +381,9 @@ return nil, errors.NotSupportedf("auth-type %q", credential.authType) } redactedAttrs := credential.Attributes() - for attrName, attr := range schema { + for _, attr := range schema { if attr.Hidden { - delete(redactedAttrs, attrName) + delete(redactedAttrs, attr.Name) } } return &Credential{authType: credential.authType, attributes: redactedAttrs}, nil diff -Nru charm-2.1.1/src/github.com/juju/juju/cloud/credentials_test.go charm-2.2.0/src/github.com/juju/juju/cloud/credentials_test.go --- charm-2.1.1/src/github.com/juju/juju/cloud/credentials_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloud/credentials_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -4,6 +4,8 @@ package cloud_test import ( + "io/ioutil" + "path/filepath" "regexp" "github.com/juju/errors" @@ -317,9 +319,12 @@ }, ) schema := cloud.CredentialSchema{ - "key": { - Description: "key credential", - Hidden: true, + { + "key", + cloud.CredentialAttr{ + Description: "key credential", + Hidden: true, + }, }, } _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ @@ -337,13 +342,15 @@ }, ) schema := cloud.CredentialSchema{ - "key": { - Description: "key credential", - Hidden: true, - FileAttr: "key-file", - }, - "quay": { - FileAttr: "quay-file", + { + "key", + cloud.CredentialAttr{ + Description: "key credential", + Hidden: true, + FileAttr: "key-file", + }, + }, { + "quay", cloud.CredentialAttr{FileAttr: "quay-file"}, }, } readFile := func(s string) ([]byte, error) { @@ -368,10 +375,13 @@ }, ) schema := cloud.CredentialSchema{ - "key": { - Description: "key credential", - Hidden: true, - FileAttr: "key-file", + { + "key", + cloud.CredentialAttr{ + Description: "key credential", + Hidden: true, + FileAttr: "key-file", + }, }, } readFile := func(string) ([]byte, error) { @@ -389,10 +399,13 @@ map[string]string{}, ) schema := cloud.CredentialSchema{ - "key": { - Description: "key credential", - Hidden: true, - FileAttr: "key-file", + { + "key", + cloud.CredentialAttr{ + Description: "key credential", + Hidden: true, + FileAttr: "key-file", + }, }, } _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ @@ -410,10 +423,13 @@ }, ) schema := cloud.CredentialSchema{ - "key": { - Description: "key credential", - Hidden: true, - FileAttr: "key-file", + { + "key", + cloud.CredentialAttr{ + Description: "key credential", + Hidden: true, + FileAttr: "key-file", + }, }, } _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ @@ -428,9 +444,12 @@ map[string]string{}, ) schema := cloud.CredentialSchema{ - "key": { - Description: "key credential", - Hidden: true, + { + "key", + cloud.CredentialAttr{ + Description: "key credential", + Hidden: true, + }, }, } _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ @@ -464,13 +483,13 @@ }, ) schema := cloud.CredentialSchema{ - "username": { - Optional: false, + { + "username", cloud.CredentialAttr{Optional: false}, + }, { + "password", cloud.CredentialAttr{Hidden: true}, + }, { + "domain", cloud.CredentialAttr{}, }, - "password": { - Hidden: true, - }, - "domain": {}, } _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ cloud.UserPassAuthType: schema, @@ -486,10 +505,13 @@ }, ) schema := cloud.CredentialSchema{ - "key": { - Description: "key credential", - Optional: false, - FileAttr: "key-file", + { + "key", + cloud.CredentialAttr{ + Description: "key credential", + Optional: false, + FileAttr: "key-file", + }, }, } readFile := func(s string) ([]byte, error) { @@ -516,13 +538,13 @@ }, ) schema := cloud.CredentialSchema{ - "username": { - Optional: false, - }, - "password": { - Hidden: true, + { + "username", cloud.CredentialAttr{Optional: false}, + }, { + "password", cloud.CredentialAttr{Hidden: true}, + }, { + "domain", cloud.CredentialAttr{}, }, - "domain": {}, } _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ cloud.UserPassAuthType: schema, @@ -540,14 +562,12 @@ }, ) schema := cloud.CredentialSchema{ - "username": { - Optional: false, - }, - "password": { - Hidden: true, - }, - "algorithm": { - Options: []interface{}{"bar", "foobar"}, + { + "username", cloud.CredentialAttr{Optional: false}, + }, { + "password", cloud.CredentialAttr{Hidden: true}, + }, { + "algorithm", cloud.CredentialAttr{Options: []interface{}{"bar", "foobar"}}, }, } _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ @@ -556,6 +576,68 @@ c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`algorithm: expected one of [bar foobar], got "foo"`)) } +func (s *credentialsSuite) TestFinalizeCredentialFilePath(c *gc.C) { + dir := c.MkDir() + filename := filepath.Join(dir, "filename") + err := ioutil.WriteFile(filename, []byte{}, 0600) + c.Assert(err, jc.ErrorIsNil) + + cred := cloud.NewCredential( + cloud.JSONFileAuthType, + map[string]string{ + "file": filename, + }, + ) + schema := cloud.CredentialSchema{ + { + "file", cloud.CredentialAttr{FilePath: true}, + }, + } + newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ + cloud.JSONFileAuthType: schema, + }, nil) + c.Assert(err, jc.ErrorIsNil) + c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{ + "file": filename, + }) +} + +func (s *credentialsSuite) TestFinalizeCredentialInvalidFilePath(c *gc.C) { + cred := cloud.NewCredential( + cloud.JSONFileAuthType, + map[string]string{ + "file": filepath.Join(c.MkDir(), "somefile"), + }, + ) + schema := cloud.CredentialSchema{ + { + "file", cloud.CredentialAttr{FilePath: true}, + }, + } + _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ + cloud.JSONFileAuthType: schema, + }, nil) + c.Assert(err, gc.ErrorMatches, "invalid file path: .*") +} + +func (s *credentialsSuite) TestFinalizeCredentialRelativeFilePath(c *gc.C) { + cred := cloud.NewCredential( + cloud.JSONFileAuthType, + map[string]string{ + "file": "file", + }, + ) + schema := cloud.CredentialSchema{ + { + "file", cloud.CredentialAttr{FilePath: true}, + }, + } + _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ + cloud.JSONFileAuthType: schema, + }, nil) + c.Assert(err, gc.ErrorMatches, "file path must be an absolute path: file") +} + func (s *credentialsSuite) TestRemoveSecrets(c *gc.C) { cred := cloud.NewCredential( cloud.UserPassAuthType, @@ -565,9 +647,10 @@ }, ) schema := cloud.CredentialSchema{ - "username": {}, - "password": { - Hidden: true, + { + "username", cloud.CredentialAttr{}, + }, { + "password", cloud.CredentialAttr{Hidden: true}, }, } sanitisedCred, err := cloud.RemoveSecrets(cred, map[cloud.AuthType]cloud.CredentialSchema{ diff -Nru charm-2.1.1/src/github.com/juju/juju/cloud/fallback_public_cloud.go charm-2.2.0/src/github.com/juju/juju/cloud/fallback_public_cloud.go --- charm-2.1.1/src/github.com/juju/juju/cloud/fallback_public_cloud.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloud/fallback_public_cloud.go 2016-04-28 06:03:34.000000000 +0000 @@ -46,7 +46,7 @@ endpoint: https://ec2.us-gov-west-1.amazonaws-govcloud.com google: type: gce - auth-types: [ oauth2 ] + auth-types: [ jsonfile, oauth2 ] regions: us-east1: endpoint: https://www.googleapis.com @@ -129,17 +129,17 @@ auth-types: [ access-key, userpass ] endpoint: https://identity.api.rackspacecloud.com/v2.0 regions: - DFW: + dfw: endpoint: https://identity.api.rackspacecloud.com/v2.0 - ORD: + ord: endpoint: https://identity.api.rackspacecloud.com/v2.0 - IAD: + iad: endpoint: https://identity.api.rackspacecloud.com/v2.0 - LON: + lon: endpoint: https://lon.identity.api.rackspacecloud.com/v2.0 - SYD: + syd: endpoint: https://identity.api.rackspacecloud.com/v2.0 - HKG: + hkg: endpoint: https://identity.api.rackspacecloud.com/v2.0 joyent: type: joyent diff -Nru charm-2.1.1/src/github.com/juju/juju/cloud/fallback-public-cloud.yaml charm-2.2.0/src/github.com/juju/juju/cloud/fallback-public-cloud.yaml --- charm-2.1.1/src/github.com/juju/juju/cloud/fallback-public-cloud.yaml 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloud/fallback-public-cloud.yaml 2016-04-28 06:03:34.000000000 +0000 @@ -38,7 +38,7 @@ endpoint: https://ec2.us-gov-west-1.amazonaws-govcloud.com google: type: gce - auth-types: [ oauth2 ] + auth-types: [ jsonfile, oauth2 ] regions: us-east1: endpoint: https://www.googleapis.com @@ -121,17 +121,17 @@ auth-types: [ access-key, userpass ] endpoint: https://identity.api.rackspacecloud.com/v2.0 regions: - DFW: + dfw: endpoint: https://identity.api.rackspacecloud.com/v2.0 - ORD: + ord: endpoint: https://identity.api.rackspacecloud.com/v2.0 - IAD: + iad: endpoint: https://identity.api.rackspacecloud.com/v2.0 - LON: + lon: endpoint: https://lon.identity.api.rackspacecloud.com/v2.0 - SYD: + syd: endpoint: https://identity.api.rackspacecloud.com/v2.0 - HKG: + hkg: endpoint: https://identity.api.rackspacecloud.com/v2.0 joyent: type: joyent diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_centos.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_centos.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_centos.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_centos.go 2016-04-28 06:03:34.000000000 +0000 @@ -206,7 +206,6 @@ packages := []string{ "curl", "bridge-utils", - "rsyslog-gnutls", "cloud-utils", "nmap-ncat", "tmux", diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_ubuntu.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_ubuntu.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_ubuntu.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/cloudinit/cloudinit_ubuntu.go 2016-04-28 06:03:34.000000000 +0000 @@ -267,7 +267,6 @@ // Don't install bridge-utils in cloud-init; // leave it to the networker worker. "bridge-utils", - "rsyslog-gnutls", "cloud-utils", "cloud-image-utils", "tmux", diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/cloudinit/renderscript_test.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/cloudinit/renderscript_test.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/cloudinit/renderscript_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/cloudinit/renderscript_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -8,6 +8,7 @@ jc "github.com/juju/testing/checkers" "github.com/juju/utils/packaging" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/cloudconfig" @@ -20,7 +21,6 @@ "github.com/juju/juju/state/multiwatcher" coretesting "github.com/juju/juju/testing" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) type configureSuite struct { @@ -63,6 +63,9 @@ ) c.Assert(err, jc.ErrorIsNil) icfg.InstanceId = "instance-id" + icfg.HostedModelConfig = map[string]interface{}{ + "name": "hosted-model", + } icfg.Jobs = []multiwatcher.MachineJob{multiwatcher.JobManageModel, multiwatcher.JobHostUnits} } else { icfg, err = instancecfg.NewInstanceConfig("0", "ya", imagemetadata.ReleasedStream, vers.Series, "", true, nil, nil, nil) diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata.go 2016-04-28 06:03:34.000000000 +0000 @@ -21,6 +21,7 @@ "github.com/juju/juju/cloudconfig/instancecfg" "github.com/juju/juju/container" "github.com/juju/juju/environs" + "github.com/juju/juju/network" "github.com/juju/juju/service" "github.com/juju/juju/service/common" ) @@ -81,7 +82,26 @@ {{end}}{{range $nic := . }}{{if eq $nic.ConfigType "static"}} {{template "static" $nic}}{{else}}{{template "dhcp" $nic}}{{end}}{{end}}` -var networkInterfacesFile = "/etc/network/interfaces" +// multiBridgeNetworkConfigTemplate defines how to render /etc/network/interfaces +// file for a multi-NIC container. +const multiBridgeNetworkConfigTemplate = ` +auto lo +iface lo inet loopback +{{range $nic := .}}{{template "single" $nic}}{{end}} +{{define "single"}}{{if not .NoAutoStart}} +auto {{.InterfaceName}}{{end}} +iface {{.InterfaceName}} inet manual{{if .DNSServers}} + dns-nameservers{{range $srv := .DNSServers}} {{$srv.Value}}{{end}}{{end}}{{if .DNSSearchDomains}} + dns-search{{range $dom := .DNSSearchDomains}} {{$dom}}{{end}}{{end}} + pre-up ip address add {{.CIDRAddress}} dev {{.InterfaceName}} || true + up ip route replace {{.CIDR}} dev {{.InterfaceName}} || true + down ip route del {{.CIDR}} dev {{.InterfaceName}} || true + post-down address del {{.CIDRAddress}} dev {{.InterfaceName}} || true{{if .GatewayAddress.Value}} + up ip route replace default via {{.GatewayAddress.Value}} || true + down ip route del default via {{.GatewayAddress.Value}} || true{{end}} +{{end}}` + +var networkInterfacesFile = "/etc/network/interfaces.d/00-juju.cfg" // GenerateNetworkConfig renders a network config for one or more // network interfaces, using the given non-nil networkConfig @@ -92,19 +112,36 @@ logger.Tracef("no network config to generate") return "", nil } + logger.Debugf("generating network config from %#v", *networkConfig) + + // Copy the InterfaceInfo before modifying the original. + interfacesCopy := make([]network.InterfaceInfo, len(networkConfig.Interfaces)) + copy(interfacesCopy, networkConfig.Interfaces) + for i, info := range interfacesCopy { + if info.MACAddress != "" { + info.MACAddress = "" + } + if info.InterfaceName != "eth0" { + info.GatewayAddress = network.Address{} + } + interfacesCopy[i] = info + } // Render the config first. - tmpl, err := template.New("config").Parse(networkConfigTemplate) + tmpl, err := template.New("config").Parse(multiBridgeNetworkConfigTemplate) if err != nil { return "", errors.Annotate(err, "cannot parse network config template") } var buf bytes.Buffer - if err = tmpl.Execute(&buf, networkConfig.Interfaces); err != nil { + if err := tmpl.Execute(&buf, interfacesCopy); err != nil { return "", errors.Annotate(err, "cannot render network config") } - return buf.String(), nil + generatedConfig := buf.String() + logger.Debugf("generated network config from %#v\nusing%#v:\n%s", interfacesCopy, networkConfig.Interfaces, generatedConfig) + + return generatedConfig, nil } // newCloudInitConfigWithNetworks creates a cloud-init config which @@ -120,8 +157,8 @@ return cloudConfig, errors.Trace(err) } - // Now add it to cloud-init as a file created early in the boot process. cloudConfig.AddBootTextFile(networkInterfacesFile, config, 0644) + cloudConfig.AddRunCmd("ifup -a || true") return cloudConfig, nil } @@ -130,13 +167,15 @@ networkConfig *container.NetworkConfig, ) ([]byte, error) { cloudConfig, err := newCloudInitConfigWithNetworks(instanceConfig.Series, networkConfig) - udata, err := cloudconfig.NewUserdataConfig(instanceConfig, cloudConfig) if err != nil { - return nil, err + return nil, errors.Trace(err) } - err = udata.Configure() + udata, err := cloudconfig.NewUserdataConfig(instanceConfig, cloudConfig) if err != nil { - return nil, err + return nil, errors.Trace(err) + } + if err = udata.Configure(); err != nil { + return nil, errors.Trace(err) } // Run ifconfig to get the addresses of the internal container at least // logged in the host. @@ -148,7 +187,7 @@ data, err := cloudConfig.RenderYAML() if err != nil { - return nil, err + return nil, errors.Trace(err) } return data, nil } diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata_test.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata_test.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/containerinit/container_userdata_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -42,38 +42,60 @@ s.BaseSuite.SetUpTest(c) s.networkInterfacesFile = filepath.Join(c.MkDir(), "interfaces") s.fakeInterfaces = []network.InterfaceInfo{{ - InterfaceName: "eth0", - CIDR: "0.1.2.0/24", - ConfigType: network.ConfigStatic, - NoAutoStart: false, - Address: network.NewAddress("0.1.2.3"), - DNSServers: network.NewAddresses("ns1.invalid", "ns2.invalid"), - DNSSearch: "foo.bar", - GatewayAddress: network.NewAddress("0.1.2.1"), + InterfaceName: "eth0", + CIDR: "0.1.2.0/24", + ConfigType: network.ConfigStatic, + NoAutoStart: false, + Address: network.NewAddress("0.1.2.3"), + DNSServers: network.NewAddresses("ns1.invalid", "ns2.invalid"), + DNSSearchDomains: []string{"foo", "bar"}, + GatewayAddress: network.NewAddress("0.1.2.1"), + MACAddress: "aa:bb:cc:dd:ee:f0", }, { - InterfaceName: "eth1", + InterfaceName: "eth1", + CIDR: "0.1.2.0/24", + ConfigType: network.ConfigStatic, + NoAutoStart: false, + Address: network.NewAddress("0.1.2.4"), + DNSServers: network.NewAddresses("ns1.invalid", "ns2.invalid"), + DNSSearchDomains: []string{"foo", "bar"}, + GatewayAddress: network.NewAddress("0.1.2.1"), + MACAddress: "aa:bb:cc:dd:ee:f0", + }, { + InterfaceName: "eth2", ConfigType: network.ConfigDHCP, NoAutoStart: true, }} s.expectedNetConfig = ` -# loopback interface auto lo iface lo inet loopback -# interface "eth0" auto eth0 iface eth0 inet manual - dns-nameservers ns1.invalid ns2.invalid - dns-search foo.bar - pre-up ip address add 0.1.2.3/32 dev eth0 &> /dev/null || true - up ip route replace 0.1.2.1 dev eth0 - up ip route replace default via 0.1.2.1 - down ip route del default via 0.1.2.1 &> /dev/null || true - down ip route del 0.1.2.1 dev eth0 &> /dev/null || true - post-down ip address del 0.1.2.3/32 dev eth0 &> /dev/null || true + dns-nameservers ns1.invalid ns2.invalid + dns-search foo bar + pre-up ip address add 0.1.2.3/24 dev eth0 || true + up ip route replace 0.1.2.0/24 dev eth0 || true + down ip route del 0.1.2.0/24 dev eth0 || true + post-down address del 0.1.2.3/24 dev eth0 || true + up ip route replace default via 0.1.2.1 || true + down ip route del default via 0.1.2.1 || true + +auto eth1 +iface eth1 inet manual + dns-nameservers ns1.invalid ns2.invalid + dns-search foo bar + pre-up ip address add 0.1.2.4/24 dev eth1 || true + up ip route replace 0.1.2.0/24 dev eth1 || true + down ip route del 0.1.2.0/24 dev eth1 || true + post-down address del 0.1.2.4/24 dev eth1 || true + +iface eth2 inet manual + pre-up ip address add dev eth2 || true + up ip route replace dev eth2 || true + down ip route del dev eth2 || true + post-down address del dev eth2 || true -# interface "eth1" -iface eth1 inet dhcp ` s.PatchValue(containerinit.NetworkInterfacesFile, s.networkInterfacesFile) } @@ -102,8 +124,8 @@ // We need to indent expectNetConfig to make it valid YAML, // dropping the last new line and using unindented blank lines. lines := strings.Split(s.expectedNetConfig, "\n") - indentedNetConfig := strings.Join(lines[:len(lines)-1], "\n ") - indentedNetConfig = strings.Replace(indentedNetConfig, "\n \n", "\n\n", -1) + indentedNetConfig := strings.Join(lines[:len(lines)-2], "\n ") + indentedNetConfig = strings.Replace(indentedNetConfig, "\n \n", "\n\n", -1) + "\n" expected := ` #cloud-config bootcmd: @@ -111,6 +133,8 @@ - |- printf '%s\n' '` + indentedNetConfig + ` ' > '` + s.networkInterfacesFile + `' +runcmd: +- ifup -a || true ` assertUserData(c, cloudConf, expected) } diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg.go 2016-04-28 06:03:34.000000000 +0000 @@ -13,9 +13,9 @@ "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" - "github.com/juju/utils" "github.com/juju/utils/proxy" "github.com/juju/utils/shell" + "github.com/juju/version" "github.com/juju/juju/agent" agenttools "github.com/juju/juju/agent/tools" @@ -32,7 +32,6 @@ "github.com/juju/juju/service/common" "github.com/juju/juju/state/multiwatcher" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) var logger = loggo.GetLogger("juju.cloudconfig.instancecfg") @@ -140,11 +139,16 @@ // Config holds the initial environment configuration. Config *config.Config + // HostedModelConfig is a set of config attributes to be overlaid + // on the controller model config (Config, above) to construct the + // initial hosted model config. + HostedModelConfig map[string]interface{} + // Constraints holds the machine constraints. Constraints constraints.Value - // EnvironConstraints holds the initial environment constraints. - EnvironConstraints constraints.Value + // ModelConstraints holds the initial model constraints. + ModelConstraints constraints.Value // DisableSSLHostnameVerification can be set to true to tell cloud-init // that it shouldn't verify SSL certificates @@ -380,6 +384,9 @@ if cfg.InstanceId == "" { return errors.New("missing instance-id") } + if len(cfg.HostedModelConfig) == 0 { + return errors.New("missing hosted model config") + } } else { if len(cfg.MongoInfo.Addrs) == 0 { return errors.New("missing state hosts") @@ -396,6 +403,9 @@ if cfg.StateServingInfo != nil { return errors.New("state serving info unexpectedly present") } + if len(cfg.HostedModelConfig) != 0 { + return errors.New("hosted model config unexpectedly present") + } } if cfg.MachineNonce == "" { return errors.New("missing machine nonce") @@ -468,7 +478,10 @@ // NewBootstrapInstanceConfig sets up a basic machine configuration for a // bootstrap node. You'll still need to supply more information, but this // takes care of the fixed entries and the ones that are always needed. -func NewBootstrapInstanceConfig(cons, environCons constraints.Value, series, publicImageSigningKey string) (*InstanceConfig, error) { +func NewBootstrapInstanceConfig( + cons, modelCons constraints.Value, + series, publicImageSigningKey string, +) (*InstanceConfig, error) { // For a bootstrap instance, FinishInstanceConfig will provide the // state.Info and the api.Info. The machine id must *always* be "0". icfg, err := NewInstanceConfig("0", agent.BootstrapNonce, "", series, publicImageSigningKey, true, nil, nil, nil) @@ -481,7 +494,7 @@ multiwatcher.JobHostUnits, } icfg.Constraints = cons - icfg.EnvironConstraints = environCons + icfg.ModelConstraints = modelCons return icfg, nil } @@ -571,17 +584,12 @@ if password == "" { return errors.New("model configuration has no admin-secret") } - passwordHash := utils.UserPasswordHash(password, utils.CompatSalt) - modelUUID, uuidSet := cfg.UUID() - if !uuidSet { - return errors.New("config missing model uuid") - } icfg.APIInfo = &api.Info{ - Password: passwordHash, + Password: password, CACert: caCert, - ModelTag: names.NewModelTag(modelUUID), + ModelTag: names.NewModelTag(cfg.UUID()), } - icfg.MongoInfo = &mongo.MongoInfo{Password: passwordHash, Info: mongo.Info{CACert: caCert}} + icfg.MongoInfo = &mongo.MongoInfo{Password: password, Info: mongo.Info{CACert: caCert}} // These really are directly relevant to running a controller. // Initially, generate a controller certificate with no host IP @@ -613,8 +621,7 @@ // InstanceTags returns the minimum set of tags that should be set on a // machine instance, if the provider supports them. func InstanceTags(cfg *config.Config, jobs []multiwatcher.MachineJob) map[string]string { - uuid, _ := cfg.UUID() - instanceTags := tags.ResourceTags(names.NewModelTag(uuid), cfg) + instanceTags := tags.ResourceTags(names.NewModelTag(cfg.UUID()), cfg) if multiwatcher.AnyJobNeedsState(jobs...) { instanceTags[tags.JujuController] = "true" } diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg_test.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg_test.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/instancecfg/instancecfg_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,6 +5,7 @@ import ( jc "github.com/juju/testing/checkers" + "github.com/juju/version" gc "gopkg.in/check.v1" "github.com/juju/juju/cloudconfig/instancecfg" @@ -12,7 +13,6 @@ "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/testing" coretools "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) type instancecfgSuite struct { @@ -34,18 +34,6 @@ }) } -func (*instancecfgSuite) TestInstanceTagsNoUUID(c *gc.C) { - attrsWithoutUUID := testing.FakeConfig() - delete(attrsWithoutUUID, "uuid") - cfgWithoutUUID, err := config.New(config.NoDefaults, attrsWithoutUUID) - c.Assert(err, jc.ErrorIsNil) - testInstanceTags(c, - cfgWithoutUUID, - []multiwatcher.MachineJob(nil), - map[string]string{"juju-model-uuid": ""}, - ) -} - func (*instancecfgSuite) TestInstanceTagsUserSpecified(c *gc.C) { cfg := testing.CustomModelConfig(c, testing.Attrs{ "resource-tags": "a=b c=", diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/powershell_helpers.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/powershell_helpers.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/powershell_helpers.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/powershell_helpers.go 2016-04-28 06:03:34.000000000 +0000 @@ -22,32 +22,7 @@ package cloudconfig -var winPowershellHelperFunctions = ` - -$ErrorActionPreference = "Stop" - -function ExecRetry($command, $retryInterval = 15) -{ - $currErrorActionPreference = $ErrorActionPreference - $ErrorActionPreference = "Continue" - - while ($true) - { - try - { - & $command - break - } - catch [System.Exception] - { - Write-Error $_.Exception - Start-Sleep $retryInterval - } - } - - $ErrorActionPreference = $currErrorActionPreference -} - +const addJujudUser = ` function create-account ([string]$accountName, [string]$accountDescription, [string]$password) { $hostname = hostname $comp = [adsi]"WinNT://$hostname" @@ -451,405 +426,240 @@ } } -$Source = @" -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Text; - -namespace Tarer -{ - public enum EntryType : byte - { - File = 0, - FileObsolete = 0x30, - HardLink = 0x31, - SymLink = 0x32, - CharDevice = 0x33, - BlockDevice = 0x34, - Directory = 0x35, - Fifo = 0x36, - } - - public interface ITarHeader - { - string FileName { get; set; } - long SizeInBytes { get; set; } - DateTime LastModification { get; set; } - int HeaderSize { get; } - EntryType EntryType { get; set; } - } - - public class Tar - { - private byte[] dataBuffer = new byte[512]; - private UsTarHeader header; - private Stream inStream; - private long remainingBytesInFile; - - public Tar(Stream tarredData) { - inStream = tarredData; - header = new UsTarHeader(); - } - - public ITarHeader FileInfo - { - get { return header; } - } - - public void ReadToEnd(string destDirectory) - { - while (MoveNext()) - { - string fileNameFromArchive = FileInfo.FileName; - string totalPath = destDirectory + Path.DirectorySeparatorChar + fileNameFromArchive; - if(UsTarHeader.IsPathSeparator(fileNameFromArchive[fileNameFromArchive.Length -1]) || FileInfo.EntryType == EntryType.Directory) - { - Directory.CreateDirectory(totalPath); - continue; - } - string fileName = Path.GetFileName(totalPath); - string directory = totalPath.Remove(totalPath.Length - fileName.Length); - Directory.CreateDirectory(directory); - using (FileStream file = File.Create(totalPath)) - { - Read(file); - } - } - } - - public void Read(Stream dataDestination) - { - int readBytes; - byte[] read; - while ((readBytes = Read(out read)) != -1) - { - dataDestination.Write(read, 0, readBytes); - } - } - - protected int Read(out byte[] buffer) - { - if(remainingBytesInFile == 0) - { - buffer = null; - return -1; - } - int align512 = -1; - long toRead = remainingBytesInFile - 512; - - if (toRead > 0) - { - toRead = 512; - } - else - { - align512 = 512 - (int)remainingBytesInFile; - toRead = remainingBytesInFile; - } - - int bytesRead = 0; - long bytesRemainingToRead = toRead; - while (bytesRead < toRead && bytesRemainingToRead > 0) - { - bytesRead = inStream.Read(dataBuffer, (int)(toRead-bytesRemainingToRead), (int)bytesRemainingToRead); - bytesRemainingToRead -= bytesRead; - remainingBytesInFile -= bytesRead; - } - - if(inStream.CanSeek && align512 > 0) - { - inStream.Seek(align512, SeekOrigin.Current); - } - else - { - while(align512 > 0) - { - inStream.ReadByte(); - --align512; - } - } +$juju_passwd = Get-RandomPassword 20 +$juju_passwd += "^" +create-account jujud "Juju Admin user" $juju_passwd +$hostname = hostname +$juju_user = "$hostname\jujud" - buffer = dataBuffer; - return bytesRead; - } +SetUserLogonAsServiceRights $juju_user +SetAssignPrimaryTokenPrivilege $juju_user - private static bool IsEmpty(IEnumerable buffer) - { - foreach(byte b in buffer) - { - if (b != 0) - { - return false; - } - } - return true; - } +$path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" +if(!(Test-Path $path)){ + New-Item -Path $path -force +} +New-ItemProperty $path -Name "jujud" -Value 0 -PropertyType "DWord" - public bool MoveNext() - { - byte[] bytes = header.GetBytes(); - int headerRead; - int bytesRemaining = header.HeaderSize; - while (bytesRemaining > 0) - { - headerRead = inStream.Read(bytes, header.HeaderSize - bytesRemaining, bytesRemaining); - bytesRemaining -= headerRead; - if (headerRead <= 0 && bytesRemaining > 0) - { - throw new Exception("Error reading tar header. Header size invalid"); - } - } +$secpasswd = ConvertTo-SecureString $juju_passwd -AsPlainText -Force +$jujuCreds = New-Object System.Management.Automation.PSCredential ($juju_user, $secpasswd) +` - if(IsEmpty(bytes)) - { - bytesRemaining = header.HeaderSize; - while (bytesRemaining > 0) - { - headerRead = inStream.Read(bytes, header.HeaderSize - bytesRemaining, bytesRemaining); - bytesRemaining -= headerRead; - if (headerRead <= 0 && bytesRemaining > 0) - { - throw new Exception("Broken archive"); - } - } - if (bytesRemaining == 0 && IsEmpty(bytes)) - { - return false; - } - throw new Exception("Error occured: expected end of archive"); - } +const winPowershellHelperFunctions = ` - if (!header.UpdateHeaderFromBytes()) - { - throw new Exception("Checksum check failed"); - } +$ErrorActionPreference = "Stop" - remainingBytesInFile = header.SizeInBytes; - return true; - } - } +function ExecRetry($command, $retryInterval = 15) +{ + $currErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" - internal class TarHeader : ITarHeader + while ($true) { - private byte[] buffer = new byte[512]; - private long headerChecksum; - - private string fileName; - protected DateTime dateTime1970 = new DateTime(1970, 1, 1, 0, 0, 0); - private Tarer.EntryType _entryType; - public EntryType EntryType - { - get { return _entryType; } - set { _entryType = value; } - } - private static byte[] spaces = Encoding.ASCII.GetBytes(" "); - - public virtual string FileName - { - get { return fileName.Replace("\0",string.Empty); } - set { fileName = value; } - } - - private long _sizeInBytes; - public long SizeInBytes { - get{ return _sizeInBytes; } - set{ _sizeInBytes = value; } - } - - public string SizeString { get { return Convert.ToString(SizeInBytes, 8).PadLeft(11, '0'); } } - - private DateTime _lastModified; - public DateTime LastModification { - get{return _lastModified;} - set{_lastModified = value;} - } - - public virtual int HeaderSize { get { return 512; } } - - public byte[] GetBytes() - { - return buffer; - } - - public virtual bool UpdateHeaderFromBytes() - { - FileName = Encoding.UTF8.GetString(buffer, 0, 100); - - EntryType = (EntryType)buffer[156]; - - if((buffer[124] & 0x80) == 0x80) // if size in binary - { - long sizeBigEndian = BitConverter.ToInt64(buffer,0x80); - SizeInBytes = IPAddress.NetworkToHostOrder(sizeBigEndian); - } - else - { - SizeInBytes = Convert.ToInt64(Encoding.ASCII.GetString(buffer, 124, 11).Trim(), 8); - } - long unixTimeStamp = Convert.ToInt64(Encoding.ASCII.GetString(buffer,136,11).Trim(),8); - LastModification = dateTime1970.AddSeconds(unixTimeStamp); - - long storedChecksum = Convert.ToInt64(Encoding.ASCII.GetString(buffer,148,6).Trim(), 8); - RecalculateChecksum(buffer); - if (storedChecksum == headerChecksum) - { - return true; - } - - RecalculateAltChecksum(buffer); - return storedChecksum == headerChecksum; - } - - private void RecalculateAltChecksum(byte[] buf) + try { - spaces.CopyTo(buf, 148); - headerChecksum = 0; - foreach(byte b in buf) - { - if((b & 0x80) == 0x80) - { - headerChecksum -= b ^ 0x80; - } - else - { - headerChecksum += b; - } - } + & $command + break } - - protected virtual void RecalculateChecksum(byte[] buf) + catch [System.Exception] { - // Set default value for checksum. That is 8 spaces. - spaces.CopyTo(buf, 148); - // Calculate checksum - headerChecksum = 0; - foreach (byte b in buf) - { - headerChecksum += b; - } + Write-Error $_.Exception + Start-Sleep $retryInterval } } - internal class UsTarHeader : TarHeader - { - private const string magic = "ustar"; - private const string version = " "; - - private string namePrefix = string.Empty; - public override string FileName - { - get { return namePrefix.Replace("\0", string.Empty) + base.FileName.Replace("\0", string.Empty); } - set - { - if (value.Length > 255) - { - throw new Exception("UsTar fileName can not be longer than 255 chars"); - } - if (value.Length > 100) - { - int position = value.Length - 100; - while (!IsPathSeparator(value[position])) - { - ++position; - if (position == value.Length) - { - break; - } - } - if (position == value.Length) - { - position = value.Length - 100; - } - namePrefix = value.Substring(0, position); - base.FileName = value.Substring(position, value.Length - position); - } - else - { - base.FileName = value; - } - } - } - - public override bool UpdateHeaderFromBytes() - { - byte[] bytes = GetBytes(); - namePrefix = Encoding.UTF8.GetString(bytes, 347, 157); - return base.UpdateHeaderFromBytes(); - } - - internal static bool IsPathSeparator(char ch) - { - return (ch == '\\' || ch == '/' || ch == '|'); - } - } + $ErrorActionPreference = $currErrorActionPreference } -"@ - -Add-Type -TypeDefinition $Source -Language CSharp Function GUnZip-File{ - Param( - $infile, - $outdir - ) - - $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) - $tempFile = "$env:TEMP\jujud.tar" - $tempOut = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) - $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress) - - $buffer = New-Object byte[](1024) - while($true){ - $read = $gzipstream.Read($buffer, 0, 1024) - if ($read -le 0){break} - $tempOut.Write($buffer, 0, $read) - } - $gzipStream.Close() - $tempOut.Close() - $input.Close() - - $in = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) - $tar = New-Object Tarer.Tar($in) - $tar.ReadToEnd($outdir) - $in.Close() - rm $tempFile + Param( + $infile, + $outdir + ) + + $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) + $tempFile = "$env:TEMP\jujud.tar" + $tempOut = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) + $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress) + + $buffer = New-Object byte[](1024) + while($true) { + $read = $gzipstream.Read($buffer, 0, 1024) + if ($read -le 0){break} + $tempOut.Write($buffer, 0, $read) + } + $gzipStream.Close() + $tempOut.Close() + $input.Close() + + $in = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) + Untar-File $in $outdir + $in.Close() + rm $tempFile +} + +$HEADERSIZE = 512 + +Function Untar-File { + Param( + $inStream, + $outdir + ) + $DirectoryEntryType = 0x35 + $headerBytes = New-Object byte[]($HEADERSIZE) + + # $headerBytes is written inside, function returns whether we've reached the end + while(GetHeaderBytes $inStream $headerBytes) { + $fileName, $entryType, $sizeInBytes = GetFileInfoFromHeader $headerBytes + + $totalPath = Join-Path $outDir $fileName + if ($entryType -eq $DirectoryEntryType) { + [System.IO.Directory]::CreateDirectory($totalPath) + continue; + } + + $fName = [System.IO.Path]::GetFileName($totalPath) + $dirName = [System.IO.Path]::GetDirectoryName($totalPath) + [System.IO.Directory]::CreateDirectory($dirName) + $file = [System.IO.File]::Create($totalPath) + WriteTarEntryToFile $inStream $file $sizeInBytes + $file.Close() + } +} + +Function WriteTarEntryToFile { + Param( + $inStream, + $outFile, + $sizeInBytes + ) + $moveToAlign512 = 0 + $toRead = 0 + $buf = New-Object byte[](512) + + $remainingBytesInFile = $sizeInBytes + while ($remainingBytesInFile -ne 0) { + if ($remainingBytesInFile - 512 -lt 0) { + $moveToAlign512 = 512 - $remainingBytesInFile + $toRead = $remainingBytesInFile + } else { + $toRead = 512 + } + + $bytesRead = 0 + $bytesRemainingToRead = $toRead + while ($bytesRead -lt $toRead -and $bytesRemainingToRead -gt 0) { + $bytesRead = $inStream.Read($buf, $toRead - $bytesRemainingToRead, $bytesRemainingToRead) + $bytesRemainingToRead = $bytesRemainingToRead - $bytesRead + $remainingBytesInFile = $remainingBytesInFile - $bytesRead + $outFile.Write($buf, 0, $bytesRead) + } + + if ($moveToAlign512 -ne 0) { + $inStream.Seek($moveToAlign512, [System.IO.SeekOrigin]::Current) + } + } +} + +Function GetHeaderBytes { + Param($inStream, $headerBytes) + + $headerRead = 0 + $bytesRemaining = $HEADERSIZE + while ($bytesRemaining -gt 0) { + $headerRead = $inStream.Read($headerBytes, $HEADERSIZE - $bytesRemaining, $bytesRemaining) + $bytesRemaining -= $headerRead + if ($headerRead -le 0 -and $bytesRemaining -gt 0) { + throw "Error reading tar header. Header size invalid" + } + } + + # Proper end of archive is 2 empty headers + if (IsEmptyByteArray $headerBytes) { + $bytesRemaining = $HEADERSIZE + while ($bytesRemaining -gt 0) { + $headerRead = $inStream.Read($headerBytes, $HEADERSIZE - $bytesRemaining, $bytesRemaining) + $bytesRemaining -= $headerRead + if ($headerRead -le 0 -and $bytesRemaining -gt 0) { + throw "Broken end archive" + } + } + if ($bytesRemaining -eq 0 -and (IsEmptyByteArray($headerBytes))) { + return $false + } + throw "Error occurred: expected end of archive" + } + + return $true +} + +Function GetFileInfoFromHeader { + Param($headerBytes) + + $FileName = [System.Text.Encoding]::UTF8.GetString($headerBytes, 0, 100); + $EntryType = $headerBytes[156]; + $SizeInBytes = [Convert]::ToInt64([System.Text.Encoding]::ASCII.GetString($headerBytes, 124, 11).Trim(), 8); + Return $FileName.replace("` + "`" + `0", [String].Empty), $EntryType, $SizeInBytes +} + +Function IsEmptyByteArray { + Param ($bytes) + foreach($b in $bytes) { + if ($b -ne 0) { + return $false + } + } + return $true } Function Get-FileSHA256{ Param( $FilePath ) - $hash = [Security.Cryptography.HashAlgorithm]::Create( "SHA256" ) - $stream = ([IO.StreamReader]$FilePath).BaseStream - $res = -join ($hash.ComputeHash($stream) | ForEach { "{0:x2}" -f $_ }) - $stream.Close() - return $res + try { + $hash = [Security.Cryptography.HashAlgorithm]::Create( "SHA256" ) + $stream = ([IO.StreamReader]$FilePath).BaseStream + $res = -join ($hash.ComputeHash($stream) | ForEach { "{0:x2}" -f $_ }) + $stream.Close() + return $res + } catch [System.Management.Automation.RuntimeException] { + return (Get-FileHash -Path $FilePath).Hash + } } -$juju_passwd = Get-RandomPassword 20 -$juju_passwd += "^" -create-account jujud "Juju Admin user" $juju_passwd -$hostname = hostname -$juju_user = "$hostname\jujud" +Function Invoke-FastWebRequest { + Param( + $URI, + $OutFile + ) -SetUserLogonAsServiceRights $juju_user -SetAssignPrimaryTokenPrivilege $juju_user + if(!([System.Management.Automation.PSTypeName]'System.Net.Http.HttpClient').Type) + { + $assembly = [System.Reflection.Assembly]::LoadWithPartialName("System.Net.Http") + } -$path = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" -if(!(Test-Path $path)){ - New-Item -Path $path -force -} -New-ItemProperty $path -Name "jujud" -Value 0 -PropertyType "DWord" + $client = new-object System.Net.Http.HttpClient -$secpasswd = ConvertTo-SecureString $juju_passwd -AsPlainText -Force -$jujuCreds = New-Object System.Management.Automation.PSCredential ($juju_user, $secpasswd) + $task = $client.GetStreamAsync($URI) + $response = $task.Result + $outStream = New-Object IO.FileStream $OutFile, Create, Write, None + + try { + $totRead = 0 + $buffer = New-Object Byte[] 1MB + while (($read = $response.Read($buffer, 0, $buffer.Length)) -gt 0) { + $totRead += $read + $outStream.Write($buffer, 0, $read); + } + } + finally { + $outStream.Close() + } +} ` -var UserdataScript = `#ps1_sysnative +const UserdataScript = `#ps1_sysnative $userdata=@" %s "@ diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/providerinit/providerinit_test.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/providerinit/providerinit_test.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/providerinit/providerinit_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/providerinit/providerinit_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -13,6 +13,7 @@ "github.com/juju/names" jc "github.com/juju/testing/checkers" "github.com/juju/utils" + "github.com/juju/version" gc "gopkg.in/check.v1" goyaml "gopkg.in/yaml.v2" @@ -33,7 +34,6 @@ "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/testing" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) // dummySampleConfig returns the dummy sample config without @@ -73,7 +73,7 @@ MongoInfo: &mongo.MongoInfo{Tag: userTag}, APIInfo: &api.Info{Tag: userTag}, DisableSSLHostnameVerification: false, - PreferIPv6: true, + PreferIPv6: false, EnableOSRefreshUpdate: true, EnableOSUpgrade: true, } @@ -129,7 +129,7 @@ MongoInfo: &mongo.MongoInfo{Tag: userTag}, APIInfo: &api.Info{Tag: userTag}, DisableSSLHostnameVerification: true, - PreferIPv6: true, + PreferIPv6: false, EnableOSRefreshUpdate: true, EnableOSUpgrade: true, }) @@ -152,7 +152,7 @@ c.Assert(err, jc.ErrorIsNil) c.Check(icfg.AuthorizedKeys, gc.Equals, "we-are-the-keys") c.Check(icfg.DisableSSLHostnameVerification, jc.IsFalse) - password := utils.UserPasswordHash("lisboan-pork", utils.CompatSalt) + password := "lisboan-pork" c.Check(icfg.APIInfo, gc.DeepEquals, &api.Info{ Password: password, CACert: testing.CACert, ModelTag: testing.ModelTag, diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/userdatacfg_test.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/userdatacfg_test.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/userdatacfg_test.go 2016-03-17 19:44:59.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/userdatacfg_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -19,6 +19,7 @@ jc "github.com/juju/testing/checkers" pacconf "github.com/juju/utils/packaging/config" "github.com/juju/utils/set" + "github.com/juju/version" gc "gopkg.in/check.v1" goyaml "gopkg.in/yaml.v2" @@ -37,7 +38,6 @@ "github.com/juju/juju/state/multiwatcher" "github.com/juju/juju/testing" "github.com/juju/juju/tools" - "github.com/juju/juju/version" ) type cloudinitSuite struct { @@ -163,9 +163,9 @@ } // setGUI populates the configuration with the Juju GUI tools. -func (cfg *testInstanceConfig) setGUI(path string) *testInstanceConfig { +func (cfg *testInstanceConfig) setGUI(url string) *testInstanceConfig { cfg.GUI = &tools.GUIArchive{ - URL: "file://" + filepath.ToSlash(path), + URL: url, Version: version.MustParse("1.2.3"), Size: 42, SHA256: "1234", @@ -178,6 +178,9 @@ func (cfg *testInstanceConfig) maybeSetModelConfig(envConfig *config.Config) *testInstanceConfig { if envConfig != nil { cfg.Config = envConfig + if cfg.Bootstrap { + cfg.HostedModelConfig = map[string]interface{}{"name": "hosted-model"} + } } return cfg } @@ -206,7 +209,7 @@ func (cfg *testInstanceConfig) setController() *testInstanceConfig { cfg.setMachineID("0") cfg.Constraints = bootstrapConstraints - cfg.EnvironConstraints = envConstraints + cfg.ModelConstraints = envConstraints cfg.Bootstrap = true cfg.StateServingInfo = stateServingInfo cfg.Jobs = allMachineJobs @@ -323,7 +326,7 @@ cat > '/var/lib/juju/agents/machine-0/agent\.conf' << 'EOF'\\n.*\\nEOF chmod 0600 '/var/lib/juju/agents/machine-0/agent\.conf' echo 'Bootstrapping Juju machine agent'.* -/var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --instance-id 'i-bootstrap' --bootstrap-constraints 'mem=4096M' --constraints 'mem=2048M' --debug +/var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --hosted-model-config '[^']*' --instance-id 'i-bootstrap' --bootstrap-constraints 'mem=4096M' --constraints 'mem=2048M' --debug ln -s 1\.2\.3-precise-amd64 '/var/lib/juju/tools/machine-0' echo 'Starting Juju machine agent \(jujud-machine-0\)'.* cat > /etc/init/jujud-machine-0\.conf << 'EOF'\\ndescription "juju agent for machine-0"\\nauthor "Juju Team "\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit nofile 20000 20000\\n\\nscript\\n\\n\\n # Ensure log files are properly protected\\n touch /var/log/juju/machine-0\.log\\n chown syslog:syslog /var/log/juju/machine-0\.log\\n chmod 0600 /var/log/juju/machine-0\.log\\n\\n exec '/var/lib/juju/tools/machine-0/jujud' machine --data-dir '/var/lib/juju' --machine-id 0 --debug >> /var/log/juju/machine-0\.log 2>&1\\nend script\\nEOF\\n @@ -343,7 +346,7 @@ sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-raring-amd64\.sha256 grep '1234' \$bin/juju1\.2\.3-raring-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\) printf %s '{"version":"1\.2\.3-raring-amd64","url":"http://foo\.com/tools/released/juju1\.2\.3-raring-amd64\.tgz","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt -/var/lib/juju/tools/1\.2\.3-raring-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --instance-id 'i-bootstrap' --bootstrap-constraints 'mem=4096M' --constraints 'mem=2048M' --debug +/var/lib/juju/tools/1\.2\.3-raring-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --hosted-model-config '[^']*' --instance-id 'i-bootstrap' --bootstrap-constraints 'mem=4096M' --constraints 'mem=2048M' --debug ln -s 1\.2\.3-raring-amd64 '/var/lib/juju/tools/machine-0' rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-raring-amd64\.sha256 `, @@ -400,7 +403,7 @@ // non controller with GUI (the GUI is not installed) { - cfg: makeNormalConfig("quantal").setGUI("/path/to/gui.tar.bz2"), + cfg: makeNormalConfig("quantal").setGUI("file:///path/to/gui.tar.bz2"), expectScripts: ` set -xe install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown\.conf' @@ -478,19 +481,19 @@ setEnvConfig: true, inexactMatch: true, expectScripts: ` -/var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --instance-id 'i-bootstrap' --constraints 'mem=2048M' --debug +/var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --hosted-model-config '[^']*' --instance-id 'i-bootstrap' --constraints 'mem=2048M' --debug `, }, // empty environ contraints. { cfg: makeBootstrapConfig("precise").mutate(func(cfg *testInstanceConfig) { - cfg.EnvironConstraints = constraints.Value{} + cfg.ModelConstraints = constraints.Value{} }), setEnvConfig: true, inexactMatch: true, expectScripts: ` -/var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --instance-id 'i-bootstrap' --bootstrap-constraints 'mem=4096M' --debug +/var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --model-config '[^']*' --hosted-model-config '[^']*' --instance-id 'i-bootstrap' --bootstrap-constraints 'mem=4096M' --debug `, }, @@ -641,10 +644,53 @@ } } -func (*cloudinitSuite) TestCloudInitWithGUI(c *gc.C) { +func (*cloudinitSuite) TestCloudInitWithLocalGUI(c *gc.C) { guiPath := path.Join(c.MkDir(), "gui.tar.bz2") - err := ioutil.WriteFile(guiPath, []byte("content"), 0644) - cfg := makeBootstrapConfig("precise").setGUI(guiPath) + content := []byte("content") + err := ioutil.WriteFile(guiPath, content, 0644) + c.Assert(err, jc.ErrorIsNil) + cfg := makeBootstrapConfig("precise").setGUI("file://" + filepath.ToSlash(guiPath)) + guiJson, err := json.Marshal(cfg.GUI) + c.Assert(err, jc.ErrorIsNil) + base64Content := base64.StdEncoding.EncodeToString(content) + expectedScripts := regexp.QuoteMeta(fmt.Sprintf(`gui='/var/lib/juju/gui' +mkdir -p $gui +install -D -m 644 /dev/null '/var/lib/juju/gui/gui.tar.bz2' +printf %%s %s | base64 -d > '/var/lib/juju/gui/gui.tar.bz2' +[ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256 +[ -f $gui/jujugui.sha256 ] && (grep '1234' $gui/jujugui.sha256 && printf %%s '%s' > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch) +rm -f $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt +`, base64Content, guiJson)) + checkCloudInitWithGUI(c, cfg, expectedScripts, "") +} + +func (*cloudinitSuite) TestCloudInitWithRemoteGUI(c *gc.C) { + cfg := makeBootstrapConfig("precise").setGUI("https://1.2.3.4/gui.tar.bz2") + guiJson, err := json.Marshal(cfg.GUI) + c.Assert(err, jc.ErrorIsNil) + expectedScripts := regexp.QuoteMeta(fmt.Sprintf(`gui='/var/lib/juju/gui' +mkdir -p $gui +curl -sSf -o $gui/gui.tar.bz2 --retry 10 'https://1.2.3.4/gui.tar.bz2' || echo Unable to retrieve Juju GUI +[ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256 +[ -f $gui/jujugui.sha256 ] && (grep '1234' $gui/jujugui.sha256 && printf %%s '%s' > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch) +rm -f $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt +`, guiJson)) + checkCloudInitWithGUI(c, cfg, expectedScripts, "") +} + +func (*cloudinitSuite) TestCloudInitWithGUIReadError(c *gc.C) { + cfg := makeBootstrapConfig("precise").setGUI("file:///no/such/gui.tar.bz2") + expectedError := "cannot set up Juju GUI: cannot read Juju GUI archive: .*" + checkCloudInitWithGUI(c, cfg, "", expectedError) +} + +func (*cloudinitSuite) TestCloudInitWithGUIURLError(c *gc.C) { + cfg := makeBootstrapConfig("precise").setGUI(":") + expectedError := "cannot set up Juju GUI: cannot parse Juju GUI URL: .*" + checkCloudInitWithGUI(c, cfg, "", expectedError) +} + +func checkCloudInitWithGUI(c *gc.C, cfg *testInstanceConfig, expectedScripts string, expectedError string) { envConfig := minimalModelConfig(c) testConfig := cfg.maybeSetModelConfig(envConfig).render() ci, err := cloudinit.New(testConfig.Series) @@ -652,6 +698,10 @@ udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci) c.Assert(err, jc.ErrorIsNil) err = udata.Configure() + if expectedError != "" { + c.Assert(err, gc.ErrorMatches, expectedError) + return + } c.Assert(err, jc.ErrorIsNil) c.Check(ci, gc.NotNil) data, err := ci.RenderYAML() @@ -661,30 +711,10 @@ err = goyaml.Unmarshal(data, &configKeyValues) c.Assert(err, jc.ErrorIsNil) - guiJson, err := json.Marshal(cfg.GUI) - c.Assert(err, jc.ErrorIsNil) - scripts := getScripts(configKeyValues) - expectedScripts := regexp.QuoteMeta(fmt.Sprintf(`sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256 -grep '1234' $gui/jujugui.sha256 || (echo Juju GUI checksum mismatch; exit 1) -printf %%s '%s' > $gui/downloaded-gui.txt -rm $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt -`, guiJson)) assertScriptMatch(c, scripts, expectedScripts, false) } -func (*cloudinitSuite) TestCloudInitWithGUIError(c *gc.C) { - cfg := makeBootstrapConfig("precise").setGUI("/path/to/gui.tar.bz2") - envConfig := minimalModelConfig(c) - testConfig := cfg.maybeSetModelConfig(envConfig).render() - ci, err := cloudinit.New(testConfig.Series) - c.Assert(err, jc.ErrorIsNil) - udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci) - c.Assert(err, jc.ErrorIsNil) - err = udata.Configure() - c.Assert(err, gc.ErrorMatches, "cannot fetch Juju GUI: cannot read Juju GUI archive: .*") -} - func (*cloudinitSuite) TestCloudInitConfigure(c *gc.C) { for i, test := range cloudinitTests { testConfig := test.cfg.maybeSetModelConfig(minimalModelConfig(c)).render() @@ -698,7 +728,7 @@ } } -func (*cloudinitSuite) TestCloudInitConfigureBootstrapLogging(c *gc.C) { +func (*cloudinitSuite) bootstrapConfigScripts(c *gc.C) []string { loggo.GetLogger("").SetLogLevel(loggo.INFO) envConfig := minimalModelConfig(c) instConfig := makeBootstrapConfig("quantal").maybeSetModelConfig(envConfig) @@ -722,12 +752,25 @@ c.Logf("scripts[%d]: %q", i, script) } } + return scripts +} + +func (s *cloudinitSuite) TestCloudInitConfigureBootstrapLogging(c *gc.C) { + scripts := s.bootstrapConfigScripts(c) expected := "jujud bootstrap-state --data-dir '.*' --model-config '.*'" + + " --hosted-model-config '[^']*'" + " --instance-id '.*' --bootstrap-constraints 'mem=4096M'" + " --constraints 'mem=2048M' --show-log" assertScriptMatch(c, scripts, expected, false) } +func (s *cloudinitSuite) TestCloudInitConfigureBootstrapFeatureFlags(c *gc.C) { + s.SetFeatureFlags("special", "foo") + scripts := s.bootstrapConfigScripts(c) + expected := "JUJU_DEV_FEATURE_FLAGS=foo,special .*/jujud bootstrap-state .*" + assertScriptMatch(c, scripts, expected, false) +} + func (*cloudinitSuite) TestCloudInitConfigureUsesGivenConfig(c *gc.C) { // Create a simple cloudinit config with a 'runcmd' statement. cloudcfg, err := cloudinit.New("quantal") @@ -1068,6 +1111,7 @@ ModelTag: testing.ModelTag, }, Config: minimalModelConfig(c), + HostedModelConfig: map[string]interface{}{"name": "hosted-model"}, DataDir: jujuDataDir("quantal"), LogDir: jujuLogDir("quantal"), MetricsSpoolDir: metricsSpoolDir("quantal"), diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/userdatacfg_unix.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/userdatacfg_unix.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/userdatacfg_unix.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/userdatacfg_unix.go 2016-04-28 06:03:34.000000000 +0000 @@ -20,19 +20,19 @@ "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/names" + "github.com/juju/utils/featureflag" "github.com/juju/utils/os" "github.com/juju/utils/proxy" goyaml "gopkg.in/yaml.v2" "github.com/juju/juju/agent" "github.com/juju/juju/cloudconfig/cloudinit" - "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/imagemetadata" "github.com/juju/juju/environs/simplestreams" + "github.com/juju/juju/juju/osenv" "github.com/juju/juju/service" "github.com/juju/juju/service/systemd" "github.com/juju/juju/service/upstart" - coretools "github.com/juju/juju/tools" ) const ( @@ -308,31 +308,12 @@ if w.icfg.Bootstrap { // Add the Juju GUI to the bootstrap node. - guiData, err := w.fetchGUI(w.icfg.GUI) + cleanup, err := w.setUpGUI() if err != nil { - return errors.Annotate(err, "cannot fetch Juju GUI") + return errors.Annotate(err, "cannot set up Juju GUI") } - // TODO frankban: guiData will never be nil at this point when using - // simplestreams. This will be fixed before landing to master. - if guiData != nil { - guiJson, err := json.Marshal(w.icfg.GUI) - if err != nil { - return errors.Trace(err) - } - guiDir := w.icfg.GUITools() - w.conf.AddScripts( - "gui="+shquote(guiDir), - "mkdir -p $gui", - ) - w.conf.AddRunBinaryFile(path.Join(guiDir, "gui.tar.bz2"), []byte(guiData), 0644) - w.conf.AddScripts( - "sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256", - fmt.Sprintf(`grep '%s' $gui/jujugui.sha256 || (echo Juju GUI checksum mismatch; exit 1)`, w.icfg.GUI.SHA256), - fmt.Sprintf("printf %%s %s > $gui/downloaded-gui.txt", shquote(string(guiJson))), - ) - // Don't remove the GUI archive until after bootstrap agent runs, - // so it has a chance to add it to its catalogue. - defer w.conf.AddRunCmd("rm $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt") + if cleanup != nil { + defer cleanup() } var metadataDir string @@ -353,9 +334,9 @@ if bootstrapCons != "" { bootstrapCons = " --bootstrap-constraints " + shquote(bootstrapCons) } - environCons := w.icfg.EnvironConstraints.String() - if environCons != "" { - environCons = " --constraints " + shquote(environCons) + modelCons := w.icfg.ModelConstraints.String() + if modelCons != "" { + modelCons = " --constraints " + shquote(modelCons) } var hardware string if w.icfg.HardwareCharacteristics != nil { @@ -370,15 +351,20 @@ if loggo.GetLogger("").LogLevel() == loggo.DEBUG { loggingOption = " --debug" } + featureFlags := featureflag.AsEnvironmentValue() + if featureFlags != "" { + featureFlags = fmt.Sprintf("%s=%s ", osenv.JujuFeatureFlagEnvKey, featureFlags) + } w.conf.AddScripts( // The bootstrapping is always run with debug on. - w.icfg.JujuTools() + "/jujud bootstrap-state" + + featureFlags + w.icfg.JujuTools() + "/jujud bootstrap-state" + " --data-dir " + shquote(w.icfg.DataDir) + - " --model-config " + shquote(base64yaml(w.icfg.Config)) + + " --model-config " + shquote(base64yaml(w.icfg.Config.AllAttrs())) + + " --hosted-model-config " + shquote(base64yaml(w.icfg.HostedModelConfig)) + " --instance-id " + shquote(string(w.icfg.InstanceId)) + hardware + bootstrapCons + - environCons + + modelCons + metadataDir + loggingOption, ) @@ -387,27 +373,59 @@ return w.addMachineAgentToBoot() } -// fetchGUI fetches the Juju GUI. -func (w *unixConfigure) fetchGUI(gui *coretools.GUIArchive) ([]byte, error) { - if gui == nil { - // TODO frankban: return an error in this case. - // This will be fixed before landing to master. +// setUpGUI fetches the Juju GUI archive and save it to the controller. +// The returned clean up function must be called when the bootstrapping +// process is completed. +func (w *unixConfigure) setUpGUI() (func(), error) { + if w.icfg.GUI == nil { + // No GUI archives were found on simplestreams, and no development + // GUI path has been passed with the JUJU_GUI environment variable. return nil, nil } - u, err := url.Parse(gui.URL) + u, err := url.Parse(w.icfg.GUI.URL) if err != nil { return nil, errors.Annotate(err, "cannot parse Juju GUI URL") } - if u.Scheme != "file" { - // TODO frankban: support retrieving the GUI archive from the web. - // This will be fixed before landing to master. - return nil, nil - } - guiData, err := ioutil.ReadFile(filepath.FromSlash(u.Path)) + guiJson, err := json.Marshal(w.icfg.GUI) if err != nil { - return nil, errors.Annotate(err, "cannot read Juju GUI archive") + return nil, errors.Trace(err) + } + guiDir := w.icfg.GUITools() + w.conf.AddScripts( + "gui="+shquote(guiDir), + "mkdir -p $gui", + ) + if u.Scheme == "file" { + // Upload the GUI from a local archive file. + guiData, err := ioutil.ReadFile(filepath.FromSlash(u.Path)) + if err != nil { + return nil, errors.Annotate(err, "cannot read Juju GUI archive") + } + w.conf.AddRunBinaryFile(path.Join(guiDir, "gui.tar.bz2"), guiData, 0644) + } else { + // Download the GUI from simplestreams. + command := "curl -sSf -o $gui/gui.tar.bz2 --retry 10" + if w.icfg.DisableSSLHostnameVerification { + command += " --insecure" + } + command += " " + shquote(u.String()) + // A failure in fetching the Juju GUI archive should not prevent the + // model to be bootstrapped. Better no GUI than no Juju at all. + command += " || echo Unable to retrieve Juju GUI" + w.conf.AddRunCmd(command) } - return guiData, nil + w.conf.AddScripts( + "[ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256", + fmt.Sprintf( + `[ -f $gui/jujugui.sha256 ] && (grep '%s' $gui/jujugui.sha256 && printf %%s %s > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch)`, + w.icfg.GUI.SHA256, shquote(string(guiJson))), + ) + return func() { + // Don't remove the GUI archive until after bootstrap agent runs, + // so it has a chance to add it to its catalogue. + w.conf.AddRunCmd("rm -f $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt") + }, nil + } // toolsDownloadCommand takes a curl command minus the source URL, @@ -431,8 +449,8 @@ return buf.String() } -func base64yaml(m *config.Config) string { - data, err := goyaml.Marshal(m.AllAttrs()) +func base64yaml(attrs map[string]interface{}) string { + data, err := goyaml.Marshal(attrs) if err != nil { // can't happen, these values have been validated a number of times panic(err) diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/userdatacfg_win.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/userdatacfg_win.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/userdatacfg_win.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/userdatacfg_win.go 2016-04-28 06:03:34.000000000 +0000 @@ -5,6 +5,7 @@ package cloudconfig import ( + "encoding/base64" "encoding/json" "fmt" "path/filepath" @@ -12,7 +13,9 @@ "github.com/juju/errors" "github.com/juju/names" "github.com/juju/utils/featureflag" + "github.com/juju/utils/series" + "github.com/juju/juju/cert" "github.com/juju/juju/juju/osenv" "github.com/juju/juju/juju/paths" ) @@ -39,8 +42,7 @@ func (w *windowsConfigure) ConfigureBasic() error { - series := w.icfg.Series - tmpDir, err := paths.TempDir(series) + tmpDir, err := paths.TempDir(w.icfg.Series) if err != nil { return err } @@ -49,21 +51,32 @@ baseDir := renderer.FromSlash(filepath.Dir(tmpDir)) binDir := renderer.Join(baseDir, "bin") - w.conf.AddScripts( - fmt.Sprintf(`%s`, winPowershellHelperFunctions), + w.conf.AddScripts(fmt.Sprintf(`%s`, winPowershellHelperFunctions)) + + // The jujud user only gets created on non-nano versions for now. + if !series.IsWindowsNano(w.icfg.Series) { + w.conf.AddScripts(fmt.Sprintf(`%s`, addJujudUser)) + } + w.conf.AddScripts( // Some providers create a baseDir before this step, but we need to // make sure it exists before applying icacls fmt.Sprintf(`mkdir -Force "%s"`, renderer.FromSlash(baseDir)), fmt.Sprintf(`mkdir %s`, renderer.FromSlash(tmpDir)), fmt.Sprintf(`mkdir "%s"`, binDir), fmt.Sprintf(`mkdir "%s\locks"`, renderer.FromSlash(dataDir)), + `setx /m PATH "$env:PATH;C:\Juju\bin\"`, + // This is necessary for setACLs to work + `$adminsGroup = (New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount])`, + fmt.Sprintf(`icacls "%s" /inheritance:r /grant "${adminsGroup}:(OI)(CI)(F)" /t`, renderer.FromSlash(baseDir)), ) - // This is necessary for setACLs to work - w.conf.AddScripts(`$adminsGroup = (New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount])`) - w.conf.AddScripts(setACLs(renderer.FromSlash(baseDir), fileSystem)...) - w.conf.AddScripts(`setx /m PATH "$env:PATH;C:\Juju\bin\"`) + // TODO(bogdanteleaga): This, together with the call above, should be using setACLs, once it starts working across all windows versions properly. + // Until then, if we change permissions, both this and setACLs should be changed to do the same thing. + if !series.IsWindowsNano(w.icfg.Series) { + w.conf.AddScripts(fmt.Sprintf(`icacls "%s" /inheritance:r /grant "jujud:(OI)(CI)(F)" /t`, renderer.FromSlash(baseDir))) + } + noncefile := renderer.Join(dataDir, NonceFile) w.conf.AddScripts( fmt.Sprintf(`Set-Content "%s" "%s"`, noncefile, shquote(w.icfg.MachineNonce)), @@ -84,14 +97,21 @@ if err != nil { return errors.Annotate(err, "while serializing the tools") } + renderer := w.conf.ShellRenderer() w.conf.AddScripts( fmt.Sprintf(`$binDir="%s"`, renderer.FromSlash(w.icfg.JujuTools())), fmt.Sprintf(`mkdir '%s'`, renderer.FromSlash(w.icfg.LogDir)), `mkdir $binDir`, - `$WebClient = New-Object System.Net.WebClient`, - `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}`, - fmt.Sprintf(`ExecRetry { $WebClient.DownloadFile('%s', "$binDir\tools.tar.gz") }`, w.icfg.Tools.URL), + ) + + toolsDownloadCmds, err := addDownloadToolsCmds(w.icfg.Series, w.icfg.MongoInfo.CACert, w.icfg.Tools.URL) + if err != nil { + return errors.Trace(err) + } + w.conf.AddScripts(toolsDownloadCmds...) + + w.conf.AddScripts( `$dToolsHash = Get-FileSHA256 -FilePath "$binDir\tools.tar.gz"`, fmt.Sprintf(`$dToolsHash > "$binDir\juju%s.sha256"`, w.icfg.Tools.Version), @@ -102,7 +122,7 @@ fmt.Sprintf(`Set-Content $binDir\downloaded-tools.txt '%s'`, string(toolsJson)), ) - for _, cmd := range CreateJujuRegistryKeyCmds() { + for _, cmd := range createJujuRegistryKeyCmds(w.icfg.Series) { w.conf.AddRunCmd(cmd) } @@ -114,11 +134,10 @@ return w.addMachineAgentToBoot() } -// CreateJujuRegistryKey is going to create a juju registry key and set +// createJujuRegistryKeyCmds is going to create a juju registry key and set // permissions on it such that it's only accessible to administrators -// It is exported because it is used in an upgrade step -func CreateJujuRegistryKeyCmds() []string { - aclCmds := setACLs(osenv.JujuRegistryKey, registryEntry) +func createJujuRegistryKeyCmds(series string) []string { + aclCmds := setACLs(osenv.JujuRegistryKey, registryEntry, series) regCmds := []string{ // Create a registry key for storing juju related information @@ -136,24 +155,62 @@ return append(regCmds[:1], append(aclCmds, regCmds[1:]...)...) } -func setACLs(path string, permType aclType) []string { +func setACLs(path string, permType aclType, ser string) []string { ruleModel := `$rule = New-Object System.Security.AccessControl.%sAccessRule %s` permModel := `%s = "%s", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"` adminPermVar := `$adminPerm` jujudPermVar := `$jujudPerm` - return []string{ - fmt.Sprintf(`$acl = Get-Acl -Path '%s'`, path), - - // Reset the ACL's on it and add administrator access only. - `$acl.SetAccessRuleProtection($true, $false)`, + rulesToAdd := []string{ // $adminsGroup must be defined before calling setACLs fmt.Sprintf(permModel, adminPermVar, `$adminsGroup`), - fmt.Sprintf(permModel, jujudPermVar, `jujud`), fmt.Sprintf(ruleModel, permType, adminPermVar), `$acl.AddAccessRule($rule)`, - fmt.Sprintf(ruleModel, permType, jujudPermVar), - `$acl.AddAccessRule($rule)`, + } + + if !series.IsWindowsNano(ser) { + jujudUserACLRules := []string{ + fmt.Sprintf(permModel, jujudPermVar, `jujud`), + fmt.Sprintf(ruleModel, permType, jujudPermVar), + `$acl.AddAccessRule($rule)`, + } + + rulesToAdd = append(rulesToAdd, jujudUserACLRules...) + } + + aclCmds := []string{ + fmt.Sprintf(`$acl = Get-Acl -Path '%s'`, path), + + // Reset the ACL's on it and add administrator access only. + `$acl.SetAccessRuleProtection($true, $false)`, + fmt.Sprintf(`Set-Acl -Path '%s' -AclObject $acl`, path), } + + return append(aclCmds[:2], append(rulesToAdd, aclCmds[2:]...)...) +} + +func addDownloadToolsCmds(ser string, certificate string, toolsURL string) ([]string, error) { + if series.IsWindowsNano(ser) { + parsedCert, err := cert.ParseCert(certificate) + if err != nil { + return nil, err + } + caCert := base64.URLEncoding.EncodeToString(parsedCert.Raw) + return []string{fmt.Sprintf(`$cacert = "%s"`, caCert), + `$cert_bytes = $cacert | %{ ,[System.Text.Encoding]::UTF8.GetBytes($_) }`, + `$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2(,$cert_bytes)`, + `$store = Get-Item Cert:\LocalMachine\AuthRoot`, + `$store.Open("ReadWrite")`, + `$store.Add($cert)`, + fmt.Sprintf(`ExecRetry { Invoke-FastWebRequest -URI '%s' -OutFile "$binDir\tools.tar.gz" }`, toolsURL), + }, nil + } else { + return []string{ + `$WebClient = New-Object System.Net.WebClient`, + `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}`, + fmt.Sprintf(`ExecRetry { $WebClient.DownloadFile('%s', "$binDir\tools.tar.gz") }`, + toolsURL), + }, nil + } } diff -Nru charm-2.1.1/src/github.com/juju/juju/cloudconfig/windows_userdata_test.go charm-2.2.0/src/github.com/juju/juju/cloudconfig/windows_userdata_test.go --- charm-2.1.1/src/github.com/juju/juju/cloudconfig/windows_userdata_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cloudconfig/windows_userdata_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -41,6 +41,194 @@ $ErrorActionPreference = $currErrorActionPreference } +Function GUnZip-File{ + Param( + $infile, + $outdir + ) + + $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) + $tempFile = "$env:TEMP\jujud.tar" + $tempOut = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) + $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress) + + $buffer = New-Object byte[](1024) + while($true) { + $read = $gzipstream.Read($buffer, 0, 1024) + if ($read -le 0){break} + $tempOut.Write($buffer, 0, $read) + } + $gzipStream.Close() + $tempOut.Close() + $input.Close() + + $in = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) + Untar-File $in $outdir + $in.Close() + rm $tempFile +} + +$HEADERSIZE = 512 + +Function Untar-File { + Param( + $inStream, + $outdir + ) + $DirectoryEntryType = 0x35 + $headerBytes = New-Object byte[]($HEADERSIZE) + + # $headerBytes is written inside, function returns whether we've reached the end + while(GetHeaderBytes $inStream $headerBytes) { + $fileName, $entryType, $sizeInBytes = GetFileInfoFromHeader $headerBytes + + $totalPath = Join-Path $outDir $fileName + if ($entryType -eq $DirectoryEntryType) { + [System.IO.Directory]::CreateDirectory($totalPath) + continue; + } + + $fName = [System.IO.Path]::GetFileName($totalPath) + $dirName = [System.IO.Path]::GetDirectoryName($totalPath) + [System.IO.Directory]::CreateDirectory($dirName) + $file = [System.IO.File]::Create($totalPath) + WriteTarEntryToFile $inStream $file $sizeInBytes + $file.Close() + } +} + +Function WriteTarEntryToFile { + Param( + $inStream, + $outFile, + $sizeInBytes + ) + $moveToAlign512 = 0 + $toRead = 0 + $buf = New-Object byte[](512) + + $remainingBytesInFile = $sizeInBytes + while ($remainingBytesInFile -ne 0) { + if ($remainingBytesInFile - 512 -lt 0) { + $moveToAlign512 = 512 - $remainingBytesInFile + $toRead = $remainingBytesInFile + } else { + $toRead = 512 + } + + $bytesRead = 0 + $bytesRemainingToRead = $toRead + while ($bytesRead -lt $toRead -and $bytesRemainingToRead -gt 0) { + $bytesRead = $inStream.Read($buf, $toRead - $bytesRemainingToRead, $bytesRemainingToRead) + $bytesRemainingToRead = $bytesRemainingToRead - $bytesRead + $remainingBytesInFile = $remainingBytesInFile - $bytesRead + $outFile.Write($buf, 0, $bytesRead) + } + + if ($moveToAlign512 -ne 0) { + $inStream.Seek($moveToAlign512, [System.IO.SeekOrigin]::Current) + } + } +} + +Function GetHeaderBytes { + Param($inStream, $headerBytes) + + $headerRead = 0 + $bytesRemaining = $HEADERSIZE + while ($bytesRemaining -gt 0) { + $headerRead = $inStream.Read($headerBytes, $HEADERSIZE - $bytesRemaining, $bytesRemaining) + $bytesRemaining -= $headerRead + if ($headerRead -le 0 -and $bytesRemaining -gt 0) { + throw "Error reading tar header. Header size invalid" + } + } + + # Proper end of archive is 2 empty headers + if (IsEmptyByteArray $headerBytes) { + $bytesRemaining = $HEADERSIZE + while ($bytesRemaining -gt 0) { + $headerRead = $inStream.Read($headerBytes, $HEADERSIZE - $bytesRemaining, $bytesRemaining) + $bytesRemaining -= $headerRead + if ($headerRead -le 0 -and $bytesRemaining -gt 0) { + throw "Broken end archive" + } + } + if ($bytesRemaining -eq 0 -and (IsEmptyByteArray($headerBytes))) { + return $false + } + throw "Error occurred: expected end of archive" + } + + return $true +} + +Function GetFileInfoFromHeader { + Param($headerBytes) + + $FileName = [System.Text.Encoding]::UTF8.GetString($headerBytes, 0, 100); + $EntryType = $headerBytes[156]; + $SizeInBytes = [Convert]::ToInt64([System.Text.Encoding]::ASCII.GetString($headerBytes, 124, 11).Trim(), 8); + Return $FileName.replace("` + "`" + `0", [String].Empty), $EntryType, $SizeInBytes +} + +Function IsEmptyByteArray { + Param ($bytes) + foreach($b in $bytes) { + if ($b -ne 0) { + return $false + } + } + return $true +} + +Function Get-FileSHA256{ + Param( + $FilePath + ) + try { + $hash = [Security.Cryptography.HashAlgorithm]::Create( "SHA256" ) + $stream = ([IO.StreamReader]$FilePath).BaseStream + $res = -join ($hash.ComputeHash($stream) | ForEach { "{0:x2}" -f $_ }) + $stream.Close() + return $res + } catch [System.Management.Automation.RuntimeException] { + return (Get-FileHash -Path $FilePath).Hash + } +} + +Function Invoke-FastWebRequest { + Param( + $URI, + $OutFile + ) + + if(!([System.Management.Automation.PSTypeName]'System.Net.Http.HttpClient').Type) + { + $assembly = [System.Reflection.Assembly]::LoadWithPartialName("System.Net.Http") + } + + $client = new-object System.Net.Http.HttpClient + + $task = $client.GetStreamAsync($URI) + $response = $task.Result + $outStream = New-Object IO.FileStream $OutFile, Create, Write, None + + try { + $totRead = 0 + $buffer = New-Object Byte[] 1MB + while (($read = $response.Read($buffer, 0, $buffer.Length)) -gt 0) { + $totRead += $read + $outStream.Write($buffer, 0, $read); + } + } + finally { + $outStream.Close() + } +} + + + function create-account ([string]$accountName, [string]$accountDescription, [string]$password) { $hostname = hostname $comp = [adsi]"WinNT://$hostname" @@ -444,384 +632,6 @@ } } -$Source = @" -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Text; - -namespace Tarer -{ - public enum EntryType : byte - { - File = 0, - FileObsolete = 0x30, - HardLink = 0x31, - SymLink = 0x32, - CharDevice = 0x33, - BlockDevice = 0x34, - Directory = 0x35, - Fifo = 0x36, - } - - public interface ITarHeader - { - string FileName { get; set; } - long SizeInBytes { get; set; } - DateTime LastModification { get; set; } - int HeaderSize { get; } - EntryType EntryType { get; set; } - } - - public class Tar - { - private byte[] dataBuffer = new byte[512]; - private UsTarHeader header; - private Stream inStream; - private long remainingBytesInFile; - - public Tar(Stream tarredData) { - inStream = tarredData; - header = new UsTarHeader(); - } - - public ITarHeader FileInfo - { - get { return header; } - } - - public void ReadToEnd(string destDirectory) - { - while (MoveNext()) - { - string fileNameFromArchive = FileInfo.FileName; - string totalPath = destDirectory + Path.DirectorySeparatorChar + fileNameFromArchive; - if(UsTarHeader.IsPathSeparator(fileNameFromArchive[fileNameFromArchive.Length -1]) || FileInfo.EntryType == EntryType.Directory) - { - Directory.CreateDirectory(totalPath); - continue; - } - string fileName = Path.GetFileName(totalPath); - string directory = totalPath.Remove(totalPath.Length - fileName.Length); - Directory.CreateDirectory(directory); - using (FileStream file = File.Create(totalPath)) - { - Read(file); - } - } - } - - public void Read(Stream dataDestination) - { - int readBytes; - byte[] read; - while ((readBytes = Read(out read)) != -1) - { - dataDestination.Write(read, 0, readBytes); - } - } - - protected int Read(out byte[] buffer) - { - if(remainingBytesInFile == 0) - { - buffer = null; - return -1; - } - int align512 = -1; - long toRead = remainingBytesInFile - 512; - - if (toRead > 0) - { - toRead = 512; - } - else - { - align512 = 512 - (int)remainingBytesInFile; - toRead = remainingBytesInFile; - } - - int bytesRead = 0; - long bytesRemainingToRead = toRead; - while (bytesRead < toRead && bytesRemainingToRead > 0) - { - bytesRead = inStream.Read(dataBuffer, (int)(toRead-bytesRemainingToRead), (int)bytesRemainingToRead); - bytesRemainingToRead -= bytesRead; - remainingBytesInFile -= bytesRead; - } - - if(inStream.CanSeek && align512 > 0) - { - inStream.Seek(align512, SeekOrigin.Current); - } - else - { - while(align512 > 0) - { - inStream.ReadByte(); - --align512; - } - } - - buffer = dataBuffer; - return bytesRead; - } - - private static bool IsEmpty(IEnumerable buffer) - { - foreach(byte b in buffer) - { - if (b != 0) - { - return false; - } - } - return true; - } - - public bool MoveNext() - { - byte[] bytes = header.GetBytes(); - int headerRead; - int bytesRemaining = header.HeaderSize; - while (bytesRemaining > 0) - { - headerRead = inStream.Read(bytes, header.HeaderSize - bytesRemaining, bytesRemaining); - bytesRemaining -= headerRead; - if (headerRead <= 0 && bytesRemaining > 0) - { - throw new Exception("Error reading tar header. Header size invalid"); - } - } - - if(IsEmpty(bytes)) - { - bytesRemaining = header.HeaderSize; - while (bytesRemaining > 0) - { - headerRead = inStream.Read(bytes, header.HeaderSize - bytesRemaining, bytesRemaining); - bytesRemaining -= headerRead; - if (headerRead <= 0 && bytesRemaining > 0) - { - throw new Exception("Broken archive"); - } - } - if (bytesRemaining == 0 && IsEmpty(bytes)) - { - return false; - } - throw new Exception("Error occured: expected end of archive"); - } - - if (!header.UpdateHeaderFromBytes()) - { - throw new Exception("Checksum check failed"); - } - - remainingBytesInFile = header.SizeInBytes; - return true; - } - } - - internal class TarHeader : ITarHeader - { - private byte[] buffer = new byte[512]; - private long headerChecksum; - - private string fileName; - protected DateTime dateTime1970 = new DateTime(1970, 1, 1, 0, 0, 0); - private Tarer.EntryType _entryType; - public EntryType EntryType - { - get { return _entryType; } - set { _entryType = value; } - } - private static byte[] spaces = Encoding.ASCII.GetBytes(" "); - - public virtual string FileName - { - get { return fileName.Replace("\0",string.Empty); } - set { fileName = value; } - } - - private long _sizeInBytes; - public long SizeInBytes { - get{ return _sizeInBytes; } - set{ _sizeInBytes = value; } - } - - public string SizeString { get { return Convert.ToString(SizeInBytes, 8).PadLeft(11, '0'); } } - - private DateTime _lastModified; - public DateTime LastModification { - get{return _lastModified;} - set{_lastModified = value;} - } - - public virtual int HeaderSize { get { return 512; } } - - public byte[] GetBytes() - { - return buffer; - } - - public virtual bool UpdateHeaderFromBytes() - { - FileName = Encoding.UTF8.GetString(buffer, 0, 100); - - EntryType = (EntryType)buffer[156]; - - if((buffer[124] & 0x80) == 0x80) // if size in binary - { - long sizeBigEndian = BitConverter.ToInt64(buffer,0x80); - SizeInBytes = IPAddress.NetworkToHostOrder(sizeBigEndian); - } - else - { - SizeInBytes = Convert.ToInt64(Encoding.ASCII.GetString(buffer, 124, 11).Trim(), 8); - } - long unixTimeStamp = Convert.ToInt64(Encoding.ASCII.GetString(buffer,136,11).Trim(),8); - LastModification = dateTime1970.AddSeconds(unixTimeStamp); - - long storedChecksum = Convert.ToInt64(Encoding.ASCII.GetString(buffer,148,6).Trim(), 8); - RecalculateChecksum(buffer); - if (storedChecksum == headerChecksum) - { - return true; - } - - RecalculateAltChecksum(buffer); - return storedChecksum == headerChecksum; - } - - private void RecalculateAltChecksum(byte[] buf) - { - spaces.CopyTo(buf, 148); - headerChecksum = 0; - foreach(byte b in buf) - { - if((b & 0x80) == 0x80) - { - headerChecksum -= b ^ 0x80; - } - else - { - headerChecksum += b; - } - } - } - - protected virtual void RecalculateChecksum(byte[] buf) - { - // Set default value for checksum. That is 8 spaces. - spaces.CopyTo(buf, 148); - // Calculate checksum - headerChecksum = 0; - foreach (byte b in buf) - { - headerChecksum += b; - } - } - } - internal class UsTarHeader : TarHeader - { - private const string magic = "ustar"; - private const string version = " "; - - private string namePrefix = string.Empty; - - public override string FileName - { - get { return namePrefix.Replace("\0", string.Empty) + base.FileName.Replace("\0", string.Empty); } - set - { - if (value.Length > 255) - { - throw new Exception("UsTar fileName can not be longer than 255 chars"); - } - if (value.Length > 100) - { - int position = value.Length - 100; - while (!IsPathSeparator(value[position])) - { - ++position; - if (position == value.Length) - { - break; - } - } - if (position == value.Length) - { - position = value.Length - 100; - } - namePrefix = value.Substring(0, position); - base.FileName = value.Substring(position, value.Length - position); - } - else - { - base.FileName = value; - } - } - } - - public override bool UpdateHeaderFromBytes() - { - byte[] bytes = GetBytes(); - namePrefix = Encoding.UTF8.GetString(bytes, 347, 157); - return base.UpdateHeaderFromBytes(); - } - - internal static bool IsPathSeparator(char ch) - { - return (ch == '\\' || ch == '/' || ch == '|'); - } - } -} -"@ - -Add-Type -TypeDefinition $Source -Language CSharp - -Function GUnZip-File{ - Param( - $infile, - $outdir - ) - - $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) - $tempFile = "$env:TEMP\jujud.tar" - $tempOut = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) - $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress) - - $buffer = New-Object byte[](1024) - while($true){ - $read = $gzipstream.Read($buffer, 0, 1024) - if ($read -le 0){break} - $tempOut.Write($buffer, 0, $read) - } - $gzipStream.Close() - $tempOut.Close() - $input.Close() - - $in = New-Object System.IO.FileStream $tempFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) - $tar = New-Object Tarer.Tar($in) - $tar.ReadToEnd($outdir) - $in.Close() - rm $tempFile -} - -Function Get-FileSHA256{ - Param( - $FilePath - ) - $hash = [Security.Cryptography.HashAlgorithm]::Create( "SHA256" ) - $stream = ([IO.StreamReader]$FilePath).BaseStream - $res = -join ($hash.ComputeHash($stream) | ForEach { "{0:x2}" -f $_ }) - $stream.Close() - return $res -} - $juju_passwd = Get-RandomPassword 20 $juju_passwd += "^" create-account jujud "Juju Admin user" $juju_passwd @@ -840,22 +650,14 @@ $secpasswd = ConvertTo-SecureString $juju_passwd -AsPlainText -Force $jujuCreds = New-Object System.Management.Automation.PSCredential ($juju_user, $secpasswd) - mkdir -Force "C:\Juju" mkdir C:\Juju\tmp mkdir "C:\Juju\bin" mkdir "C:\Juju\lib\juju\locks" -$adminsGroup = (New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]) -$acl = Get-Acl -Path 'C:\Juju' -$acl.SetAccessRuleProtection($true, $false) -$adminPerm = "$adminsGroup", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" -$jujudPerm = "jujud", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" -$rule = New-Object System.Security.AccessControl.FileSystemAccessRule $adminPerm -$acl.AddAccessRule($rule) -$rule = New-Object System.Security.AccessControl.FileSystemAccessRule $jujudPerm -$acl.AddAccessRule($rule) -Set-Acl -Path 'C:\Juju' -AclObject $acl setx /m PATH "$env:PATH;C:\Juju\bin\" +$adminsGroup = (New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]) +icacls "C:\Juju" /inheritance:r /grant "${adminsGroup}:(OI)(CI)(F)" /t +icacls "C:\Juju" /inheritance:r /grant "jujud:(OI)(CI)(F)" /t Set-Content "C:\Juju\lib\juju\nonce.txt" "'FAKE_NONCE'" $binDir="C:\Juju\lib\juju\tools\1.2.3-win8-amd64" mkdir 'C:\Juju\log\juju' @@ -873,9 +675,9 @@ $acl = Get-Acl -Path 'HKLM:\SOFTWARE\juju-core' $acl.SetAccessRuleProtection($true, $false) $adminPerm = "$adminsGroup", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" -$jujudPerm = "jujud", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" $rule = New-Object System.Security.AccessControl.RegistryAccessRule $adminPerm $acl.AddAccessRule($rule) +$jujudPerm = "jujud", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" $rule = New-Object System.Security.AccessControl.RegistryAccessRule $jujudPerm $acl.AddAccessRule($rule) Set-Acl -Path 'HKLM:\SOFTWARE\juju-core' -AclObject $acl @@ -918,7 +720,11 @@ "@ cmd.exe /C mklink /D C:\Juju\lib\juju\tools\machine-10 1.2.3-win8-amd64 -New-Service -Credential $jujuCreds -Name 'jujud-machine-10' -DependsOn Winmgmt -DisplayName 'juju agent for machine-10' '"C:\Juju\lib\juju\tools\machine-10\jujud.exe" machine --data-dir "C:\Juju\lib\juju" --machine-id 10 --debug' +if ($jujuCreds) { + New-Service -Credential $jujuCreds -Name 'jujud-machine-10' -DependsOn Winmgmt -DisplayName 'juju agent for machine-10' '"C:\Juju\lib\juju\tools\machine-10\jujud.exe" machine --data-dir "C:\Juju\lib\juju" --machine-id 10 --debug' +} else { + New-Service -Name 'jujud-machine-10' -DependsOn Winmgmt -DisplayName 'juju agent for machine-10' '"C:\Juju\lib\juju\tools\machine-10\jujud.exe" machine --data-dir "C:\Juju\lib\juju" --machine-id 10 --debug' +} sc.exe failure 'jujud-machine-10' reset=5 actions=restart/1000 sc.exe failureflag 'jujud-machine-10' 1 Start-Service 'jujud-machine-10'` diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/action.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/action.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/action.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/action.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,7 +6,6 @@ import ( "io" - "github.com/juju/cmd" "github.com/juju/errors" "gopkg.in/juju/charm.v6-unstable" @@ -15,30 +14,6 @@ "github.com/juju/juju/cmd/modelcmd" ) -var actionDoc = ` -"juju action" executes and manages actions on units; it queues up new actions, -monitors the status of running actions, and retrieves the results of completed -actions. -` - -var actionPurpose = "execute, manage, monitor, and retrieve results of actions" - -// NewSuperCommand returns a new action super-command. -func NewSuperCommand() cmd.Command { - actionCmd := cmd.NewSuperCommand( - cmd.SuperCommandParams{ - Name: "action", - Doc: actionDoc, - UsagePrefix: "juju", - Purpose: actionPurpose, - }) - actionCmd.Register(newDefinedCommand()) - actionCmd.Register(newDoCommand()) - actionCmd.Register(newFetchCommand()) - actionCmd.Register(newStatusCommand()) - return actionCmd -} - // type APIClient represents the action API functionality. type APIClient interface { io.Closer diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/action_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/action_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/action_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/action_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -// Copyright 2014 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package action_test - -import ( - "strings" - - "github.com/juju/cmd" - jc "github.com/juju/testing/checkers" - gc "gopkg.in/check.v1" - - "github.com/juju/juju/testing" -) - -type ActionCommandSuite struct { - BaseActionSuite -} - -var _ = gc.Suite(&ActionCommandSuite{}) - -func (s *ActionCommandSuite) SetUpTest(c *gc.C) { - s.BaseActionSuite.SetUpTest(c) -} - -func (s *ActionCommandSuite) TestHelp(c *gc.C) { - // Check the normal help for any command - ctx, err := testing.RunCommand(c, s.command, "--help") - c.Assert(err, gc.IsNil) - - expected := "(?s).*^Usage: juju action \\[options\\] .+" - c.Check(testing.Stdout(ctx), gc.Matches, expected) - - supercommand, ok := s.command.(*cmd.SuperCommand) - c.Check(ok, jc.IsTrue) - expected = "(?sm).*^Summary:\n" + supercommand.Purpose + "$.*" - c.Check(testing.Stdout(ctx), gc.Matches, expected) - expected = "(?sm).*^Details:" + supercommand.Doc + "$.*" - c.Check(testing.Stdout(ctx), gc.Matches, expected) - - // Check that we've properly registered all subcommands - s.checkHelpSubCommands(c, ctx) -} - -func (s *ActionCommandSuite) checkHelpSubCommands(c *gc.C, ctx *cmd.Context) { - var expectedSubCommmands = [][]string{ - {"defined", "show actions defined for a service"}, - {"do", "queue an action for execution"}, - {"fetch", "show results of an action by ID"}, - {"help", "show help on a command or other topic"}, - {"status", "show results of all actions filtered by optional ID prefix"}, - } - - // Check that we have registered all the sub commands by - // inspecting the help output. - var subsFound [][]string - commandHelp := strings.SplitAfter(testing.Stdout(ctx), "commands:")[1] - commandHelp = strings.TrimSpace(commandHelp) - for _, line := range strings.Split(commandHelp, "\n") { - subcommand := strings.Split(line, " - ") - c.Assert(len(subcommand), gc.Equals, 2) - name := strings.TrimSpace(subcommand[0]) - desc := strings.TrimSpace(subcommand[1]) - subsFound = append(subsFound, []string{name, desc}) - } - - c.Check(subsFound, jc.DeepEquals, expectedSubCommmands) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/defined.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/defined.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/defined.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/defined.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,103 +0,0 @@ -// Copyright 2014, 2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package action - -import ( - "github.com/juju/cmd" - errors "github.com/juju/errors" - "github.com/juju/names" - "launchpad.net/gnuflag" - - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/cmd/modelcmd" -) - -func newDefinedCommand() cmd.Command { - return modelcmd.Wrap(&definedCommand{}) -} - -// definedCommand lists actions defined by the charm of a given service. -type definedCommand struct { - ActionCommandBase - serviceTag names.ServiceTag - fullSchema bool - out cmd.Output -} - -const definedDoc = ` -Show the actions available to run on the target service, with a short -description. To show the full schema for the actions, use --schema. - -For more information, see also the 'do' subcommand, which executes actions. -` - -// Set up the output. -func (c *definedCommand) SetFlags(f *gnuflag.FlagSet) { - c.out.AddFlags(f, "smart", cmd.DefaultFormatters) - f.BoolVar(&c.fullSchema, "schema", false, "display the full action schema") -} - -func (c *definedCommand) Info() *cmd.Info { - return &cmd.Info{ - Name: "defined", - Args: "", - Purpose: "show actions defined for a service", - Doc: definedDoc, - } -} - -// Init validates the service name and any other options. -func (c *definedCommand) Init(args []string) error { - switch len(args) { - case 0: - return errors.New("no service name specified") - case 1: - svcName := args[0] - if !names.IsValidService(svcName) { - return errors.Errorf("invalid service name %q", svcName) - } - c.serviceTag = names.NewServiceTag(svcName) - return nil - default: - return cmd.CheckEmpty(args[1:]) - } -} - -// Run grabs the Actions spec from the api. It then sets up a sensible -// output format for the map. -func (c *definedCommand) Run(ctx *cmd.Context) error { - api, err := c.NewActionAPIClient() - if err != nil { - return err - } - defer api.Close() - - actions, err := api.ServiceCharmActions(params.Entity{c.serviceTag.String()}) - if err != nil { - return err - } - - output := actions.ActionSpecs - if len(output) == 0 { - return c.out.Write(ctx, "No actions defined for "+c.serviceTag.Id()) - } - - if c.fullSchema { - verboseSpecs := make(map[string]interface{}) - for k, v := range output { - verboseSpecs[k] = v.Params - } - - return c.out.Write(ctx, verboseSpecs) - } - - shortOutput := make(map[string]string) - for name, action := range actions.ActionSpecs { - shortOutput[name] = action.Description - if shortOutput[name] == "" { - shortOutput[name] = "No description" - } - } - return c.out.Write(ctx, shortOutput) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/defined_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/defined_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/defined_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/defined_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,176 +0,0 @@ -// Copyright 2014 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package action_test - -import ( - "bytes" - "errors" - "strings" - - "github.com/juju/cmd" - "github.com/juju/names" - jc "github.com/juju/testing/checkers" - gc "gopkg.in/check.v1" - "gopkg.in/juju/charm.v6-unstable" - "gopkg.in/yaml.v2" - - "github.com/juju/juju/cmd/juju/action" - "github.com/juju/juju/state" - "github.com/juju/juju/testing" -) - -type DefinedSuite struct { - BaseActionSuite - svc *state.Service - wrappedCommand cmd.Command - command *action.DefinedCommand -} - -var _ = gc.Suite(&DefinedSuite{}) - -func (s *DefinedSuite) SetUpTest(c *gc.C) { - s.BaseActionSuite.SetUpTest(c) - s.wrappedCommand, s.command = action.NewDefinedCommand(s.store) -} - -func (s *DefinedSuite) TestHelp(c *gc.C) { - s.checkHelp(c, s.wrappedCommand) -} - -func (s *DefinedSuite) TestInit(c *gc.C) { - tests := []struct { - should string - args []string - expectedSvc names.ServiceTag - expectedOutputSchema bool - expectedErr string - }{{ - should: "fail with missing service name", - args: []string{}, - expectedErr: "no service name specified", - }, { - should: "fail with invalid service name", - args: []string{invalidServiceId}, - expectedErr: "invalid service name \"" + invalidServiceId + "\"", - }, { - should: "fail with too many args", - args: []string{"two", "things"}, - expectedErr: "unrecognized args: \\[\"things\"\\]", - }, { - should: "init properly with valid service name", - args: []string{validServiceId}, - expectedSvc: names.NewServiceTag(validServiceId), - }, { - should: "init properly with valid service name and --schema", - args: []string{"--schema", validServiceId}, - expectedOutputSchema: true, - expectedSvc: names.NewServiceTag(validServiceId), - }} - - for i, t := range tests { - for _, modelFlag := range s.modelFlags { - c.Logf("test %d should %s: juju actions defined %s", i, - t.should, strings.Join(t.args, " ")) - s.wrappedCommand, s.command = action.NewDefinedCommand(s.store) - args := append([]string{modelFlag, "dummymodel"}, t.args...) - err := testing.InitCommand(s.wrappedCommand, args) - if t.expectedErr == "" { - c.Check(s.command.ServiceTag(), gc.Equals, t.expectedSvc) - c.Check(s.command.FullSchema(), gc.Equals, t.expectedOutputSchema) - } else { - c.Check(err, gc.ErrorMatches, t.expectedErr) - } - } - } -} - -func (s *DefinedSuite) TestRun(c *gc.C) { - tests := []struct { - should string - expectFullSchema bool - expectNoResults bool - expectMessage string - withArgs []string - withAPIErr string - withCharmActions *charm.Actions - expectedErr string - }{{ - should: "pass back API error correctly", - withArgs: []string{validServiceId}, - withAPIErr: "an API error", - expectedErr: "an API error", - }, { - should: "get short results properly", - withArgs: []string{validServiceId}, - withCharmActions: someCharmActions, - }, { - should: "get full schema results properly", - withArgs: []string{"--schema", validServiceId}, - expectFullSchema: true, - withCharmActions: someCharmActions, - }, { - should: "work properly when no results found", - withArgs: []string{validServiceId}, - expectNoResults: true, - expectMessage: "No actions defined for " + validServiceId, - withCharmActions: &charm.Actions{ActionSpecs: map[string]charm.ActionSpec{}}, - }} - - for i, t := range tests { - for _, modelFlag := range s.modelFlags { - func() { - c.Logf("test %d should %s", i, t.should) - - fakeClient := &fakeAPIClient{charmActions: t.withCharmActions} - if t.withAPIErr != "" { - fakeClient.apiErr = errors.New(t.withAPIErr) - } - restore := s.patchAPIClient(fakeClient) - defer restore() - - args := append([]string{modelFlag, "dummymodel"}, t.withArgs...) - s.wrappedCommand, s.command = action.NewDefinedCommand(s.store) - ctx, err := testing.RunCommand(c, s.wrappedCommand, args...) - - if t.expectedErr != "" || t.withAPIErr != "" { - c.Check(err, gc.ErrorMatches, t.expectedErr) - } else { - c.Assert(err, gc.IsNil) - result := ctx.Stdout.(*bytes.Buffer).Bytes() - if t.expectFullSchema { - checkFullSchema(c, t.withCharmActions, result) - } else if t.expectNoResults { - c.Check(string(result), gc.Matches, t.expectMessage+"(?sm).*") - } else { - checkSimpleSchema(c, t.withCharmActions, result) - } - } - - }() - } - } -} - -func checkFullSchema(c *gc.C, expected *charm.Actions, actual []byte) { - expectedOutput := make(map[string]interface{}) - for k, v := range expected.ActionSpecs { - expectedOutput[k] = v.Params - } - c.Check(string(actual), jc.YAMLEquals, expectedOutput) -} - -func checkSimpleSchema(c *gc.C, expected *charm.Actions, actualOutput []byte) { - specs := expected.ActionSpecs - expectedSpecs := make(map[string]string) - for name, spec := range specs { - expectedSpecs[name] = spec.Description - if expectedSpecs[name] == "" { - expectedSpecs[name] = "No description" - } - } - actual := make(map[string]string) - err := yaml.Unmarshal(actualOutput, &actual) - c.Assert(err, gc.IsNil) - c.Check(actual, jc.DeepEquals, expectedSpecs) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/do.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/do.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/do.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/do.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,263 +0,0 @@ -// Copyright 2014, 2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package action - -import ( - "fmt" - "regexp" - "strings" - - "github.com/juju/cmd" - "github.com/juju/errors" - "github.com/juju/names" - yaml "gopkg.in/yaml.v2" - "launchpad.net/gnuflag" - - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/cmd/juju/common" - "github.com/juju/juju/cmd/modelcmd" -) - -var keyRule = regexp.MustCompile("^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$") - -func newDoCommand() cmd.Command { - return modelcmd.Wrap(&doCommand{}) -} - -// doCommand enqueues an Action for running on the given unit with given -// params -type doCommand struct { - ActionCommandBase - unitTag names.UnitTag - actionName string - paramsYAML cmd.FileVar - parseStrings bool - out cmd.Output - args [][]string -} - -const doDoc = ` -Queue an Action for execution on a given unit, with a given set of params. -Displays the ID of the Action for use with 'juju kill', 'juju status', etc. - -Params are validated according to the charm for the unit's service. The -valid params can be seen using "juju action defined --schema". -Params may be in a yaml file which is passed with the --params flag, or they -may be specified by a key.key.key...=value format (see examples below.) - -Params given in the CLI invocation will be parsed as YAML unless the ---string-args flag is set. This can be helpful for values such as 'y', which -is a boolean true in YAML. - -If --params is passed, along with key.key...=value explicit arguments, the -explicit arguments will override the parameter file. - -Examples: - -$ juju action do mysql/3 backup -action: - -$ juju action fetch -result: - status: success - file: - size: 873.2 - units: GB - name: foo.sql - -$ juju action do mysql/3 backup --params parameters.yml -... -Params sent will be the contents of parameters.yml. -... - -$ juju action do mysql/3 backup out=out.tar.bz2 file.kind=xz file.quality=high -... -Params sent will be: - -out: out.tar.bz2 -file: - kind: xz - quality: high -... - -$ juju action do mysql/3 backup --params p.yml file.kind=xz file.quality=high -... -If p.yml contains: - -file: - location: /var/backups/mysql/ - kind: gzip - -then the merged args passed will be: - -file: - location: /var/backups/mysql/ - kind: xz - quality: high -... - -$ juju action do sleeper/0 pause time=1000 -... - -$ juju action do sleeper/0 pause --string-args time=1000 -... -The value for the "time" param will be the string literal "1000". -` - -// ActionNameRule describes the format an action name must match to be valid. -var ActionNameRule = regexp.MustCompile("^[a-z](?:[a-z-]*[a-z])?$") - -// SetFlags offers an option for YAML output. -func (c *doCommand) SetFlags(f *gnuflag.FlagSet) { - c.out.AddFlags(f, "smart", cmd.DefaultFormatters) - f.Var(&c.paramsYAML, "params", "path to yaml-formatted params file") - f.BoolVar(&c.parseStrings, "string-args", false, "use raw string values of CLI args") -} - -func (c *doCommand) Info() *cmd.Info { - return &cmd.Info{ - Name: "do", - Args: " [key.key.key...=value]", - Purpose: "queue an action for execution", - Doc: doDoc, - } -} - -// Init gets the unit tag, and checks for other correct args. -func (c *doCommand) Init(args []string) error { - switch len(args) { - case 0: - return errors.New("no unit specified") - case 1: - return errors.New("no action specified") - default: - // Grab and verify the unit and action names. - unitName := args[0] - if !names.IsValidUnit(unitName) { - return errors.Errorf("invalid unit name %q", unitName) - } - ActionName := args[1] - if valid := ActionNameRule.MatchString(ActionName); !valid { - return fmt.Errorf("invalid action name %q", ActionName) - } - c.unitTag = names.NewUnitTag(unitName) - c.actionName = ActionName - if len(args) == 2 { - return nil - } - // Parse CLI key-value args if they exist. - c.args = make([][]string, 0) - for _, arg := range args[2:] { - thisArg := strings.SplitN(arg, "=", 2) - if len(thisArg) != 2 { - return fmt.Errorf("argument %q must be of the form key...=value", arg) - } - keySlice := strings.Split(thisArg[0], ".") - // check each key for validity - for _, key := range keySlice { - if valid := keyRule.MatchString(key); !valid { - return fmt.Errorf("key %q must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", key) - } - } - // c.args={..., [key, key, key, key, value]} - c.args = append(c.args, append(keySlice, thisArg[1])) - } - return nil - } -} - -func (c *doCommand) Run(ctx *cmd.Context) error { - api, err := c.NewActionAPIClient() - if err != nil { - return err - } - defer api.Close() - - actionParams := map[string]interface{}{} - - if c.paramsYAML.Path != "" { - b, err := c.paramsYAML.Read(ctx) - if err != nil { - return err - } - - err = yaml.Unmarshal(b, &actionParams) - if err != nil { - return err - } - - conformantParams, err := common.ConformYAML(actionParams) - if err != nil { - return err - } - - betterParams, ok := conformantParams.(map[string]interface{}) - if !ok { - return errors.New("params must contain a YAML map with string keys") - } - - actionParams = betterParams - } - - // If we had explicit args {..., [key, key, key, key, value], ...} - // then iterate and set params ..., key.key.key.key=value, ... - for _, argSlice := range c.args { - valueIndex := len(argSlice) - 1 - keys := argSlice[:valueIndex] - value := argSlice[valueIndex] - cleansedValue := interface{}(value) - if !c.parseStrings { - err := yaml.Unmarshal([]byte(value), &cleansedValue) - if err != nil { - return err - } - } - // Insert the value in the map. - addValueToMap(keys, cleansedValue, actionParams) - } - - conformantParams, err := common.ConformYAML(actionParams) - if err != nil { - return err - } - - typedConformantParams, ok := conformantParams.(map[string]interface{}) - if !ok { - return errors.Errorf("params must be a map, got %T", typedConformantParams) - } - - actionParam := params.Actions{ - Actions: []params.Action{{ - Receiver: c.unitTag.String(), - Name: c.actionName, - Parameters: actionParams, - }}, - } - - results, err := api.Enqueue(actionParam) - if err != nil { - return err - } - if len(results.Results) != 1 { - return errors.New("illegal number of results returned") - } - - result := results.Results[0] - - if result.Error != nil { - return result.Error - } - - if result.Action == nil { - return errors.New("action failed to enqueue") - } - - tag, err := names.ParseActionTag(result.Action.Tag) - if err != nil { - return err - } - - output := map[string]string{"Action queued with id": tag.Id()} - return c.out.Write(ctx, output) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/do_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/do_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/do_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/do_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,406 +0,0 @@ -// Copyright 2014, 2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package action_test - -import ( - "bytes" - "errors" - "strings" - "unicode/utf8" - - "github.com/juju/names" - jc "github.com/juju/testing/checkers" - "github.com/juju/utils" - gc "gopkg.in/check.v1" - "gopkg.in/yaml.v2" - - "github.com/juju/juju/apiserver/common" - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/cmd/juju/action" - "github.com/juju/juju/testing" -) - -var ( - validParamsYaml = ` -out: name -compression: - kind: xz - quality: high -`[1:] - invalidParamsYaml = ` -broken-map: - foo: - foo - bar: baz -`[1:] - invalidUTFYaml = "out: ok" + string([]byte{0xFF, 0xFF}) -) - -type DoSuite struct { - BaseActionSuite - dir string -} - -var _ = gc.Suite(&DoSuite{}) - -func (s *DoSuite) SetUpTest(c *gc.C) { - s.BaseActionSuite.SetUpTest(c) - s.dir = c.MkDir() - c.Assert(utf8.ValidString(validParamsYaml), jc.IsTrue) - c.Assert(utf8.ValidString(invalidParamsYaml), jc.IsTrue) - c.Assert(utf8.ValidString(invalidUTFYaml), jc.IsFalse) - setupValueFile(c, s.dir, "validParams.yml", validParamsYaml) - setupValueFile(c, s.dir, "invalidParams.yml", invalidParamsYaml) - setupValueFile(c, s.dir, "invalidUTF.yml", invalidUTFYaml) -} - -func (s *DoSuite) TestHelp(c *gc.C) { - cmd, _ := action.NewDoCommand(s.store) - s.checkHelp(c, cmd) -} - -func (s *DoSuite) TestInit(c *gc.C) { - tests := []struct { - should string - args []string - expectUnit names.UnitTag - expectAction string - expectParamsYamlPath string - expectParseStrings bool - expectKVArgs [][]string - expectOutput string - expectError string - }{{ - should: "fail with missing args", - args: []string{}, - expectError: "no unit specified", - }, { - should: "fail with no action specified", - args: []string{validUnitId}, - expectError: "no action specified", - }, { - should: "fail with invalid unit tag", - args: []string{invalidUnitId, "valid-action-name"}, - expectError: "invalid unit name \"something-strange-\"", - }, { - should: "fail with invalid action name", - args: []string{validUnitId, "BadName"}, - expectError: "invalid action name \"BadName\"", - }, { - should: "fail with wrong formatting of k-v args", - args: []string{validUnitId, "valid-action-name", "uh"}, - expectError: "argument \"uh\" must be of the form key...=value", - }, { - should: "fail with wrong formatting of k-v args", - args: []string{validUnitId, "valid-action-name", "foo.Baz=3"}, - expectError: "key \"Baz\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", - }, { - should: "fail with wrong formatting of k-v args", - args: []string{validUnitId, "valid-action-name", "no-go?od=3"}, - expectError: "key \"no-go\\?od\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", - }, { - should: "work with empty values", - args: []string{validUnitId, "valid-action-name", "ok="}, - expectUnit: names.NewUnitTag(validUnitId), - expectAction: "valid-action-name", - expectKVArgs: [][]string{{"ok", ""}}, - }, { - should: "handle --parse-strings", - args: []string{validUnitId, "valid-action-name", "--string-args"}, - expectUnit: names.NewUnitTag(validUnitId), - expectAction: "valid-action-name", - expectParseStrings: true, - }, { - // cf. worker/uniter/runner/jujuc/action-set_test.go per @fwereade - should: "work with multiple '=' signs", - args: []string{validUnitId, "valid-action-name", "ok=this=is=weird="}, - expectUnit: names.NewUnitTag(validUnitId), - expectAction: "valid-action-name", - expectKVArgs: [][]string{{"ok", "this=is=weird="}}, - }, { - should: "init properly with no params", - args: []string{validUnitId, "valid-action-name"}, - expectUnit: names.NewUnitTag(validUnitId), - expectAction: "valid-action-name", - }, { - should: "handle --params properly", - args: []string{validUnitId, "valid-action-name", "--params=foo.yml"}, - expectUnit: names.NewUnitTag(validUnitId), - expectAction: "valid-action-name", - expectParamsYamlPath: "foo.yml", - }, { - should: "handle --params and key-value args", - args: []string{ - validUnitId, - "valid-action-name", - "--params=foo.yml", - "foo.bar=2", - "foo.baz.bo=3", - "bar.foo=hello", - }, - expectUnit: names.NewUnitTag(validUnitId), - expectAction: "valid-action-name", - expectParamsYamlPath: "foo.yml", - expectKVArgs: [][]string{ - {"foo", "bar", "2"}, - {"foo", "baz", "bo", "3"}, - {"bar", "foo", "hello"}, - }, - }, { - should: "handle key-value args with no --params", - args: []string{ - validUnitId, - "valid-action-name", - "foo.bar=2", - "foo.baz.bo=y", - "bar.foo=hello", - }, - expectUnit: names.NewUnitTag(validUnitId), - expectAction: "valid-action-name", - expectKVArgs: [][]string{ - {"foo", "bar", "2"}, - {"foo", "baz", "bo", "y"}, - {"bar", "foo", "hello"}, - }, - }} - - for i, t := range tests { - for _, modelFlag := range s.modelFlags { - wrappedCommand, command := action.NewDoCommand(s.store) - c.Logf("test %d: should %s:\n$ juju actions do %s\n", i, - t.should, strings.Join(t.args, " ")) - args := append([]string{modelFlag, "dummymodel"}, t.args...) - err := testing.InitCommand(wrappedCommand, args) - if t.expectError == "" { - c.Check(command.UnitTag(), gc.Equals, t.expectUnit) - c.Check(command.ActionName(), gc.Equals, t.expectAction) - c.Check(command.ParamsYAML().Path, gc.Equals, t.expectParamsYamlPath) - c.Check(command.Args(), jc.DeepEquals, t.expectKVArgs) - c.Check(command.ParseStrings(), gc.Equals, t.expectParseStrings) - } else { - c.Check(err, gc.ErrorMatches, t.expectError) - } - } - } -} - -func (s *DoSuite) TestRun(c *gc.C) { - tests := []struct { - should string - withArgs []string - withAPIErr string - withActionResults []params.ActionResult - expectedActionEnqueued params.Action - expectedErr string - }{{ - should: "fail with multiple results", - withArgs: []string{validUnitId, "some-action"}, - withActionResults: []params.ActionResult{ - {Action: ¶ms.Action{Tag: validActionTagString}}, - {Action: ¶ms.Action{Tag: validActionTagString}}, - }, - expectedErr: "illegal number of results returned", - }, { - should: "fail with API error", - withArgs: []string{validUnitId, "some-action"}, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: validActionTagString}}, - }, - withAPIErr: "something wrong in API", - expectedErr: "something wrong in API", - }, { - should: "fail with error in result", - withArgs: []string{validUnitId, "some-action"}, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: validActionTagString}, - Error: common.ServerError(errors.New("database error")), - }}, - expectedErr: "database error", - }, { - should: "fail with invalid tag in result", - withArgs: []string{validUnitId, "some-action"}, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: invalidActionTagString}, - }}, - expectedErr: "\"" + invalidActionTagString + "\" is not a valid action tag", - }, { - should: "fail with missing file passed", - withArgs: []string{validUnitId, "some-action", - "--params", s.dir + "/" + "missing.yml", - }, - expectedErr: "open .*missing.yml: " + utils.NoSuchFileErrRegexp, - }, { - should: "fail with invalid yaml in file", - withArgs: []string{validUnitId, "some-action", - "--params", s.dir + "/" + "invalidParams.yml", - }, - expectedErr: "yaml: line 3: mapping values are not allowed in this context", - }, { - should: "fail with invalid UTF in file", - withArgs: []string{validUnitId, "some-action", - "--params", s.dir + "/" + "invalidUTF.yml", - }, - expectedErr: "yaml: invalid leading UTF-8 octet", - }, { - should: "fail with invalid YAML passed as arg and no --string-args", - withArgs: []string{validUnitId, "some-action", "foo.bar=\""}, - expectedErr: "yaml: found unexpected end of stream", - }, { - should: "enqueue a basic action with no params", - withArgs: []string{validUnitId, "some-action"}, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: validActionTagString}, - }}, - expectedActionEnqueued: params.Action{ - Name: "some-action", - Parameters: map[string]interface{}{}, - Receiver: names.NewUnitTag(validUnitId).String(), - }, - }, { - should: "enqueue an action with some explicit params", - withArgs: []string{validUnitId, "some-action", - "out.name=bar", - "out.kind=tmpfs", - "out.num=3", - "out.boolval=y", - }, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: validActionTagString}, - }}, - expectedActionEnqueued: params.Action{ - Name: "some-action", - Receiver: names.NewUnitTag(validUnitId).String(), - Parameters: map[string]interface{}{ - "out": map[string]interface{}{ - "name": "bar", - "kind": "tmpfs", - "num": 3, - "boolval": true, - }, - }, - }, - }, { - should: "enqueue an action with some raw string params", - withArgs: []string{validUnitId, "some-action", "--string-args", - "out.name=bar", - "out.kind=tmpfs", - "out.num=3", - "out.boolval=y", - }, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: validActionTagString}, - }}, - expectedActionEnqueued: params.Action{ - Name: "some-action", - Receiver: names.NewUnitTag(validUnitId).String(), - Parameters: map[string]interface{}{ - "out": map[string]interface{}{ - "name": "bar", - "kind": "tmpfs", - "num": "3", - "boolval": "y", - }, - }, - }, - }, { - should: "enqueue an action with file params plus CLI args", - withArgs: []string{validUnitId, "some-action", - "--params", s.dir + "/" + "validParams.yml", - "compression.kind=gz", - "compression.fast=true", - }, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: validActionTagString}, - }}, - expectedActionEnqueued: params.Action{ - Name: "some-action", - Receiver: names.NewUnitTag(validUnitId).String(), - Parameters: map[string]interface{}{ - "out": "name", - "compression": map[string]interface{}{ - "kind": "gz", - "quality": "high", - "fast": true, - }, - }, - }, - }, { - should: "enqueue an action with file params and explicit params", - withArgs: []string{validUnitId, "some-action", - "out.name=bar", - "out.kind=tmpfs", - "compression.quality.speed=high", - "compression.quality.size=small", - "--params", s.dir + "/" + "validParams.yml", - }, - withActionResults: []params.ActionResult{{ - Action: ¶ms.Action{Tag: validActionTagString}, - }}, - expectedActionEnqueued: params.Action{ - Name: "some-action", - Receiver: names.NewUnitTag(validUnitId).String(), - Parameters: map[string]interface{}{ - "out": map[string]interface{}{ - "name": "bar", - "kind": "tmpfs", - }, - "compression": map[string]interface{}{ - "kind": "xz", - "quality": map[string]interface{}{ - "speed": "high", - "size": "small", - }, - }, - }, - }, - }} - - for i, t := range tests { - for _, modelFlag := range s.modelFlags { - func() { - c.Logf("test %d: should %s:\n$ juju actions do %s\n", i, - t.should, strings.Join(t.withArgs, " ")) - fakeClient := &fakeAPIClient{ - actionResults: t.withActionResults, - } - if t.withAPIErr != "" { - fakeClient.apiErr = errors.New(t.withAPIErr) - } - restore := s.patchAPIClient(fakeClient) - defer restore() - - wrappedCommand, _ := action.NewDoCommand(s.store) - args := append([]string{modelFlag, "dummymodel"}, t.withArgs...) - ctx, err := testing.RunCommand(c, wrappedCommand, args...) - - if t.expectedErr != "" || t.withAPIErr != "" { - c.Check(err, gc.ErrorMatches, t.expectedErr) - } else { - c.Assert(err, gc.IsNil) - // Before comparing, double-check to avoid - // panics in malformed tests. - c.Assert(len(t.withActionResults), gc.Equals, 1) - // Make sure the test's expected Action was - // non-nil and correct. - c.Assert(t.withActionResults[0].Action, gc.NotNil) - expectedTag, err := names.ParseActionTag(t.withActionResults[0].Action.Tag) - c.Assert(err, gc.IsNil) - // Make sure the CLI responded with the expected tag - keyToCheck := "Action queued with id" - expectedMap := map[string]string{keyToCheck: expectedTag.Id()} - outputResult := ctx.Stdout.(*bytes.Buffer).Bytes() - resultMap := make(map[string]string) - err = yaml.Unmarshal(outputResult, &resultMap) - c.Assert(err, gc.IsNil) - c.Check(resultMap, jc.DeepEquals, expectedMap) - // Make sure the Action sent to the API to be - // enqueued was indeed the expected map - enqueued := fakeClient.EnqueuedActions() - c.Assert(enqueued.Actions, gc.HasLen, 1) - c.Check(enqueued.Actions[0], jc.DeepEquals, t.expectedActionEnqueued) - } - }() - } - } -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/export_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/export_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/export_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/export_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -17,72 +17,72 @@ AddValueToMap = addValueToMap ) -type FetchCommand struct { - *fetchCommand +type ShowOutputCommand struct { + *showOutputCommand } type StatusCommand struct { *statusCommand } -type DoCommand struct { - *doCommand +type RunCommand struct { + *runCommand } -func (c *DoCommand) UnitTag() names.UnitTag { +func (c *RunCommand) UnitTag() names.UnitTag { return c.unitTag } -func (c *DoCommand) ActionName() string { +func (c *RunCommand) ActionName() string { return c.actionName } -func (c *DoCommand) ParseStrings() bool { +func (c *RunCommand) ParseStrings() bool { return c.parseStrings } -func (c *DoCommand) ParamsYAML() cmd.FileVar { +func (c *RunCommand) ParamsYAML() cmd.FileVar { return c.paramsYAML } -func (c *DoCommand) Args() [][]string { +func (c *RunCommand) Args() [][]string { return c.args } -type DefinedCommand struct { - *definedCommand +type ListCommand struct { + *listCommand } -func (c *DefinedCommand) ServiceTag() names.ServiceTag { +func (c *ListCommand) ServiceTag() names.ServiceTag { return c.serviceTag } -func (c *DefinedCommand) FullSchema() bool { +func (c *ListCommand) FullSchema() bool { return c.fullSchema } -func NewFetchCommand(store jujuclient.ClientStore) (cmd.Command, *FetchCommand) { - c := &fetchCommand{} +func NewShowOutputCommandForTest(store jujuclient.ClientStore) (cmd.Command, *ShowOutputCommand) { + c := &showOutputCommand{} c.SetClientStore(store) - return modelcmd.Wrap(c), &FetchCommand{c} + return modelcmd.Wrap(c), &ShowOutputCommand{c} } -func NewStatusCommand(store jujuclient.ClientStore) (cmd.Command, *StatusCommand) { +func NewStatusCommandForTest(store jujuclient.ClientStore) (cmd.Command, *StatusCommand) { c := &statusCommand{} c.SetClientStore(store) return modelcmd.Wrap(c), &StatusCommand{c} } -func NewDefinedCommand(store jujuclient.ClientStore) (cmd.Command, *DefinedCommand) { - c := &definedCommand{} +func NewListCommandForTest(store jujuclient.ClientStore) (cmd.Command, *ListCommand) { + c := &listCommand{} c.SetClientStore(store) - return modelcmd.Wrap(c, modelcmd.ModelSkipDefault), &DefinedCommand{c} + return modelcmd.Wrap(c, modelcmd.ModelSkipDefault), &ListCommand{c} } -func NewDoCommand(store jujuclient.ClientStore) (cmd.Command, *DoCommand) { - c := &doCommand{} +func NewRunCommandForTest(store jujuclient.ClientStore) (cmd.Command, *RunCommand) { + c := &runCommand{} c.SetClientStore(store) - return modelcmd.Wrap(c, modelcmd.ModelSkipDefault), &DoCommand{c} + return modelcmd.Wrap(c, modelcmd.ModelSkipDefault), &RunCommand{c} } func ActionResultsToMap(results []params.ActionResult) map[string]interface{} { diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/fetch.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/fetch.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/fetch.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/fetch.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,213 +0,0 @@ -// Copyright 2014, 2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package action - -import ( - "regexp" - "time" - - "github.com/juju/cmd" - errors "github.com/juju/errors" - "launchpad.net/gnuflag" - - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/cmd/modelcmd" -) - -func newFetchCommand() cmd.Command { - return modelcmd.Wrap(&fetchCommand{}) -} - -// fetchCommand fetches the results of an action by ID. -type fetchCommand struct { - ActionCommandBase - out cmd.Output - requestedId string - fullSchema bool - wait string -} - -const fetchDoc = ` -Show the results returned by an action with the given ID. A partial ID may -also be used. To block until the result is known completed or failed, use -the --wait flag with a duration, as in --wait 5s or --wait 1h. Use --wait 0 -to wait indefinitely. If units are left off, seconds are assumed. - -The default behavior without --wait is to immediately check and return; if -the results are "pending" then only the available information will be -displayed. This is also the behavior when any negative time is given. -` - -// Set up the output. -func (c *fetchCommand) SetFlags(f *gnuflag.FlagSet) { - c.out.AddFlags(f, "smart", cmd.DefaultFormatters) - f.StringVar(&c.wait, "wait", "-1s", "wait for results") -} - -func (c *fetchCommand) Info() *cmd.Info { - return &cmd.Info{ - Name: "fetch", - Args: "", - Purpose: "show results of an action by ID", - Doc: fetchDoc, - } -} - -// Init validates the action ID and any other options. -func (c *fetchCommand) Init(args []string) error { - switch len(args) { - case 0: - return errors.New("no action ID specified") - case 1: - c.requestedId = args[0] - return nil - default: - return cmd.CheckEmpty(args[1:]) - } -} - -// Run issues the API call to get Actions by ID. -func (c *fetchCommand) Run(ctx *cmd.Context) error { - // Check whether units were left off our time string. - r := regexp.MustCompile("[a-zA-Z]") - matches := r.FindStringSubmatch(c.wait[len(c.wait)-1:]) - // If any match, we have units. Otherwise, we don't; assume seconds. - if len(matches) == 0 { - c.wait = c.wait + "s" - } - - waitDur, err := time.ParseDuration(c.wait) - if err != nil { - return err - } - - api, err := c.NewActionAPIClient() - if err != nil { - return err - } - defer api.Close() - - // tick every two seconds, to delay the loop timer. - tick := time.NewTimer(2 * time.Second) - wait := time.NewTimer(0 * time.Second) - - switch { - case waitDur.Nanoseconds() < 0: - // Negative duration signals immediate return. All is well. - case waitDur.Nanoseconds() == 0: - // Zero duration signals indefinite wait. Discard the tick. - wait = time.NewTimer(0 * time.Second) - _ = <-wait.C - default: - // Otherwise, start an ordinary timer. - wait = time.NewTimer(waitDur) - } - - result, err := timerLoop(api, c.requestedId, wait, tick) - if err != nil { - return err - } - - return c.out.Write(ctx, formatActionResult(result)) -} - -// timerLoop loops indefinitely to query the given API, until "wait" times -// out, using the "tick" timer to delay the API queries. It writes the -// result to the given output. -func timerLoop(api APIClient, requestedId string, wait, tick *time.Timer) (params.ActionResult, error) { - var ( - result params.ActionResult - err error - ) - - // Loop over results until we get "failed" or "completed". Wait for - // timer, and reset it each time. - for { - result, err = fetchResult(api, requestedId) - if err != nil { - return result, err - } - - // Whether or not we're waiting for a result, if a completed - // result arrives, we're done. - switch result.Status { - case params.ActionRunning, params.ActionPending: - default: - return result, nil - } - - // Block until a tick happens, or the timeout arrives. - select { - case _ = <-wait.C: - return result, nil - - case _ = <-tick.C: - tick.Reset(2 * time.Second) - } - } -} - -// fetchResult queries the given API for the given Action ID prefix, and -// makes sure the results are acceptable, returning an error if they are not. -func fetchResult(api APIClient, requestedId string) (params.ActionResult, error) { - none := params.ActionResult{} - - actionTag, err := getActionTagByPrefix(api, requestedId) - if err != nil { - return none, err - } - - actions, err := api.Actions(params.Entities{ - Entities: []params.Entity{{actionTag.String()}}, - }) - if err != nil { - return none, err - } - actionResults := actions.Results - numActionResults := len(actionResults) - if numActionResults == 0 { - return none, errors.Errorf("no results for action %s", requestedId) - } - if numActionResults != 1 { - return none, errors.Errorf("too many results for action %s", requestedId) - } - - result := actionResults[0] - if result.Error != nil { - return none, result.Error - } - - return result, nil -} - -// formatActionResult removes empty values from the given ActionResult and -// inserts the remaining ones in a map[string]interface{} for cmd.Output to -// write in an easy-to-read format. -func formatActionResult(result params.ActionResult) map[string]interface{} { - response := map[string]interface{}{"status": result.Status} - if result.Message != "" { - response["message"] = result.Message - } - if len(result.Output) != 0 { - response["results"] = result.Output - } - - if result.Enqueued.IsZero() && result.Started.IsZero() && result.Completed.IsZero() { - return response - } - - responseTiming := make(map[string]string) - for k, v := range map[string]time.Time{ - "enqueued": result.Enqueued, - "started": result.Started, - "completed": result.Completed, - } { - if !v.IsZero() { - responseTiming[k] = v.String() - } - } - response["timing"] = responseTiming - - return response -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/fetch_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/fetch_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/fetch_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/fetch_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,330 +0,0 @@ -// Copyright 2014-2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package action_test - -import ( - "bytes" - "errors" - "strings" - "time" - - gc "gopkg.in/check.v1" - - "github.com/juju/juju/apiserver/common" - "github.com/juju/juju/apiserver/params" - "github.com/juju/juju/cmd/juju/action" - "github.com/juju/juju/testing" -) - -type FetchSuite struct { - BaseActionSuite -} - -var _ = gc.Suite(&FetchSuite{}) - -func (s *FetchSuite) SetUpTest(c *gc.C) { - s.BaseActionSuite.SetUpTest(c) -} - -func (s *FetchSuite) TestHelp(c *gc.C) { - cmd, _ := action.NewFetchCommand(s.store) - s.checkHelp(c, cmd) -} - -func (s *FetchSuite) TestInit(c *gc.C) { - tests := []struct { - should string - args []string - expectError string - }{{ - should: "fail with missing arg", - args: []string{}, - expectError: "no action ID specified", - }, { - should: "fail with multiple args", - args: []string{"12345", "54321"}, - expectError: `unrecognized args: \["54321"\]`, - }} - - for i, t := range tests { - for _, modelFlag := range s.modelFlags { - c.Logf("test %d: it should %s: juju actions fetch %s", i, - t.should, strings.Join(t.args, " ")) - cmd, _ := action.NewFetchCommand(s.store) - args := append([]string{modelFlag, "dummymodel"}, t.args...) - err := testing.InitCommand(cmd, args) - if t.expectError != "" { - c.Check(err, gc.ErrorMatches, t.expectError) - } - } - } -} - -func (s *FetchSuite) TestRun(c *gc.C) { - tests := []struct { - should string - withClientWait string - withClientQueryID string - withAPIDelay time.Duration - withAPITimeout time.Duration - withTags params.FindTagsResults - withAPIResponse []params.ActionResult - withAPIError string - expectedErr string - expectedOutput string - }{{ - should: "handle wait-time formatting errors", - withClientWait: "not-a-duration-at-all", - expectedErr: "time: invalid duration not-a-duration-at-all", - }, { - should: "timeout if result never comes", - withClientWait: "3s", - withAPIDelay: 6 * time.Second, - withAPITimeout: 10 * time.Second, - withClientQueryID: validActionId, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{}}, - expectedOutput: ` -status: pending -timing: - enqueued: 2015-02-14 08:13:00 +0000 UTC - started: 2015-02-14 08:15:00 +0000 UTC -`[1:], - }, { - should: "pass api error through properly", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIError: "api call error", - expectedErr: "api call error", - }, { - should: "fail with no tag matches", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId), - expectedErr: `actions for identifier "` + validActionId + `" not found`, - }, { - should: "fail with no results", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{}, - expectedErr: "no results for action " + validActionId, - }, { - should: "error correctly with multiple results", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{}, {}}, - expectedErr: "too many results for action " + validActionId, - }, { - should: "pass through an error from the API server", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{ - Error: common.ServerError(errors.New("an apiserver error")), - }}, - expectedErr: "an apiserver error", - }, { - should: "only return once status is no longer running or pending", - withAPIDelay: 2 * time.Second, - withClientWait: "6s", - withClientQueryID: validActionId, - withAPITimeout: 4 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{ - Status: "running", - Output: map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "baz", - }, - }, - Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), - Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), - }}, - expectedErr: "test timed out before wait time", - }, { - should: "pretty-print action output", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{ - Status: "complete", - Message: "oh dear", - Output: map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "baz", - }, - }, - Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), - Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), - Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), - }}, - expectedOutput: ` -message: oh dear -results: - foo: - bar: baz -status: complete -timing: - completed: 2015-02-14 08:15:30 +0000 UTC - enqueued: 2015-02-14 08:13:00 +0000 UTC - started: 2015-02-14 08:15:00 +0000 UTC -`[1:], - }, { - should: "pretty-print action output with no completed time", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{ - Status: "pending", - Output: map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "baz", - }, - }, - Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), - Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), - }}, - expectedOutput: ` -results: - foo: - bar: baz -status: pending -timing: - enqueued: 2015-02-14 08:13:00 +0000 UTC - started: 2015-02-14 08:15:00 +0000 UTC -`[1:], - }, { - should: "pretty-print action output with no enqueued time", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{ - Status: "pending", - Output: map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "baz", - }, - }, - Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), - Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), - }}, - expectedOutput: ` -results: - foo: - bar: baz -status: pending -timing: - completed: 2015-02-14 08:15:30 +0000 UTC - started: 2015-02-14 08:15:00 +0000 UTC -`[1:], - }, { - should: "pretty-print action output with no started time", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{ - Status: "pending", - Output: map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "baz", - }, - }, - Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), - Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), - }}, - expectedOutput: ` -results: - foo: - bar: baz -status: pending -timing: - completed: 2015-02-14 08:15:30 +0000 UTC - enqueued: 2015-02-14 08:13:00 +0000 UTC -`[1:], - }, { - should: "set an appropriate timer and wait, get a result", - withClientQueryID: validActionId, - withAPITimeout: 10 * time.Second, - withClientWait: "4s", - withAPIDelay: 2 * time.Second, - withTags: tagsForIdPrefix(validActionId, validActionTagString), - withAPIResponse: []params.ActionResult{{ - Status: "completed", - Output: map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "baz", - }, - }, - Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), - Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), - }}, - expectedOutput: ` -results: - foo: - bar: baz -status: completed -timing: - completed: 2015-02-14 08:15:30 +0000 UTC - enqueued: 2015-02-14 08:13:00 +0000 UTC -`[1:], - }} - - for i, t := range tests { - for _, modelFlag := range s.modelFlags { - c.Logf("test %d (model flag %v): should %s", i, modelFlag, t.should) - testRunHelper( - c, s, - makeFakeClient( - t.withAPIDelay, - t.withAPITimeout, - t.withTags, - t.withAPIResponse, - t.withAPIError), - t.expectedErr, - t.expectedOutput, - t.withClientWait, - t.withClientQueryID, - modelFlag, - ) - } - } -} - -func testRunHelper(c *gc.C, s *FetchSuite, client *fakeAPIClient, expectedErr, expectedOutput, wait, query, modelFlag string) { - unpatch := s.BaseActionSuite.patchAPIClient(client) - defer unpatch() - args := append([]string{modelFlag, "dummymodel"}, query) - if wait != "" { - args = append(args, "--wait", wait) - } - cmd, _ := action.NewFetchCommand(s.store) - ctx, err := testing.RunCommand(c, cmd, args...) - if expectedErr != "" { - c.Check(err, gc.ErrorMatches, expectedErr) - } else { - c.Assert(err, gc.IsNil) - c.Check(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, expectedOutput) - } -} - -func makeFakeClient( - delay, timeout time.Duration, - tags params.FindTagsResults, - response []params.ActionResult, - errStr string, -) *fakeAPIClient { - client := &fakeAPIClient{ - delay: time.NewTimer(delay), - timeout: time.NewTimer(timeout), - actionTagMatches: tags, - actionResults: response, - } - if errStr != "" { - client.apiErr = errors.New(errStr) - } - return client -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/list.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/list.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/list.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/list.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,104 @@ +// Copyright 2014, 2015 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action + +import ( + "github.com/juju/cmd" + errors "github.com/juju/errors" + "github.com/juju/names" + "launchpad.net/gnuflag" + + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/cmd/modelcmd" +) + +func NewListCommand() cmd.Command { + return modelcmd.Wrap(&listCommand{}) +} + +// listCommand lists actions defined by the charm of a given service. +type listCommand struct { + ActionCommandBase + serviceTag names.ServiceTag + fullSchema bool + out cmd.Output +} + +const listDoc = ` +List the actions available to run on the target service, with a short +description. To show the full schema for the actions, use --schema. + +For more information, see also the 'run-ation' command, which executes actions. +` + +// Set up the output. +func (c *listCommand) SetFlags(f *gnuflag.FlagSet) { + c.out.AddFlags(f, "smart", cmd.DefaultFormatters) + f.BoolVar(&c.fullSchema, "schema", false, "display the full action schema") +} + +func (c *listCommand) Info() *cmd.Info { + return &cmd.Info{ + Name: "list-actions", + Args: "", + Purpose: "list actions defined for a service", + Doc: listDoc, + Aliases: []string{"actions"}, + } +} + +// Init validates the service name and any other options. +func (c *listCommand) Init(args []string) error { + switch len(args) { + case 0: + return errors.New("no service name specified") + case 1: + svcName := args[0] + if !names.IsValidService(svcName) { + return errors.Errorf("invalid service name %q", svcName) + } + c.serviceTag = names.NewServiceTag(svcName) + return nil + default: + return cmd.CheckEmpty(args[1:]) + } +} + +// Run grabs the Actions spec from the api. It then sets up a sensible +// output format for the map. +func (c *listCommand) Run(ctx *cmd.Context) error { + api, err := c.NewActionAPIClient() + if err != nil { + return err + } + defer api.Close() + + actions, err := api.ServiceCharmActions(params.Entity{c.serviceTag.String()}) + if err != nil { + return err + } + + output := actions.ActionSpecs + if len(output) == 0 { + return c.out.Write(ctx, "No actions defined for "+c.serviceTag.Id()) + } + + if c.fullSchema { + verboseSpecs := make(map[string]interface{}) + for k, v := range output { + verboseSpecs[k] = v.Params + } + + return c.out.Write(ctx, verboseSpecs) + } + + shortOutput := make(map[string]string) + for name, action := range actions.ActionSpecs { + shortOutput[name] = action.Description + if shortOutput[name] == "" { + shortOutput[name] = "No description" + } + } + return c.out.Write(ctx, shortOutput) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/list_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/list_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/list_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/list_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,172 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action_test + +import ( + "bytes" + "errors" + "strings" + + "github.com/juju/cmd" + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/juju/charm.v6-unstable" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/cmd/juju/action" + "github.com/juju/juju/state" + "github.com/juju/juju/testing" +) + +type ListSuite struct { + BaseActionSuite + svc *state.Service + wrappedCommand cmd.Command + command *action.ListCommand +} + +var _ = gc.Suite(&ListSuite{}) + +func (s *ListSuite) SetUpTest(c *gc.C) { + s.BaseActionSuite.SetUpTest(c) + s.wrappedCommand, s.command = action.NewListCommandForTest(s.store) +} + +func (s *ListSuite) TestInit(c *gc.C) { + tests := []struct { + should string + args []string + expectedSvc names.ServiceTag + expectedOutputSchema bool + expectedErr string + }{{ + should: "fail with missing service name", + args: []string{}, + expectedErr: "no service name specified", + }, { + should: "fail with invalid service name", + args: []string{invalidServiceId}, + expectedErr: "invalid service name \"" + invalidServiceId + "\"", + }, { + should: "fail with too many args", + args: []string{"two", "things"}, + expectedErr: "unrecognized args: \\[\"things\"\\]", + }, { + should: "init properly with valid service name", + args: []string{validServiceId}, + expectedSvc: names.NewServiceTag(validServiceId), + }, { + should: "init properly with valid service name and --schema", + args: []string{"--schema", validServiceId}, + expectedOutputSchema: true, + expectedSvc: names.NewServiceTag(validServiceId), + }} + + for i, t := range tests { + for _, modelFlag := range s.modelFlags { + c.Logf("test %d should %s: juju actions defined %s", i, + t.should, strings.Join(t.args, " ")) + s.wrappedCommand, s.command = action.NewListCommandForTest(s.store) + args := append([]string{modelFlag, "admin"}, t.args...) + err := testing.InitCommand(s.wrappedCommand, args) + if t.expectedErr == "" { + c.Check(s.command.ServiceTag(), gc.Equals, t.expectedSvc) + c.Check(s.command.FullSchema(), gc.Equals, t.expectedOutputSchema) + } else { + c.Check(err, gc.ErrorMatches, t.expectedErr) + } + } + } +} + +func (s *ListSuite) TestRun(c *gc.C) { + tests := []struct { + should string + expectFullSchema bool + expectNoResults bool + expectMessage string + withArgs []string + withAPIErr string + withCharmActions *charm.Actions + expectedErr string + }{{ + should: "pass back API error correctly", + withArgs: []string{validServiceId}, + withAPIErr: "an API error", + expectedErr: "an API error", + }, { + should: "get short results properly", + withArgs: []string{validServiceId}, + withCharmActions: someCharmActions, + }, { + should: "get full schema results properly", + withArgs: []string{"--schema", validServiceId}, + expectFullSchema: true, + withCharmActions: someCharmActions, + }, { + should: "work properly when no results found", + withArgs: []string{validServiceId}, + expectNoResults: true, + expectMessage: "No actions defined for " + validServiceId, + withCharmActions: &charm.Actions{ActionSpecs: map[string]charm.ActionSpec{}}, + }} + + for i, t := range tests { + for _, modelFlag := range s.modelFlags { + func() { + c.Logf("test %d should %s", i, t.should) + + fakeClient := &fakeAPIClient{charmActions: t.withCharmActions} + if t.withAPIErr != "" { + fakeClient.apiErr = errors.New(t.withAPIErr) + } + restore := s.patchAPIClient(fakeClient) + defer restore() + + args := append([]string{modelFlag, "admin"}, t.withArgs...) + s.wrappedCommand, s.command = action.NewListCommandForTest(s.store) + ctx, err := testing.RunCommand(c, s.wrappedCommand, args...) + + if t.expectedErr != "" || t.withAPIErr != "" { + c.Check(err, gc.ErrorMatches, t.expectedErr) + } else { + c.Assert(err, gc.IsNil) + result := ctx.Stdout.(*bytes.Buffer).Bytes() + if t.expectFullSchema { + checkFullSchema(c, t.withCharmActions, result) + } else if t.expectNoResults { + c.Check(string(result), gc.Matches, t.expectMessage+"(?sm).*") + } else { + checkSimpleSchema(c, t.withCharmActions, result) + } + } + + }() + } + } +} + +func checkFullSchema(c *gc.C, expected *charm.Actions, actual []byte) { + expectedOutput := make(map[string]interface{}) + for k, v := range expected.ActionSpecs { + expectedOutput[k] = v.Params + } + c.Check(string(actual), jc.YAMLEquals, expectedOutput) +} + +func checkSimpleSchema(c *gc.C, expected *charm.Actions, actualOutput []byte) { + specs := expected.ActionSpecs + expectedSpecs := make(map[string]string) + for name, spec := range specs { + expectedSpecs[name] = spec.Description + if expectedSpecs[name] == "" { + expectedSpecs[name] = "No description" + } + } + actual := make(map[string]string) + err := yaml.Unmarshal(actualOutput, &actual) + c.Assert(err, gc.IsNil) + c.Check(actual, jc.DeepEquals, expectedSpecs) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/package_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/package_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/package_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/package_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -6,7 +6,6 @@ import ( "errors" "io/ioutil" - "regexp" "testing" "time" @@ -49,7 +48,6 @@ func (s *BaseActionSuite) SetUpTest(c *gc.C) { s.FakeJujuXDGDataHomeSuite.SetUpTest(c) - s.command = action.NewSuperCommand() s.modelFlags = []string{"-m", "--model"} @@ -69,22 +67,6 @@ ) } -func (s *BaseActionSuite) checkHelp(c *gc.C, subcmd cmd.Command) { - ctx, err := coretesting.RunCommand(c, s.command, subcmd.Info().Name, "--help") - c.Assert(err, gc.IsNil) - - expected := "(?sm).*^Usage: juju action " + - regexp.QuoteMeta(subcmd.Info().Name) + - ` \[options\] ` + regexp.QuoteMeta(subcmd.Info().Args) + ".+" - c.Check(coretesting.Stdout(ctx), gc.Matches, expected) - - expected = "(?sm).*^Summary:\n" + regexp.QuoteMeta(subcmd.Info().Purpose) + "$.*" - c.Check(coretesting.Stdout(ctx), gc.Matches, expected) - - expected = "(?sm).*^Details:" + regexp.QuoteMeta(subcmd.Info().Doc) + "$.*" - c.Check(coretesting.Stdout(ctx), gc.Matches, expected) -} - var someCharmActions = &charm.Actions{ ActionSpecs: map[string]charm.ActionSpec{ "snapshot": { diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/run.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/run.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/run.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/run.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,263 @@ +// Copyright 2014, 2015 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action + +import ( + "fmt" + "regexp" + "strings" + + "github.com/juju/cmd" + "github.com/juju/errors" + "github.com/juju/names" + yaml "gopkg.in/yaml.v2" + "launchpad.net/gnuflag" + + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/cmd/juju/common" + "github.com/juju/juju/cmd/modelcmd" +) + +var keyRule = regexp.MustCompile("^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$") + +func NewRunCommand() cmd.Command { + return modelcmd.Wrap(&runCommand{}) +} + +// runCommand enqueues an Action for running on the given unit with given +// params +type runCommand struct { + ActionCommandBase + unitTag names.UnitTag + actionName string + paramsYAML cmd.FileVar + parseStrings bool + out cmd.Output + args [][]string +} + +const runDoc = ` +Queue an Action for execution on a given unit, with a given set of params. +Displays the ID of the Action for use with 'juju kill', 'juju status', etc. + +Params are validated according to the charm for the unit's service. The +valid params can be seen using "juju action defined --schema". +Params may be in a yaml file which is passed with the --params flag, or they +may be specified by a key.key.key...=value format (see examples below.) + +Params given in the CLI invocation will be parsed as YAML unless the +--string-args flag is set. This can be helpful for values such as 'y', which +is a boolean true in YAML. + +If --params is passed, along with key.key...=value explicit arguments, the +explicit arguments will override the parameter file. + +Examples: + +$ juju run-action mysql/3 backup +action: + +$ juju show-action-output +result: + status: success + file: + size: 873.2 + units: GB + name: foo.sql + +$ juju run-action mysql/3 backup --params parameters.yml +... +Params sent will be the contents of parameters.yml. +... + +$ juju run-action mysql/3 backup out=out.tar.bz2 file.kind=xz file.quality=high +... +Params sent will be: + +out: out.tar.bz2 +file: + kind: xz + quality: high +... + +$ juju run-action mysql/3 backup --params p.yml file.kind=xz file.quality=high +... +If p.yml contains: + +file: + location: /var/backups/mysql/ + kind: gzip + +then the merged args passed will be: + +file: + location: /var/backups/mysql/ + kind: xz + quality: high +... + +$ juju run-action sleeper/0 pause time=1000 +... + +$ juju run-action sleeper/0 pause --string-args time=1000 +... +The value for the "time" param will be the string literal "1000". +` + +// ActionNameRule describes the format an action name must match to be valid. +var ActionNameRule = regexp.MustCompile("^[a-z](?:[a-z-]*[a-z])?$") + +// SetFlags offers an option for YAML output. +func (c *runCommand) SetFlags(f *gnuflag.FlagSet) { + c.out.AddFlags(f, "smart", cmd.DefaultFormatters) + f.Var(&c.paramsYAML, "params", "path to yaml-formatted params file") + f.BoolVar(&c.parseStrings, "string-args", false, "use raw string values of CLI args") +} + +func (c *runCommand) Info() *cmd.Info { + return &cmd.Info{ + Name: "run-action", + Args: " [key.key.key...=value]", + Purpose: "queue an action for execution", + Doc: runDoc, + } +} + +// Init gets the unit tag, and checks for other correct args. +func (c *runCommand) Init(args []string) error { + switch len(args) { + case 0: + return errors.New("no unit specified") + case 1: + return errors.New("no action specified") + default: + // Grab and verify the unit and action names. + unitName := args[0] + if !names.IsValidUnit(unitName) { + return errors.Errorf("invalid unit name %q", unitName) + } + ActionName := args[1] + if valid := ActionNameRule.MatchString(ActionName); !valid { + return fmt.Errorf("invalid action name %q", ActionName) + } + c.unitTag = names.NewUnitTag(unitName) + c.actionName = ActionName + if len(args) == 2 { + return nil + } + // Parse CLI key-value args if they exist. + c.args = make([][]string, 0) + for _, arg := range args[2:] { + thisArg := strings.SplitN(arg, "=", 2) + if len(thisArg) != 2 { + return fmt.Errorf("argument %q must be of the form key...=value", arg) + } + keySlice := strings.Split(thisArg[0], ".") + // check each key for validity + for _, key := range keySlice { + if valid := keyRule.MatchString(key); !valid { + return fmt.Errorf("key %q must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", key) + } + } + // c.args={..., [key, key, key, key, value]} + c.args = append(c.args, append(keySlice, thisArg[1])) + } + return nil + } +} + +func (c *runCommand) Run(ctx *cmd.Context) error { + api, err := c.NewActionAPIClient() + if err != nil { + return err + } + defer api.Close() + + actionParams := map[string]interface{}{} + + if c.paramsYAML.Path != "" { + b, err := c.paramsYAML.Read(ctx) + if err != nil { + return err + } + + err = yaml.Unmarshal(b, &actionParams) + if err != nil { + return err + } + + conformantParams, err := common.ConformYAML(actionParams) + if err != nil { + return err + } + + betterParams, ok := conformantParams.(map[string]interface{}) + if !ok { + return errors.New("params must contain a YAML map with string keys") + } + + actionParams = betterParams + } + + // If we had explicit args {..., [key, key, key, key, value], ...} + // then iterate and set params ..., key.key.key.key=value, ... + for _, argSlice := range c.args { + valueIndex := len(argSlice) - 1 + keys := argSlice[:valueIndex] + value := argSlice[valueIndex] + cleansedValue := interface{}(value) + if !c.parseStrings { + err := yaml.Unmarshal([]byte(value), &cleansedValue) + if err != nil { + return err + } + } + // Insert the value in the map. + addValueToMap(keys, cleansedValue, actionParams) + } + + conformantParams, err := common.ConformYAML(actionParams) + if err != nil { + return err + } + + typedConformantParams, ok := conformantParams.(map[string]interface{}) + if !ok { + return errors.Errorf("params must be a map, got %T", typedConformantParams) + } + + actionParam := params.Actions{ + Actions: []params.Action{{ + Receiver: c.unitTag.String(), + Name: c.actionName, + Parameters: actionParams, + }}, + } + + results, err := api.Enqueue(actionParam) + if err != nil { + return err + } + if len(results.Results) != 1 { + return errors.New("illegal number of results returned") + } + + result := results.Results[0] + + if result.Error != nil { + return result.Error + } + + if result.Action == nil { + return errors.New("action failed to enqueue") + } + + tag, err := names.ParseActionTag(result.Action.Tag) + if err != nil { + return err + } + + output := map[string]string{"Action queued with id": tag.Id()} + return c.out.Write(ctx, output) +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/run_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/run_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/run_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/run_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,401 @@ +// Copyright 2014, 2015 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action_test + +import ( + "bytes" + "errors" + "strings" + "unicode/utf8" + + "github.com/juju/names" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/cmd/juju/action" + "github.com/juju/juju/testing" +) + +var ( + validParamsYaml = ` +out: name +compression: + kind: xz + quality: high +`[1:] + invalidParamsYaml = ` +broken-map: + foo: + foo + bar: baz +`[1:] + invalidUTFYaml = "out: ok" + string([]byte{0xFF, 0xFF}) +) + +type RunSuite struct { + BaseActionSuite + dir string +} + +var _ = gc.Suite(&RunSuite{}) + +func (s *RunSuite) SetUpTest(c *gc.C) { + s.BaseActionSuite.SetUpTest(c) + s.dir = c.MkDir() + c.Assert(utf8.ValidString(validParamsYaml), jc.IsTrue) + c.Assert(utf8.ValidString(invalidParamsYaml), jc.IsTrue) + c.Assert(utf8.ValidString(invalidUTFYaml), jc.IsFalse) + setupValueFile(c, s.dir, "validParams.yml", validParamsYaml) + setupValueFile(c, s.dir, "invalidParams.yml", invalidParamsYaml) + setupValueFile(c, s.dir, "invalidUTF.yml", invalidUTFYaml) +} + +func (s *RunSuite) TestInit(c *gc.C) { + tests := []struct { + should string + args []string + expectUnit names.UnitTag + expectAction string + expectParamsYamlPath string + expectParseStrings bool + expectKVArgs [][]string + expectOutput string + expectError string + }{{ + should: "fail with missing args", + args: []string{}, + expectError: "no unit specified", + }, { + should: "fail with no action specified", + args: []string{validUnitId}, + expectError: "no action specified", + }, { + should: "fail with invalid unit tag", + args: []string{invalidUnitId, "valid-action-name"}, + expectError: "invalid unit name \"something-strange-\"", + }, { + should: "fail with invalid action name", + args: []string{validUnitId, "BadName"}, + expectError: "invalid action name \"BadName\"", + }, { + should: "fail with wrong formatting of k-v args", + args: []string{validUnitId, "valid-action-name", "uh"}, + expectError: "argument \"uh\" must be of the form key...=value", + }, { + should: "fail with wrong formatting of k-v args", + args: []string{validUnitId, "valid-action-name", "foo.Baz=3"}, + expectError: "key \"Baz\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", + }, { + should: "fail with wrong formatting of k-v args", + args: []string{validUnitId, "valid-action-name", "no-go?od=3"}, + expectError: "key \"no-go\\?od\" must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric and hyphens", + }, { + should: "work with empty values", + args: []string{validUnitId, "valid-action-name", "ok="}, + expectUnit: names.NewUnitTag(validUnitId), + expectAction: "valid-action-name", + expectKVArgs: [][]string{{"ok", ""}}, + }, { + should: "handle --parse-strings", + args: []string{validUnitId, "valid-action-name", "--string-args"}, + expectUnit: names.NewUnitTag(validUnitId), + expectAction: "valid-action-name", + expectParseStrings: true, + }, { + // cf. worker/uniter/runner/jujuc/action-set_test.go per @fwereade + should: "work with multiple '=' signs", + args: []string{validUnitId, "valid-action-name", "ok=this=is=weird="}, + expectUnit: names.NewUnitTag(validUnitId), + expectAction: "valid-action-name", + expectKVArgs: [][]string{{"ok", "this=is=weird="}}, + }, { + should: "init properly with no params", + args: []string{validUnitId, "valid-action-name"}, + expectUnit: names.NewUnitTag(validUnitId), + expectAction: "valid-action-name", + }, { + should: "handle --params properly", + args: []string{validUnitId, "valid-action-name", "--params=foo.yml"}, + expectUnit: names.NewUnitTag(validUnitId), + expectAction: "valid-action-name", + expectParamsYamlPath: "foo.yml", + }, { + should: "handle --params and key-value args", + args: []string{ + validUnitId, + "valid-action-name", + "--params=foo.yml", + "foo.bar=2", + "foo.baz.bo=3", + "bar.foo=hello", + }, + expectUnit: names.NewUnitTag(validUnitId), + expectAction: "valid-action-name", + expectParamsYamlPath: "foo.yml", + expectKVArgs: [][]string{ + {"foo", "bar", "2"}, + {"foo", "baz", "bo", "3"}, + {"bar", "foo", "hello"}, + }, + }, { + should: "handle key-value args with no --params", + args: []string{ + validUnitId, + "valid-action-name", + "foo.bar=2", + "foo.baz.bo=y", + "bar.foo=hello", + }, + expectUnit: names.NewUnitTag(validUnitId), + expectAction: "valid-action-name", + expectKVArgs: [][]string{ + {"foo", "bar", "2"}, + {"foo", "baz", "bo", "y"}, + {"bar", "foo", "hello"}, + }, + }} + + for i, t := range tests { + for _, modelFlag := range s.modelFlags { + wrappedCommand, command := action.NewRunCommandForTest(s.store) + c.Logf("test %d: should %s:\n$ juju run-action %s\n", i, + t.should, strings.Join(t.args, " ")) + args := append([]string{modelFlag, "admin"}, t.args...) + err := testing.InitCommand(wrappedCommand, args) + if t.expectError == "" { + c.Check(command.UnitTag(), gc.Equals, t.expectUnit) + c.Check(command.ActionName(), gc.Equals, t.expectAction) + c.Check(command.ParamsYAML().Path, gc.Equals, t.expectParamsYamlPath) + c.Check(command.Args(), jc.DeepEquals, t.expectKVArgs) + c.Check(command.ParseStrings(), gc.Equals, t.expectParseStrings) + } else { + c.Check(err, gc.ErrorMatches, t.expectError) + } + } + } +} + +func (s *RunSuite) TestRun(c *gc.C) { + tests := []struct { + should string + withArgs []string + withAPIErr string + withActionResults []params.ActionResult + expectedActionEnqueued params.Action + expectedErr string + }{{ + should: "fail with multiple results", + withArgs: []string{validUnitId, "some-action"}, + withActionResults: []params.ActionResult{ + {Action: ¶ms.Action{Tag: validActionTagString}}, + {Action: ¶ms.Action{Tag: validActionTagString}}, + }, + expectedErr: "illegal number of results returned", + }, { + should: "fail with API error", + withArgs: []string{validUnitId, "some-action"}, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: validActionTagString}}, + }, + withAPIErr: "something wrong in API", + expectedErr: "something wrong in API", + }, { + should: "fail with error in result", + withArgs: []string{validUnitId, "some-action"}, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: validActionTagString}, + Error: common.ServerError(errors.New("database error")), + }}, + expectedErr: "database error", + }, { + should: "fail with invalid tag in result", + withArgs: []string{validUnitId, "some-action"}, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: invalidActionTagString}, + }}, + expectedErr: "\"" + invalidActionTagString + "\" is not a valid action tag", + }, { + should: "fail with missing file passed", + withArgs: []string{validUnitId, "some-action", + "--params", s.dir + "/" + "missing.yml", + }, + expectedErr: "open .*missing.yml: " + utils.NoSuchFileErrRegexp, + }, { + should: "fail with invalid yaml in file", + withArgs: []string{validUnitId, "some-action", + "--params", s.dir + "/" + "invalidParams.yml", + }, + expectedErr: "yaml: line 3: mapping values are not allowed in this context", + }, { + should: "fail with invalid UTF in file", + withArgs: []string{validUnitId, "some-action", + "--params", s.dir + "/" + "invalidUTF.yml", + }, + expectedErr: "yaml: invalid leading UTF-8 octet", + }, { + should: "fail with invalid YAML passed as arg and no --string-args", + withArgs: []string{validUnitId, "some-action", "foo.bar=\""}, + expectedErr: "yaml: found unexpected end of stream", + }, { + should: "enqueue a basic action with no params", + withArgs: []string{validUnitId, "some-action"}, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: validActionTagString}, + }}, + expectedActionEnqueued: params.Action{ + Name: "some-action", + Parameters: map[string]interface{}{}, + Receiver: names.NewUnitTag(validUnitId).String(), + }, + }, { + should: "enqueue an action with some explicit params", + withArgs: []string{validUnitId, "some-action", + "out.name=bar", + "out.kind=tmpfs", + "out.num=3", + "out.boolval=y", + }, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: validActionTagString}, + }}, + expectedActionEnqueued: params.Action{ + Name: "some-action", + Receiver: names.NewUnitTag(validUnitId).String(), + Parameters: map[string]interface{}{ + "out": map[string]interface{}{ + "name": "bar", + "kind": "tmpfs", + "num": 3, + "boolval": true, + }, + }, + }, + }, { + should: "enqueue an action with some raw string params", + withArgs: []string{validUnitId, "some-action", "--string-args", + "out.name=bar", + "out.kind=tmpfs", + "out.num=3", + "out.boolval=y", + }, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: validActionTagString}, + }}, + expectedActionEnqueued: params.Action{ + Name: "some-action", + Receiver: names.NewUnitTag(validUnitId).String(), + Parameters: map[string]interface{}{ + "out": map[string]interface{}{ + "name": "bar", + "kind": "tmpfs", + "num": "3", + "boolval": "y", + }, + }, + }, + }, { + should: "enqueue an action with file params plus CLI args", + withArgs: []string{validUnitId, "some-action", + "--params", s.dir + "/" + "validParams.yml", + "compression.kind=gz", + "compression.fast=true", + }, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: validActionTagString}, + }}, + expectedActionEnqueued: params.Action{ + Name: "some-action", + Receiver: names.NewUnitTag(validUnitId).String(), + Parameters: map[string]interface{}{ + "out": "name", + "compression": map[string]interface{}{ + "kind": "gz", + "quality": "high", + "fast": true, + }, + }, + }, + }, { + should: "enqueue an action with file params and explicit params", + withArgs: []string{validUnitId, "some-action", + "out.name=bar", + "out.kind=tmpfs", + "compression.quality.speed=high", + "compression.quality.size=small", + "--params", s.dir + "/" + "validParams.yml", + }, + withActionResults: []params.ActionResult{{ + Action: ¶ms.Action{Tag: validActionTagString}, + }}, + expectedActionEnqueued: params.Action{ + Name: "some-action", + Receiver: names.NewUnitTag(validUnitId).String(), + Parameters: map[string]interface{}{ + "out": map[string]interface{}{ + "name": "bar", + "kind": "tmpfs", + }, + "compression": map[string]interface{}{ + "kind": "xz", + "quality": map[string]interface{}{ + "speed": "high", + "size": "small", + }, + }, + }, + }, + }} + + for i, t := range tests { + for _, modelFlag := range s.modelFlags { + func() { + c.Logf("test %d: should %s:\n$ juju actions do %s\n", i, + t.should, strings.Join(t.withArgs, " ")) + fakeClient := &fakeAPIClient{ + actionResults: t.withActionResults, + } + if t.withAPIErr != "" { + fakeClient.apiErr = errors.New(t.withAPIErr) + } + restore := s.patchAPIClient(fakeClient) + defer restore() + + wrappedCommand, _ := action.NewRunCommandForTest(s.store) + args := append([]string{modelFlag, "admin"}, t.withArgs...) + ctx, err := testing.RunCommand(c, wrappedCommand, args...) + + if t.expectedErr != "" || t.withAPIErr != "" { + c.Check(err, gc.ErrorMatches, t.expectedErr) + } else { + c.Assert(err, gc.IsNil) + // Before comparing, double-check to avoid + // panics in malformed tests. + c.Assert(len(t.withActionResults), gc.Equals, 1) + // Make sure the test's expected Action was + // non-nil and correct. + c.Assert(t.withActionResults[0].Action, gc.NotNil) + expectedTag, err := names.ParseActionTag(t.withActionResults[0].Action.Tag) + c.Assert(err, gc.IsNil) + // Make sure the CLI responded with the expected tag + keyToCheck := "Action queued with id" + expectedMap := map[string]string{keyToCheck: expectedTag.Id()} + outputResult := ctx.Stdout.(*bytes.Buffer).Bytes() + resultMap := make(map[string]string) + err = yaml.Unmarshal(outputResult, &resultMap) + c.Assert(err, gc.IsNil) + c.Check(resultMap, jc.DeepEquals, expectedMap) + // Make sure the Action sent to the API to be + // enqueued was indeed the expected map + enqueued := fakeClient.EnqueuedActions() + c.Assert(enqueued.Actions, gc.HasLen, 1) + c.Check(enqueued.Actions[0], jc.DeepEquals, t.expectedActionEnqueued) + } + }() + } + } +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/showoutput.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/showoutput.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/showoutput.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/showoutput.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,223 @@ +// Copyright 2014, 2015 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action + +import ( + "regexp" + "time" + + "github.com/juju/cmd" + errors "github.com/juju/errors" + "launchpad.net/gnuflag" + + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/cmd/modelcmd" +) + +func NewShowOutputCommand() cmd.Command { + return modelcmd.Wrap(&showOutputCommand{}) +} + +// showOutputCommand fetches the results of an action by ID. +type showOutputCommand struct { + ActionCommandBase + out cmd.Output + requestedId string + fullSchema bool + wait string +} + +const showOutputDoc = ` +Show the results returned by an action with the given ID. A partial ID may +also be used. To block until the result is known completed or failed, use +the --wait flag with a duration, as in --wait 5s or --wait 1h. Use --wait 0 +to wait indefinitely. If units are left off, seconds are assumed. + +The default behavior without --wait is to immediately check and return; if +the results are "pending" then only the available information will be +displayed. This is also the behavior when any negative time is given. +` + +// Set up the output. +func (c *showOutputCommand) SetFlags(f *gnuflag.FlagSet) { + c.out.AddFlags(f, "smart", cmd.DefaultFormatters) + f.StringVar(&c.wait, "wait", "-1s", "wait for results") +} + +func (c *showOutputCommand) Info() *cmd.Info { + return &cmd.Info{ + Name: "show-action-output", + Args: "", + Purpose: "show results of an action by ID", + Doc: showOutputDoc, + } +} + +// Init validates the action ID and any other options. +func (c *showOutputCommand) Init(args []string) error { + switch len(args) { + case 0: + return errors.New("no action ID specified") + case 1: + c.requestedId = args[0] + return nil + default: + return cmd.CheckEmpty(args[1:]) + } +} + +// Run issues the API call to get Actions by ID. +func (c *showOutputCommand) Run(ctx *cmd.Context) error { + // Check whether units were left off our time string. + r := regexp.MustCompile("[a-zA-Z]") + matches := r.FindStringSubmatch(c.wait[len(c.wait)-1:]) + // If any match, we have units. Otherwise, we don't; assume seconds. + if len(matches) == 0 { + c.wait = c.wait + "s" + } + + waitDur, err := time.ParseDuration(c.wait) + if err != nil { + return err + } + + api, err := c.NewActionAPIClient() + if err != nil { + return err + } + defer api.Close() + + wait := time.NewTimer(0 * time.Second) + + switch { + case waitDur.Nanoseconds() < 0: + // Negative duration signals immediate return. All is well. + case waitDur.Nanoseconds() == 0: + // Zero duration signals indefinite wait. Discard the tick. + wait = time.NewTimer(0 * time.Second) + _ = <-wait.C + default: + // Otherwise, start an ordinary timer. + wait = time.NewTimer(waitDur) + } + + result, err := GetActionResult(api, c.requestedId, wait) + if err != nil { + return errors.Trace(err) + } + + return c.out.Write(ctx, FormatActionResult(result)) +} + +// GetActionResult tries to repeatedly fetch an action until it is +// in a completed state and then it returns it. +// It waits for a maximum of "wait" before returning with the latest action status. +func GetActionResult(api APIClient, requestedId string, wait *time.Timer) (params.ActionResult, error) { + + // tick every two seconds, to delay the loop timer. + // TODO(fwereade): 2016-03-17 lp:1558657 + tick := time.NewTimer(2 * time.Second) + + return timerLoop(api, requestedId, wait, tick) +} + +// timerLoop loops indefinitely to query the given API, until "wait" times +// out, using the "tick" timer to delay the API queries. It writes the +// result to the given output. +func timerLoop(api APIClient, requestedId string, wait, tick *time.Timer) (params.ActionResult, error) { + var ( + result params.ActionResult + err error + ) + + // Loop over results until we get "failed" or "completed". Wait for + // timer, and reset it each time. + for { + result, err = fetchResult(api, requestedId) + if err != nil { + return result, err + } + + // Whether or not we're waiting for a result, if a completed + // result arrives, we're done. + switch result.Status { + case params.ActionRunning, params.ActionPending: + default: + return result, nil + } + + // Block until a tick happens, or the timeout arrives. + select { + case _ = <-wait.C: + return result, nil + + case _ = <-tick.C: + tick.Reset(2 * time.Second) + } + } +} + +// fetchResult queries the given API for the given Action ID prefix, and +// makes sure the results are acceptable, returning an error if they are not. +func fetchResult(api APIClient, requestedId string) (params.ActionResult, error) { + none := params.ActionResult{} + + actionTag, err := getActionTagByPrefix(api, requestedId) + if err != nil { + return none, err + } + + actions, err := api.Actions(params.Entities{ + Entities: []params.Entity{{actionTag.String()}}, + }) + if err != nil { + return none, err + } + actionResults := actions.Results + numActionResults := len(actionResults) + if numActionResults == 0 { + return none, errors.Errorf("no results for action %s", requestedId) + } + if numActionResults != 1 { + return none, errors.Errorf("too many results for action %s", requestedId) + } + + result := actionResults[0] + if result.Error != nil { + return none, result.Error + } + + return result, nil +} + +// FormatActionResult removes empty values from the given ActionResult and +// inserts the remaining ones in a map[string]interface{} for cmd.Output to +// write in an easy-to-read format. +func FormatActionResult(result params.ActionResult) map[string]interface{} { + response := map[string]interface{}{"status": result.Status} + if result.Message != "" { + response["message"] = result.Message + } + if len(result.Output) != 0 { + response["results"] = result.Output + } + + if result.Enqueued.IsZero() && result.Started.IsZero() && result.Completed.IsZero() { + return response + } + + responseTiming := make(map[string]string) + for k, v := range map[string]time.Time{ + "enqueued": result.Enqueued, + "started": result.Started, + "completed": result.Completed, + } { + if !v.IsZero() { + responseTiming[k] = v.String() + } + } + response["timing"] = responseTiming + + return response +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/showoutput_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/showoutput_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/showoutput_test.go 1970-01-01 00:00:00.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/showoutput_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -0,0 +1,325 @@ +// Copyright 2014-2015 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package action_test + +import ( + "bytes" + "errors" + "strings" + "time" + + gc "gopkg.in/check.v1" + + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/cmd/juju/action" + "github.com/juju/juju/testing" +) + +type ShowOutputSuite struct { + BaseActionSuite +} + +var _ = gc.Suite(&ShowOutputSuite{}) + +func (s *ShowOutputSuite) SetUpTest(c *gc.C) { + s.BaseActionSuite.SetUpTest(c) +} + +func (s *ShowOutputSuite) TestInit(c *gc.C) { + tests := []struct { + should string + args []string + expectError string + }{{ + should: "fail with missing arg", + args: []string{}, + expectError: "no action ID specified", + }, { + should: "fail with multiple args", + args: []string{"12345", "54321"}, + expectError: `unrecognized args: \["54321"\]`, + }} + + for i, t := range tests { + for _, modelFlag := range s.modelFlags { + c.Logf("test %d: it should %s: juju show-action-output %s", i, + t.should, strings.Join(t.args, " ")) + cmd, _ := action.NewShowOutputCommandForTest(s.store) + args := append([]string{modelFlag, "admin"}, t.args...) + err := testing.InitCommand(cmd, args) + if t.expectError != "" { + c.Check(err, gc.ErrorMatches, t.expectError) + } + } + } +} + +func (s *ShowOutputSuite) TestRun(c *gc.C) { + tests := []struct { + should string + withClientWait string + withClientQueryID string + withAPIDelay time.Duration + withAPITimeout time.Duration + withTags params.FindTagsResults + withAPIResponse []params.ActionResult + withAPIError string + expectedErr string + expectedOutput string + }{{ + should: "handle wait-time formatting errors", + withClientWait: "not-a-duration-at-all", + expectedErr: "time: invalid duration not-a-duration-at-all", + }, { + should: "timeout if result never comes", + withClientWait: "3s", + withAPIDelay: 6 * time.Second, + withAPITimeout: 10 * time.Second, + withClientQueryID: validActionId, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{}}, + expectedOutput: ` +status: pending +timing: + enqueued: 2015-02-14 08:13:00 +0000 UTC + started: 2015-02-14 08:15:00 +0000 UTC +`[1:], + }, { + should: "pass api error through properly", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIError: "api call error", + expectedErr: "api call error", + }, { + should: "fail with no tag matches", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId), + expectedErr: `actions for identifier "` + validActionId + `" not found`, + }, { + should: "fail with no results", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{}, + expectedErr: "no results for action " + validActionId, + }, { + should: "error correctly with multiple results", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{}, {}}, + expectedErr: "too many results for action " + validActionId, + }, { + should: "pass through an error from the API server", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{ + Error: common.ServerError(errors.New("an apiserver error")), + }}, + expectedErr: "an apiserver error", + }, { + should: "only return once status is no longer running or pending", + withAPIDelay: 2 * time.Second, + withClientWait: "6s", + withClientQueryID: validActionId, + withAPITimeout: 4 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{ + Status: "running", + Output: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), + Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), + }}, + expectedErr: "test timed out before wait time", + }, { + should: "pretty-print action output", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{ + Status: "complete", + Message: "oh dear", + Output: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), + Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), + Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), + }}, + expectedOutput: ` +message: oh dear +results: + foo: + bar: baz +status: complete +timing: + completed: 2015-02-14 08:15:30 +0000 UTC + enqueued: 2015-02-14 08:13:00 +0000 UTC + started: 2015-02-14 08:15:00 +0000 UTC +`[1:], + }, { + should: "pretty-print action output with no completed time", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{ + Status: "pending", + Output: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), + Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), + }}, + expectedOutput: ` +results: + foo: + bar: baz +status: pending +timing: + enqueued: 2015-02-14 08:13:00 +0000 UTC + started: 2015-02-14 08:15:00 +0000 UTC +`[1:], + }, { + should: "pretty-print action output with no enqueued time", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{ + Status: "pending", + Output: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), + Started: time.Date(2015, time.February, 14, 8, 15, 0, 0, time.UTC), + }}, + expectedOutput: ` +results: + foo: + bar: baz +status: pending +timing: + completed: 2015-02-14 08:15:30 +0000 UTC + started: 2015-02-14 08:15:00 +0000 UTC +`[1:], + }, { + should: "pretty-print action output with no started time", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{ + Status: "pending", + Output: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), + Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), + }}, + expectedOutput: ` +results: + foo: + bar: baz +status: pending +timing: + completed: 2015-02-14 08:15:30 +0000 UTC + enqueued: 2015-02-14 08:13:00 +0000 UTC +`[1:], + }, { + should: "set an appropriate timer and wait, get a result", + withClientQueryID: validActionId, + withAPITimeout: 10 * time.Second, + withClientWait: "4s", + withAPIDelay: 2 * time.Second, + withTags: tagsForIdPrefix(validActionId, validActionTagString), + withAPIResponse: []params.ActionResult{{ + Status: "completed", + Output: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + Enqueued: time.Date(2015, time.February, 14, 8, 13, 0, 0, time.UTC), + Completed: time.Date(2015, time.February, 14, 8, 15, 30, 0, time.UTC), + }}, + expectedOutput: ` +results: + foo: + bar: baz +status: completed +timing: + completed: 2015-02-14 08:15:30 +0000 UTC + enqueued: 2015-02-14 08:13:00 +0000 UTC +`[1:], + }} + + for i, t := range tests { + for _, modelFlag := range s.modelFlags { + c.Logf("test %d (model flag %v): should %s", i, modelFlag, t.should) + testRunHelper( + c, s, + makeFakeClient( + t.withAPIDelay, + t.withAPITimeout, + t.withTags, + t.withAPIResponse, + t.withAPIError), + t.expectedErr, + t.expectedOutput, + t.withClientWait, + t.withClientQueryID, + modelFlag, + ) + } + } +} + +func testRunHelper(c *gc.C, s *ShowOutputSuite, client *fakeAPIClient, expectedErr, expectedOutput, wait, query, modelFlag string) { + unpatch := s.BaseActionSuite.patchAPIClient(client) + defer unpatch() + args := append([]string{modelFlag, "admin"}, query) + if wait != "" { + args = append(args, "--wait", wait) + } + cmd, _ := action.NewShowOutputCommandForTest(s.store) + ctx, err := testing.RunCommand(c, cmd, args...) + if expectedErr != "" { + c.Check(err, gc.ErrorMatches, expectedErr) + } else { + c.Assert(err, gc.IsNil) + c.Check(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, expectedOutput) + } +} + +func makeFakeClient( + delay, timeout time.Duration, + tags params.FindTagsResults, + response []params.ActionResult, + errStr string, +) *fakeAPIClient { + client := &fakeAPIClient{ + delay: time.NewTimer(delay), + timeout: time.NewTimer(timeout), + actionTagMatches: tags, + actionResults: response, + } + if errStr != "" { + client.apiErr = errors.New(errStr) + } + return client +} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/status.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/status.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/status.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/status.go 2016-04-28 06:03:34.000000000 +0000 @@ -13,7 +13,7 @@ "github.com/juju/juju/cmd/modelcmd" ) -func newStatusCommand() cmd.Command { +func NewStatusCommand() cmd.Command { return modelcmd.Wrap(&statusCommand{}) } @@ -35,7 +35,7 @@ func (c *statusCommand) Info() *cmd.Info { return &cmd.Info{ - Name: "status", + Name: "show-action-status", Args: "[|]", Purpose: "show results of all actions filtered by optional ID prefix", Doc: statusDoc, diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/status_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/status_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/action/status_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/action/status_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -25,11 +25,7 @@ func (s *StatusSuite) SetUpTest(c *gc.C) { s.BaseActionSuite.SetUpTest(c) - s.subcommand, _ = action.NewStatusCommand(s.store) -} - -func (s *StatusSuite) TestHelp(c *gc.C) { - s.checkHelp(c, s.subcommand) + s.subcommand, _ = action.NewStatusCommandForTest(s.store) } func (s *StatusSuite) TestRun(c *gc.C) { @@ -83,8 +79,8 @@ restore := s.patchAPIClient(fakeClient) defer restore() - s.subcommand, _ = action.NewStatusCommand(s.store) - args := append([]string{modelFlag, "dummymodel"}, tc.args...) + s.subcommand, _ = action.NewStatusCommandForTest(s.store) + args := append([]string{modelFlag, "admin"}, tc.args...) ctx, err := testing.RunCommand(c, s.subcommand, args...) if tc.expectError == "" { c.Assert(err, jc.ErrorIsNil) diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/backups.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/backups.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/backups.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/backups.go 2016-04-28 06:03:34.000000000 +0000 @@ -19,34 +19,6 @@ statebackups "github.com/juju/juju/state/backups" ) -var backupsDoc = ` -"juju backups" is used to manage backups of the state of a juju controller. -Backups are only supported on juju controllers, not hosted models. For -more information on juju controllers, see: - - juju help juju-controllers -` - -const backupsPurpose = "create, manage, and restore backups of juju's state" - -// NewSuperCommand returns a new backups super-command. -func NewSuperCommand() cmd.Command { - backupsCmd := cmd.NewSuperCommand(cmd.SuperCommandParams{ - Name: "backups", - Doc: backupsDoc, - UsagePrefix: "juju", - Purpose: backupsPurpose, - }) - backupsCmd.Register(newCreateCommand()) - backupsCmd.Register(newInfoCommand()) - backupsCmd.Register(newListCommand()) - backupsCmd.Register(newDownloadCommand()) - backupsCmd.Register(newUploadCommand()) - backupsCmd.Register(newRemoveCommand()) - backupsCmd.Register(newRestoreCommand()) - return backupsCmd -} - // APIClient represents the backups API client functionality used by // the backups command. type APIClient interface { @@ -114,12 +86,13 @@ fmt.Fprintf(ctx.Stdout, "juju version: %v\n", result.Version) } -type readSeekCloser interface { +// ArchiveReader can read a backup archive. +type ArchiveReader interface { io.ReadSeeker io.Closer } -func getArchive(filename string) (rc readSeekCloser, metaResult *params.BackupsMetadataResult, err error) { +func getArchive(filename string) (rc ArchiveReader, metaResult *params.BackupsMetadataResult, err error) { defer func() { if err != nil && rc != nil { rc.Close() diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/backups_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/backups_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/backups_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/backups_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -// Copyright 2014 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package backups_test - -import ( - jc "github.com/juju/testing/checkers" - gc "gopkg.in/check.v1" - - "github.com/juju/juju/testing" -) - -var expectedSubCommmandNames = []string{ - "create", - "download", - "help", - "info", - "list", - "remove", - "restore", - "upload", -} - -type backupsSuite struct { - BaseBackupsSuite -} - -var _ = gc.Suite(&backupsSuite{}) - -func (s *backupsSuite) TestHelp(c *gc.C) { - // Check the help output - ctx, err := testing.RunCommand(c, s.command, "--help") - c.Assert(err, jc.ErrorIsNil) - namesFound := testing.ExtractCommandsFromHelpOutput(ctx) - c.Assert(namesFound, gc.DeepEquals, expectedSubCommmandNames) -} diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/create.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/create.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/create.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/create.go 2016-04-28 06:03:34.000000000 +0000 @@ -24,7 +24,7 @@ ) const createDoc = ` -"create" requests that juju create a backup of its state and print the +create-backup requests that juju create a backup of its state and print the backup's unique ID. You may provide a note to associate with the backup. The backup archive and associated metadata are stored remotely by juju. @@ -37,13 +37,17 @@ destroyed. Furthermore, the remotely backup is not guaranteed to be available. -Therefore, you should use the --download or --filename options, or use -"juju backups download", to get a local copy of the backup archive. +Therefore, you should use the --download or --filename options, or use: + + juju download-backups + +to get a local copy of the backup archive. This local copy can then be used to restore an model even if that model was already destroyed or is otherwise unavailable. ` -func newCreateCommand() cmd.Command { +// NewCreateCommand returns a command used to create backups. +func NewCreateCommand() cmd.Command { return modelcmd.Wrap(&createCommand{}) } @@ -61,7 +65,7 @@ // Info implements Command.Info. func (c *createCommand) Info() *cmd.Info { return &cmd.Info{ - Name: "create", + Name: "create-backup", Args: "[]", Purpose: "create a backup", Doc: createDoc, diff -Nru charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/create_test.go charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/create_test.go --- charm-2.1.1/src/github.com/juju/juju/cmd/juju/backups/create_test.go 2016-03-17 19:44:58.000000000 +0000 +++ charm-2.2.0/src/github.com/juju/juju/cmd/juju/backups/create_test.go 2016-04-28 06:03:34.000000000 +0000 @@ -27,7 +27,7 @@ func (s *createSuite) SetUpTest(c *gc.C) { s.BaseBackupsSuite.SetUpTest(c) - s.wrappedCommand, s.command = backups.NewCreateCommand() + s.wrappedCommand, s.command = backups.NewCreateCommandForTest() s.defaultFilename = "juju-backup--