[automation/go] - Expose structured logging (#6436)

This commit is contained in:
Komal 2021-03-10 20:49:48 -08:00 committed by GitHub
parent 5a8e470176
commit 10d99b8afb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 494 additions and 53 deletions

View file

@ -19,6 +19,30 @@
Please note that `pulumi watch` will not be supported on darwin/arm64 builds.
[#6492](https://github.com/pulumi/pulumi/pull/6492)
- [automation/go] - BREAKING - Expose structured logging for Stack.Up/Preview/Refresh/Destroy.
[#6436](https://github.com/pulumi/pulumi/pull/6436)
This change is marked breaking because it changes the shape of the `PreviewResult` struct.
**Before**
```go
type PreviewResult struct {
Steps []PreviewStep `json:"steps"`
ChangeSummary map[string]int `json:"changeSummary"`
}
```
**After**
```go
type PreviewResult struct {
StdOut string
StdErr string
ChangeSummary map[apitype.OpType]int
}
```
### Bug Fixes
- [sdk/python] Fix mocks issue when passing a resource more than once.

View file

@ -87,9 +87,9 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
return apiEvent, eventTypePayloadMismatch
}
// Convert the resource changes.
changes := make(map[string]int)
changes := make(map[apitype.OpType]int)
for op, count := range p.ResourceChanges {
changes[string(op)] = count
changes[apitype.OpType(op)] = count
}
apiEvent.SummaryEvent = &apitype.SummaryEvent{
MaybeCorrupt: p.MaybeCorrupt,
@ -174,7 +174,7 @@ func convertStepEventMetadata(md engine.StepEventMetadata) apitype.StepEventMeta
}
return apitype.StepEventMetadata{
Op: string(md.Op),
Op: apitype.OpType(md.Op),
URN: string(md.URN),
Type: string(md.Type),

View file

@ -539,6 +539,7 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
@ -1067,6 +1068,7 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOA
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View file

@ -73,6 +73,7 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
@ -185,6 +186,7 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
@ -337,6 +339,7 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -406,6 +409,7 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOA
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View file

@ -22,6 +22,7 @@ require (
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/mitchellh/go-ps v1.0.0
github.com/nxadm/tail v1.4.8
github.com/opentracing/basictracer-go v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.1.0
github.com/pkg/errors v0.9.1

View file

@ -73,6 +73,8 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
@ -185,6 +187,8 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
@ -337,6 +341,7 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -406,6 +411,8 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOA
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View file

@ -72,9 +72,8 @@ type SummaryEvent struct {
MaybeCorrupt bool `json:"maybeCorrupt"`
// Duration is the number of seconds the update was executing.
DurationSeconds int `json:"durationSeconds"`
// ResourceChanges contains the count for resource change by type. The keys are deploy.StepOp,
// which is not exported in this package.
ResourceChanges map[string]int `json:"resourceChanges"`
// ResourceChanges contains the count for resource change by type.
ResourceChanges map[OpType]int `json:"resourceChanges"`
// PolicyPacks run during update. Maps PolicyPackName -> version.
// Note: When this field was initially added, we forgot to add the JSON tag
// and are now locked into to using PascalCase for this field to maintain backwards
@ -112,8 +111,8 @@ type PropertyDiff struct {
// StepEventMetadata describes a "step" within the Pulumi engine, which is any concrete action
// to migrate a set of cloud resources from one state to another.
type StepEventMetadata struct {
// Op is the operation being performed, a deploy.StepOp.
Op string `json:"op"`
// Op is the operation being performed.
Op OpType `json:"op"`
URN string `json:"urn"`
Type string `json:"type"`

View file

@ -77,6 +77,22 @@ const (
OpCreateReplacement OpType = "create-replacement"
// OpDeleteReplaced indicates an existing resource was deleted after replacement.
OpDeleteReplaced OpType = "delete-replaced"
// OpRead indicates reading an existing resource.
OpRead OpType = "read"
// OpReadReplacement indicates reading an existing resource for a replacement.
OpReadReplacement OpType = "read-replacement"
// OpRefresh indicates refreshing an existing resource.
OpRefresh OpType = "refresh" // refreshing an existing resource.
// OpReadDiscard indicates removing a resource that was read.
OpReadDiscard OpType = "discard"
// OpDiscardReplaced indicates discarding a read resource that was replaced.
OpDiscardReplaced OpType = "discard-replaced"
// OpRemovePendingReplace indicates removing a pending replace resource.
OpRemovePendingReplace OpType = "remove-pending-replace"
// OpImport indicates importing an existing resource.
OpImport OpType = "import"
// OpImportReplacement indicates replacement of an existing resource with an imported resource.
OpImportReplacement OpType = "import-replacement"
)
// UpdateInfo describes a previous update.

View file

@ -0,0 +1,8 @@
package events
import "github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
type EngineEvent struct {
apitype.EngineEvent
Error error
}

View file

@ -26,11 +26,14 @@ import (
"testing"
"time"
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v2/go/common/workspace"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi/config"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/events"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optdestroy"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optpreview"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optrefresh"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optup"
"github.com/stretchr/testify/assert"
@ -189,13 +192,17 @@ func TestNewStackLocalSource(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
@ -291,27 +298,30 @@ func TestUpsertStackLocalSource(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
ref, err := s.Refresh(ctx)
if err != nil {
t.Errorf("refresh failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, "refresh", ref.Summary.Kind)
assert.Equal(t, "succeeded", ref.Summary.Result)
// -- pulumi destroy --
dRes, err := s.Destroy(ctx)
if err != nil {
t.Errorf("destroy failed, err: %v", err)
@ -384,13 +394,17 @@ func TestNewStackRemoteSource(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
@ -472,13 +486,17 @@ func TestUpsertStackRemoteSource(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
@ -572,13 +590,17 @@ func TestNewStackRemoteSourceWithSetup(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
@ -672,13 +694,17 @@ func TestUpsertStackRemoteSourceWithSetup(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
@ -762,13 +788,17 @@ func TestNewStackInlineSource(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
@ -851,13 +881,17 @@ func TestUpsertStackInlineSource(t *testing.T) {
// -- pulumi preview --
prev, err := s.Preview(ctx)
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary["same"])
assert.Equal(t, 1, len(prev.Steps))
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
// -- pulumi refresh --
@ -1132,6 +1166,123 @@ func TestNestedConfig(t *testing.T) {
assert.JSONEq(t, "[\"one\",\"two\",\"three\"]", list.Value)
}
func TestStructuredOutput(t *testing.T) {
ctx := context.Background()
sName := fmt.Sprintf("int_test%d", rangeIn(10000000, 99999999))
stackName := FullyQualifiedStackName(pulumiOrg, pName, sName)
cfg := ConfigMap{
"bar": ConfigValue{
Value: "abc",
},
"buzz": ConfigValue{
Value: "secret",
Secret: true,
},
}
// initialize
pDir := filepath.Join(".", "test", "testproj")
s, err := UpsertStackLocalSource(ctx, stackName, pDir)
if err != nil {
t.Errorf("failed to initialize stack, err: %v", err)
t.FailNow()
}
defer func() {
// -- pulumi stack rm --
err = s.Workspace().RemoveStack(ctx, s.Name())
assert.Nil(t, err, "failed to remove stack. Resources have leaked.")
}()
err = s.SetAllConfig(ctx, cfg)
if err != nil {
t.Errorf("failed to set config, err: %v", err)
t.FailNow()
}
// Set environment variables scoped to the workspace.
envvars := map[string]string{
"foo": "bar",
"barfoo": "foobar",
}
err = s.Workspace().SetEnvVars(envvars)
assert.Nil(t, err, "failed to set environment values")
envvars = s.Workspace().GetEnvVars()
assert.NotNil(t, envvars, "failed to get environment values after setting many")
s.Workspace().SetEnvVar("bar", "buzz")
envvars = s.Workspace().GetEnvVars()
assert.NotNil(t, envvars, "failed to get environment value after setting")
s.Workspace().UnsetEnvVar("bar")
envvars = s.Workspace().GetEnvVars()
assert.NotNil(t, envvars, "failed to get environment values after unsetting.")
// -- pulumi up --
var upEvents []events.EngineEvent
upCh := make(chan events.EngineEvent)
go collectEvents(upCh, &upEvents)
res, err := s.Up(ctx, optup.EventStreams(upCh))
if err != nil {
t.Errorf("up failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs")
assert.Equal(t, "foo", res.Outputs["exp_static"].Value)
assert.False(t, res.Outputs["exp_static"].Secret)
assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value)
assert.False(t, res.Outputs["exp_cfg"].Secret)
assert.Equal(t, "secret", res.Outputs["exp_secret"].Value)
assert.True(t, res.Outputs["exp_secret"].Secret)
assert.Equal(t, "update", res.Summary.Kind)
assert.Equal(t, "succeeded", res.Summary.Result)
assert.True(t, containsSummary(upEvents))
// -- pulumi preview --
var previewEvents []events.EngineEvent
prevCh := make(chan events.EngineEvent)
go collectEvents(prevCh, &previewEvents)
prev, err := s.Preview(ctx, optpreview.EventStreams(prevCh))
if err != nil {
t.Errorf("preview failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
steps := countSteps(previewEvents)
assert.Equal(t, 1, steps)
assert.True(t, containsSummary(previewEvents))
// -- pulumi refresh --
var refreshEvents []events.EngineEvent
refCh := make(chan events.EngineEvent)
go collectEvents(refCh, &refreshEvents)
ref, err := s.Refresh(ctx, optrefresh.EventStreams(refCh))
if err != nil {
t.Errorf("refresh failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, "refresh", ref.Summary.Kind)
assert.Equal(t, "succeeded", ref.Summary.Result)
assert.True(t, containsSummary(refreshEvents))
// -- pulumi destroy --
var destroyEvents []events.EngineEvent
desCh := make(chan events.EngineEvent)
go collectEvents(desCh, &destroyEvents)
dRes, err := s.Destroy(ctx, optdestroy.EventStreams(desCh))
if err != nil {
t.Errorf("destroy failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, "destroy", dRes.Summary.Kind)
assert.Equal(t, "succeeded", dRes.Summary.Result)
assert.True(t, containsSummary(destroyEvents))
}
func BenchmarkBulkSetConfigMixed(b *testing.B) {
ctx := context.Background()
stackName := FullyQualifiedStackName(pulumiOrg, "set_config_mixed", "dev")
@ -1340,3 +1491,33 @@ func getTestOrg() string {
}
return testOrg
}
func countSteps(log []events.EngineEvent) int {
steps := 0
for _, e := range log {
if e.ResourcePreEvent != nil {
steps++
}
}
return steps
}
func containsSummary(log []events.EngineEvent) bool {
hasSummary := false
for _, e := range log {
if e.SummaryEvent != nil {
hasSummary = true
}
}
return hasSummary
}
func collectEvents(eventChannel <-chan events.EngineEvent, events *[]events.EngineEvent) {
for {
event, ok := <-eventChannel
if !ok {
return
}
*events = append(*events, event)
}
}

View file

@ -17,8 +17,10 @@
package optdestroy
import (
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"io"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/events"
)
// Parallel is the number of resource operations to run in parallel at once during the destroy
@ -57,6 +59,13 @@ func ProgressStreams(writers ...io.Writer) Option {
})
}
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
func EventStreams(channels ...chan<- events.EngineEvent) Option {
return optionFunc(func(opts *Options) {
opts.EventStreams = channels
})
}
func DebugLogging(debugOpts debug.LoggingOptions) Option {
return optionFunc(func(opts *Options) {
opts.DebugLogOpts = debugOpts
@ -83,6 +92,8 @@ type Options struct {
TargetDependents bool
// ProgressStreams allows specifying one or more io.Writers to redirect incremental destroy output
ProgressStreams []io.Writer
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
EventStreams []chan<- events.EngineEvent
// DebugLogOpts specifies additional settings for debug logging
DebugLogOpts debug.LoggingOptions
}

View file

@ -17,7 +17,10 @@
package optpreview
import (
"io"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/events"
)
// Parallel is the number of resource operations to run in parallel at once during the update
@ -76,6 +79,20 @@ func DebugLogging(debugOpts debug.LoggingOptions) Option {
})
}
// ProgressStreams allows specifying one or more io.Writers to redirect incremental preview output
func ProgressStreams(writers ...io.Writer) Option {
return optionFunc(func(opts *Options) {
opts.ProgressStreams = writers
})
}
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
func EventStreams(channels ...chan<- events.EngineEvent) Option {
return optionFunc(func(opts *Options) {
opts.EventStreams = channels
})
}
// Option is a parameter to be applied to a Stack.Preview() operation
type Option interface {
ApplyOption(*Options)
@ -102,6 +119,10 @@ type Options struct {
TargetDependents bool
// DebugLogOpts specifies additional settings for debug logging
DebugLogOpts debug.LoggingOptions
// ProgressStreams allows specifying one or more io.Writers to redirect incremental preview output
ProgressStreams []io.Writer
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
EventStreams []chan<- events.EngineEvent
}
type optionFunc func(*Options)

View file

@ -17,8 +17,10 @@
package optrefresh
import (
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"io"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/events"
)
// Parallel is the number of resource operations to run in parallel at once during the refresh
@ -57,6 +59,13 @@ func ProgressStreams(writers ...io.Writer) Option {
})
}
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
func EventStreams(channels ...chan<- events.EngineEvent) Option {
return optionFunc(func(opts *Options) {
opts.EventStreams = channels
})
}
func DebugLogging(debugOpts debug.LoggingOptions) Option {
return optionFunc(func(opts *Options) {
opts.DebugLogOpts = debugOpts
@ -83,6 +92,8 @@ type Options struct {
Target []string
// ProgressStreams allows specifying one or more io.Writers to redirect incremental refresh output
ProgressStreams []io.Writer
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
EventStreams []chan<- events.EngineEvent
// DebugLogOpts specifies additional settings for debug logging
DebugLogOpts debug.LoggingOptions
}

View file

@ -17,8 +17,10 @@
package optup
import (
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"io"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/events"
)
// Parallel is the number of resource operations to run in parallel at once during the update
@ -84,6 +86,13 @@ func DebugLogging(debugOpts debug.LoggingOptions) Option {
})
}
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
func EventStreams(channels ...chan<- events.EngineEvent) Option {
return optionFunc(func(opts *Options) {
opts.EventStreams = channels
})
}
// Option is a parameter to be applied to a Stack.Up() operation
type Option interface {
ApplyOption(*Options)
@ -108,10 +117,12 @@ type Options struct {
Target []string
// Allows updating of dependent targets discovered but not specified in the Target list
TargetDependents bool
// ProgressStreams allows specifying one or more io.Writers to redirect incremental update output
ProgressStreams []io.Writer
// DebugLogOpts specifies additional settings for debug logging
DebugLogOpts debug.LoggingOptions
// ProgressStreams allows specifying one or more io.Writers to redirect incremental update output
ProgressStreams []io.Writer
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
EventStreams []chan<- events.EngineEvent
}
type optionFunc func(*Options)

View file

@ -92,14 +92,16 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/nxadm/tail"
"github.com/pkg/errors"
"google.golang.org/grpc"
@ -109,6 +111,8 @@ import (
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/rpcutil"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/debug"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/events"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optdestroy"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optpreview"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optrefresh"
@ -242,7 +246,7 @@ func (s *Stack) Preview(ctx context.Context, opts ...optpreview.Option) (Preview
sharedArgs = append(sharedArgs, fmt.Sprintf("--parallel=%d", preOpts.Parallel))
}
kind, args := constant.ExecKindAutoLocal, []string{"preview", "--json"}
kind, args := constant.ExecKindAutoLocal, []string{"preview"}
if program := s.Workspace().Program(); program != nil {
server, err := startLanguageRuntimeServer(program)
if err != nil {
@ -255,15 +259,46 @@ func (s *Stack) Preview(ctx context.Context, opts ...optpreview.Option) (Preview
args = append(args, fmt.Sprintf("--exec-kind=%s", kind))
args = append(args, sharedArgs...)
stdout, stderr, code, err := s.runPulumiCmdSync(ctx, nil /* additionalOutput */, args...)
var summaryEvents []apitype.SummaryEvent
eventChannel := make(chan events.EngineEvent)
go func(ch chan events.EngineEvent, events *[]apitype.SummaryEvent) {
for {
event, ok := <-eventChannel
if !ok {
return
}
if event.SummaryEvent != nil {
summaryEvents = append(summaryEvents, *event.SummaryEvent)
}
}
}(eventChannel, &summaryEvents)
eventChannels := []chan<- events.EngineEvent{eventChannel}
eventChannels = append(eventChannels, preOpts.EventStreams...)
t, err := tailLogs("preview", eventChannels)
if err != nil {
return res, errors.Wrap(err, "failed to tail logs")
}
defer cleanup(t, eventChannels)
args = append(args, "--event-log", t.Filename)
stdout, stderr, code, err := s.runPulumiCmdSync(ctx, preOpts.ProgressStreams /* additionalOutput */, args...)
if err != nil {
return res, newAutoError(errors.Wrap(err, "failed to run preview"), stdout, stderr, code)
}
err = json.Unmarshal([]byte(stdout), &res)
if err != nil {
return res, newAutoError(errors.Wrap(err, "unable to unmarshal preview result"), stdout, stderr, code)
if len(summaryEvents) == 0 {
return res, newAutoError(errors.New("failed to get preview summary"), stdout, stderr, code)
}
if len(summaryEvents) > 1 {
return res, newAutoError(errors.New("got multiple preview summaries"), stdout, stderr, code)
}
res.StdOut = stdout
res.StdErr = stderr
res.ChangeSummary = summaryEvents[0].ResourceChanges
return res, nil
}
@ -317,8 +352,18 @@ func (s *Stack) Up(ctx context.Context, opts ...optup.Option) (UpResult, error)
kind, args = constant.ExecKindAutoInline, append(args, "--client="+server.address)
}
args = append(args, fmt.Sprintf("--exec-kind=%s", kind))
if len(upOpts.EventStreams) > 0 {
eventChannels := upOpts.EventStreams
t, err := tailLogs("up", eventChannels)
if err != nil {
return res, errors.Wrap(err, "failed to tail logs")
}
defer cleanup(t, eventChannels)
args = append(args, "--event-log", t.Filename)
}
args = append(args, sharedArgs...)
stdout, stderr, code, err := s.runPulumiCmdSync(ctx, upOpts.ProgressStreams, args...)
if err != nil {
@ -385,6 +430,16 @@ func (s *Stack) Refresh(ctx context.Context, opts ...optrefresh.Option) (Refresh
}
args = append(args, fmt.Sprintf("--exec-kind=%s", execKind))
if len(refreshOpts.EventStreams) > 0 {
eventChannels := refreshOpts.EventStreams
t, err := tailLogs("refresh", eventChannels)
if err != nil {
return res, errors.Wrap(err, "failed to tail logs")
}
defer cleanup(t, eventChannels)
args = append(args, "--event-log", t.Filename)
}
stdout, stderr, code, err := s.runPulumiCmdSync(ctx, refreshOpts.ProgressStreams, args...)
if err != nil {
return res, newAutoError(errors.Wrap(err, "failed to refresh stack"), stdout, stderr, code)
@ -445,6 +500,16 @@ func (s *Stack) Destroy(ctx context.Context, opts ...optdestroy.Option) (Destroy
}
args = append(args, fmt.Sprintf("--exec-kind=%s", execKind))
if len(destroyOpts.EventStreams) > 0 {
eventChannels := destroyOpts.EventStreams
t, err := tailLogs("destroy", eventChannels)
if err != nil {
return res, errors.Wrap(err, "failed to tail logs")
}
defer cleanup(t, eventChannels)
args = append(args, "--event-log", t.Filename)
}
stdout, stderr, code, err := s.runPulumiCmdSync(ctx, destroyOpts.ProgressStreams, args...)
if err != nil {
return res, newAutoError(errors.Wrap(err, "failed to destroy stack"), stdout, stderr, code)
@ -653,7 +718,6 @@ type OutputValue struct {
// UpResult contains information about a Stack.Up operation,
// including Outputs, and a summary of the deployed changes.
// TODO: remote StdOut in favor of structured info https://github.com/pulumi/pulumi/issues/5218
type UpResult struct {
StdOut string
StdErr string
@ -722,12 +786,12 @@ type PropertyDiff struct {
// PreviewResult is the output of Stack.Preview() describing the expected set of changes from the next Stack.Up()
type PreviewResult struct {
Steps []PreviewStep `json:"steps"`
ChangeSummary map[string]int `json:"changeSummary"`
StdOut string
StdErr string
ChangeSummary map[apitype.OpType]int
}
// RefreshResult is the output of a successful Stack.Refresh operation
// TODO: replace StdOut with structured output https://github.com/pulumi/pulumi/issues/5220
type RefreshResult struct {
StdOut string
StdErr string
@ -735,7 +799,6 @@ type RefreshResult struct {
}
// DestroyResult is the output of a successful Stack.Destroy operation
// TODO: replace StdOut with structured output https://github.com/pulumi/pulumi/issues/5219
type DestroyResult struct {
StdOut string
StdErr string
@ -751,6 +814,8 @@ func (s *Stack) runPulumiCmdSync(
args ...string,
) (string, string, int, error) {
var env []string
debugEnv := fmt.Sprintf("%s=%s", "PULUMI_DEBUG_COMMANDS", "true")
env = append(env, debugEnv)
if s.Workspace().PulumiHome() != "" {
homeEnv := fmt.Sprintf("%s=%s", pulumiHomeEnv, s.Workspace().PulumiHome())
env = append(env, homeEnv)
@ -924,3 +989,27 @@ func (s *languageRuntimeServer) GetPluginInfo(ctx context.Context, req *pbempty.
Version: "1.0.0",
}, nil
}
func tailLogs(command string, receivers []chan<- events.EngineEvent) (*tail.Tail, error) {
logDir, err := ioutil.TempDir("", fmt.Sprintf("automation-logs-%s-", command))
if err != nil {
return nil, errors.Wrap(err, "failed to create logdir")
}
logFile := filepath.Join(logDir, "eventlog.txt")
t, err := watchFile(logFile, receivers)
if err != nil {
return nil, errors.Wrap(err, "failed to watch file")
}
return t, nil
}
func cleanup(t *tail.Tail, channels []chan<- events.EngineEvent) {
logDir := filepath.Dir(t.Filename)
t.Cleanup()
os.RemoveAll(logDir)
for _, ch := range channels {
close(ch)
}
}

54
sdk/go/x/auto/watcher.go Normal file
View file

@ -0,0 +1,54 @@
// Copyright 2016-2021, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package auto
import (
"encoding/json"
"github.com/nxadm/tail"
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/events"
)
func watchFile(path string, receivers []chan<- events.EngineEvent) (*tail.Tail, error) {
t, err := tail.TailFile(path, tail.Config{
Follow: true,
Logger: tail.DiscardingLogger,
})
if err != nil {
return nil, err
}
go func(tailedLog *tail.Tail) {
for line := range tailedLog.Lines {
if line.Err != nil {
for _, r := range receivers {
r <- events.EngineEvent{Error: line.Err}
}
continue
}
var e apitype.EngineEvent
err = json.Unmarshal([]byte(line.Text), &e)
if err != nil {
for _, r := range receivers {
r <- events.EngineEvent{Error: err}
}
continue
}
for _, r := range receivers {
r <- events.EngineEvent{EngineEvent: e}
}
}
}(t)
return t, nil
}

View file

@ -540,6 +540,7 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
@ -1070,6 +1071,7 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOA
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=