2017-02-25 16:25:33 +01:00
|
|
|
// Copyright 2016 Pulumi, Inc. All rights reserved.
|
2016-11-15 20:30:34 +01:00
|
|
|
|
|
|
|
package diag
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2016-11-17 02:52:14 +01:00
|
|
|
"io"
|
2016-11-15 20:30:34 +01:00
|
|
|
"os"
|
2016-11-16 03:00:43 +01:00
|
|
|
"path/filepath"
|
2016-11-15 20:30:34 +01:00
|
|
|
"strconv"
|
2016-11-16 01:30:10 +01:00
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2017-02-10 02:26:49 +01:00
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
"github.com/pulumi/coconut/pkg/diag/colors"
|
|
|
|
"github.com/pulumi/coconut/pkg/util/contract"
|
2016-11-15 20:30:34 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// Sink facilitates pluggable diagnostics messages.
|
|
|
|
type Sink interface {
|
|
|
|
// Count fetches the total number of diagnostics issued (errors plus warnings).
|
|
|
|
Count() int
|
2017-02-23 03:53:36 +01:00
|
|
|
// Infos fetches the number of informational messages issued.
|
|
|
|
Infos() int
|
2016-11-15 20:30:34 +01:00
|
|
|
// Errors fetches the number of errors issued.
|
|
|
|
Errors() int
|
|
|
|
// Warnings fetches the number of warnings issued.
|
|
|
|
Warnings() int
|
2016-11-22 18:40:09 +01:00
|
|
|
// Success returns true if this sink is currently error-free.
|
|
|
|
Success() bool
|
2016-11-15 20:30:34 +01:00
|
|
|
|
2017-02-23 03:53:36 +01:00
|
|
|
// Infof issues an informational message.
|
|
|
|
Infof(diag *Diag, args ...interface{})
|
|
|
|
// Errorf issues a new error diagnostic.
|
2016-11-15 20:30:34 +01:00
|
|
|
Errorf(diag *Diag, args ...interface{})
|
2017-02-23 03:53:36 +01:00
|
|
|
// Warningf issues a new warning diagnostic.
|
2016-11-15 20:30:34 +01:00
|
|
|
Warningf(diag *Diag, args ...interface{})
|
2016-11-16 04:16:02 +01:00
|
|
|
|
2017-03-10 22:27:19 +01:00
|
|
|
// Stringify stringifies a diagnostic in the usual way (e.g., "error: MU123: Coconut.yaml:7:39: error goes here\n").
|
2017-02-10 02:26:49 +01:00
|
|
|
Stringify(diag *Diag, cat Category, args ...interface{}) string
|
2017-02-12 18:38:19 +01:00
|
|
|
// StringifyLocation stringifies a source document location.
|
|
|
|
StringifyLocation(doc *Document, loc *Location) string
|
2017-02-10 02:26:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Category dictates the kind of diagnostic.
|
|
|
|
type Category string
|
|
|
|
|
|
|
|
const (
|
|
|
|
Error Category = "error"
|
|
|
|
Warning = "warning"
|
2017-02-23 03:53:36 +01:00
|
|
|
Info = "info"
|
2017-02-10 02:26:49 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// FormatOptions controls the output style and content.
|
|
|
|
type FormatOptions struct {
|
|
|
|
Pwd string // the working directory.
|
|
|
|
Colors bool // if true, output will be colorized.
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
2017-01-28 00:42:39 +01:00
|
|
|
// DefaultSink returns a default sink that simply logs output to stderr/stdout.
|
2017-02-10 02:26:49 +01:00
|
|
|
func DefaultSink(opts FormatOptions) Sink {
|
2017-02-23 03:53:36 +01:00
|
|
|
return newDefaultSink(opts, map[Category]io.Writer{
|
|
|
|
Info: os.Stdout,
|
|
|
|
Error: os.Stderr,
|
|
|
|
Warning: os.Stdout,
|
|
|
|
})
|
2016-11-17 02:52:14 +01:00
|
|
|
}
|
|
|
|
|
2017-02-23 03:53:36 +01:00
|
|
|
func newDefaultSink(opts FormatOptions, writers map[Category]io.Writer) *defaultSink {
|
|
|
|
contract.Assert(writers[Info] != nil)
|
|
|
|
contract.Assert(writers[Error] != nil)
|
|
|
|
contract.Assert(writers[Warning] != nil)
|
|
|
|
return &defaultSink{
|
|
|
|
opts: opts,
|
|
|
|
counts: make(map[Category]int),
|
|
|
|
writers: writers,
|
|
|
|
}
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
2017-02-28 19:36:21 +01:00
|
|
|
const DefaultSinkIDPrefix = "COCO"
|
2016-11-16 17:19:26 +01:00
|
|
|
|
2016-11-16 01:30:10 +01:00
|
|
|
// defaultSink is the default sink which logs output to stderr/stdout.
|
|
|
|
type defaultSink struct {
|
2017-02-23 03:53:36 +01:00
|
|
|
opts FormatOptions // a set of options that control output style and content.
|
|
|
|
counts map[Category]int // the number of messages that have been issued per category.
|
|
|
|
writers map[Category]io.Writer // the writers to use for each kind of diagnostic category.
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
2017-02-23 03:53:36 +01:00
|
|
|
func (d *defaultSink) Count() int { return d.Infos() + d.Errors() + d.Warnings() }
|
|
|
|
func (d *defaultSink) Infos() int { return d.counts[Info] }
|
|
|
|
func (d *defaultSink) Errors() int { return d.counts[Error] }
|
|
|
|
func (d *defaultSink) Warnings() int { return d.counts[Warning] }
|
|
|
|
func (d *defaultSink) Success() bool { return d.Errors() == 0 }
|
2016-11-15 20:30:34 +01:00
|
|
|
|
2017-02-23 03:53:36 +01:00
|
|
|
func (d *defaultSink) Infof(diag *Diag, args ...interface{}) {
|
|
|
|
msg := d.Stringify(diag, Info, args...)
|
|
|
|
if glog.V(3) {
|
|
|
|
glog.V(3).Infof("defaultSink::Info(%v)", msg[:len(msg)-1])
|
|
|
|
}
|
|
|
|
fmt.Fprintf(d.writers[Info], msg)
|
|
|
|
d.counts[Info]++
|
2016-11-22 18:40:09 +01:00
|
|
|
}
|
|
|
|
|
2016-11-16 01:30:10 +01:00
|
|
|
func (d *defaultSink) Errorf(diag *Diag, args ...interface{}) {
|
2017-02-10 02:26:49 +01:00
|
|
|
msg := d.Stringify(diag, Error, args...)
|
2016-11-16 01:30:10 +01:00
|
|
|
if glog.V(3) {
|
2016-11-22 18:20:23 +01:00
|
|
|
glog.V(3).Infof("defaultSink::Error(%v)", msg[:len(msg)-1])
|
2016-11-16 01:30:10 +01:00
|
|
|
}
|
2017-02-23 03:53:36 +01:00
|
|
|
fmt.Fprintf(d.writers[Error], msg)
|
|
|
|
d.counts[Error]++
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
2016-11-16 01:30:10 +01:00
|
|
|
func (d *defaultSink) Warningf(diag *Diag, args ...interface{}) {
|
2017-02-10 02:26:49 +01:00
|
|
|
msg := d.Stringify(diag, Warning, args...)
|
2016-11-16 01:30:10 +01:00
|
|
|
if glog.V(4) {
|
2016-11-22 18:20:23 +01:00
|
|
|
glog.V(4).Infof("defaultSink::Warning(%v)", msg[:len(msg)-1])
|
2016-11-16 01:30:10 +01:00
|
|
|
}
|
2017-02-23 03:53:36 +01:00
|
|
|
fmt.Fprintf(d.writers[Warning], msg)
|
|
|
|
d.counts[Warning]++
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
2017-02-10 02:26:49 +01:00
|
|
|
func (d *defaultSink) Stringify(diag *Diag, cat Category, args ...interface{}) string {
|
2016-11-15 20:30:34 +01:00
|
|
|
var buffer bytes.Buffer
|
|
|
|
|
2017-02-10 02:26:49 +01:00
|
|
|
// First print the location if there is one.
|
2017-02-12 18:38:19 +01:00
|
|
|
if diag.Doc != nil || diag.Loc != nil {
|
|
|
|
buffer.WriteString(d.StringifyLocation(diag.Doc, diag.Loc))
|
2016-11-15 20:30:34 +01:00
|
|
|
buffer.WriteString(": ")
|
2017-02-10 02:26:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now print the message category's prefix (error/warning).
|
|
|
|
if d.opts.Colors {
|
|
|
|
switch cat {
|
2017-02-23 03:53:36 +01:00
|
|
|
case Info:
|
|
|
|
buffer.WriteString(colors.SpecInfo)
|
2017-02-10 02:26:49 +01:00
|
|
|
case Error:
|
2017-02-22 03:49:51 +01:00
|
|
|
buffer.WriteString(colors.SpecError)
|
2017-02-10 02:26:49 +01:00
|
|
|
case Warning:
|
2017-02-22 03:49:51 +01:00
|
|
|
buffer.WriteString(colors.SpecWarning)
|
2017-02-10 02:26:49 +01:00
|
|
|
default:
|
|
|
|
contract.Failf("Unrecognized diagnostic category: %v", cat)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString(string(cat))
|
|
|
|
|
|
|
|
if diag.ID > 0 {
|
|
|
|
buffer.WriteString(" ")
|
|
|
|
buffer.WriteString(DefaultSinkIDPrefix)
|
|
|
|
buffer.WriteString(strconv.Itoa(int(diag.ID)))
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString(": ")
|
|
|
|
|
|
|
|
if d.opts.Colors {
|
Begin resource modeling and planning
This change introduces a new package, pkg/resource, that will form
the foundation for actually performing deployment plans and applications.
It contains the following key abstractions:
* resource.Provider is a wrapper around the CRUD operations exposed by
underlying resource plugins. It will eventually defer to resource.Plugin,
which itself defers -- over an RPC interface -- to the actual plugin, one
per package exposing resources. The provider will also understand how to
load, cache, and overall manage the lifetime of each plugin.
* resource.Resource is the actual resource object. This is created from
the overall evaluation object graph, but is simplified. It contains only
serializable properties, for example. Inter-resource references are
translated into serializable monikers as part of creating the resource.
* resource.Moniker is a serializable string that uniquely identifies
a resource in the Mu system. This is in contrast to resource IDs, which
are generated by resource providers and generally opaque to the Mu
system. See marapongo/mu#69 for more information about monikers and some
of their challenges (namely, designing a stable algorithm).
* resource.Snapshot is a "snapshot" taken from a graph of resources. This
is a transitive closure of state representing one possible configuration
of a given environment. This is what plans are created from. Eventually,
two snapshots will be diffable, in order to perform incremental updates.
One way of thinking about this is that a snapshot of the old world's state
is advanced, one step at a time, until it reaches a desired snapshot of
the new world's state.
* resource.Plan is a plan for carrying out desired CRUD operations on a target
environment. Each plan consists of zero-to-many Steps, each of which has
a CRUD operation type, a resource target, and a next step. This is an
enumerator because it is possible the plan will evolve -- and introduce new
steps -- as it is carried out (hence, the Next() method). At the moment, this
is linearized; eventually, we want to make this more "graph-like" so that we
can exploit available parallelism within the dependencies.
There are tons of TODOs remaining. However, the `mu plan` command is functioning
with these new changes -- including colorization FTW -- so I'm landing it now.
This is part of marapongo/mu#38 and marapongo/mu#41.
2017-02-17 21:31:48 +01:00
|
|
|
buffer.WriteString(colors.Reset)
|
2017-02-10 02:26:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, actually print the message itself.
|
|
|
|
if d.opts.Colors {
|
2017-02-23 03:53:36 +01:00
|
|
|
buffer.WriteString(colors.SpecNote)
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString(fmt.Sprintf(diag.Message, args...))
|
2017-02-10 02:26:49 +01:00
|
|
|
|
|
|
|
if d.opts.Colors {
|
Begin resource modeling and planning
This change introduces a new package, pkg/resource, that will form
the foundation for actually performing deployment plans and applications.
It contains the following key abstractions:
* resource.Provider is a wrapper around the CRUD operations exposed by
underlying resource plugins. It will eventually defer to resource.Plugin,
which itself defers -- over an RPC interface -- to the actual plugin, one
per package exposing resources. The provider will also understand how to
load, cache, and overall manage the lifetime of each plugin.
* resource.Resource is the actual resource object. This is created from
the overall evaluation object graph, but is simplified. It contains only
serializable properties, for example. Inter-resource references are
translated into serializable monikers as part of creating the resource.
* resource.Moniker is a serializable string that uniquely identifies
a resource in the Mu system. This is in contrast to resource IDs, which
are generated by resource providers and generally opaque to the Mu
system. See marapongo/mu#69 for more information about monikers and some
of their challenges (namely, designing a stable algorithm).
* resource.Snapshot is a "snapshot" taken from a graph of resources. This
is a transitive closure of state representing one possible configuration
of a given environment. This is what plans are created from. Eventually,
two snapshots will be diffable, in order to perform incremental updates.
One way of thinking about this is that a snapshot of the old world's state
is advanced, one step at a time, until it reaches a desired snapshot of
the new world's state.
* resource.Plan is a plan for carrying out desired CRUD operations on a target
environment. Each plan consists of zero-to-many Steps, each of which has
a CRUD operation type, a resource target, and a next step. This is an
enumerator because it is possible the plan will evolve -- and introduce new
steps -- as it is carried out (hence, the Next() method). At the moment, this
is linearized; eventually, we want to make this more "graph-like" so that we
can exploit available parallelism within the dependencies.
There are tons of TODOs remaining. However, the `mu plan` command is functioning
with these new changes -- including colorization FTW -- so I'm landing it now.
This is part of marapongo/mu#38 and marapongo/mu#41.
2017-02-17 21:31:48 +01:00
|
|
|
buffer.WriteString(colors.Reset)
|
2017-02-10 02:26:49 +01:00
|
|
|
}
|
|
|
|
|
2016-11-15 20:30:34 +01:00
|
|
|
buffer.WriteRune('\n')
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
// TODO[pulumi/coconut#15]: support Clang-style expressive diagnostics. This would entail, for example, using the
|
2016-11-23 21:30:02 +01:00
|
|
|
// buffer within the target document, to demonstrate the offending line/column range of code.
|
2016-11-15 20:30:34 +01:00
|
|
|
|
2017-02-10 02:26:49 +01:00
|
|
|
s := buffer.String()
|
|
|
|
|
|
|
|
// If colorization was requested, compile and execute the directives now.
|
|
|
|
if d.opts.Colors {
|
2017-02-28 19:32:24 +01:00
|
|
|
s = colors.ColorizeText(s)
|
2017-02-10 02:26:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
2017-02-12 18:38:19 +01:00
|
|
|
|
|
|
|
func (d *defaultSink) StringifyLocation(doc *Document, loc *Location) string {
|
|
|
|
var buffer bytes.Buffer
|
|
|
|
|
|
|
|
if doc != nil {
|
|
|
|
if d.opts.Colors {
|
2017-02-22 03:49:51 +01:00
|
|
|
buffer.WriteString(colors.SpecLocation)
|
2017-02-12 18:38:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
file := doc.File
|
|
|
|
if d.opts.Pwd != "" {
|
|
|
|
// If a PWD is available, try to create a relative path.
|
|
|
|
rel, err := filepath.Rel(d.opts.Pwd, file)
|
|
|
|
if err == nil {
|
|
|
|
file = rel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buffer.WriteString(file)
|
|
|
|
}
|
|
|
|
|
|
|
|
if loc != nil && !loc.IsEmpty() {
|
|
|
|
buffer.WriteRune('(')
|
|
|
|
buffer.WriteString(strconv.Itoa(loc.Start.Line))
|
|
|
|
buffer.WriteRune(',')
|
|
|
|
buffer.WriteString(strconv.Itoa(loc.Start.Column))
|
|
|
|
buffer.WriteRune(')')
|
|
|
|
}
|
|
|
|
|
|
|
|
var s string
|
|
|
|
if doc != nil || loc != nil {
|
|
|
|
if d.opts.Colors {
|
Begin resource modeling and planning
This change introduces a new package, pkg/resource, that will form
the foundation for actually performing deployment plans and applications.
It contains the following key abstractions:
* resource.Provider is a wrapper around the CRUD operations exposed by
underlying resource plugins. It will eventually defer to resource.Plugin,
which itself defers -- over an RPC interface -- to the actual plugin, one
per package exposing resources. The provider will also understand how to
load, cache, and overall manage the lifetime of each plugin.
* resource.Resource is the actual resource object. This is created from
the overall evaluation object graph, but is simplified. It contains only
serializable properties, for example. Inter-resource references are
translated into serializable monikers as part of creating the resource.
* resource.Moniker is a serializable string that uniquely identifies
a resource in the Mu system. This is in contrast to resource IDs, which
are generated by resource providers and generally opaque to the Mu
system. See marapongo/mu#69 for more information about monikers and some
of their challenges (namely, designing a stable algorithm).
* resource.Snapshot is a "snapshot" taken from a graph of resources. This
is a transitive closure of state representing one possible configuration
of a given environment. This is what plans are created from. Eventually,
two snapshots will be diffable, in order to perform incremental updates.
One way of thinking about this is that a snapshot of the old world's state
is advanced, one step at a time, until it reaches a desired snapshot of
the new world's state.
* resource.Plan is a plan for carrying out desired CRUD operations on a target
environment. Each plan consists of zero-to-many Steps, each of which has
a CRUD operation type, a resource target, and a next step. This is an
enumerator because it is possible the plan will evolve -- and introduce new
steps -- as it is carried out (hence, the Next() method). At the moment, this
is linearized; eventually, we want to make this more "graph-like" so that we
can exploit available parallelism within the dependencies.
There are tons of TODOs remaining. However, the `mu plan` command is functioning
with these new changes -- including colorization FTW -- so I'm landing it now.
This is part of marapongo/mu#38 and marapongo/mu#41.
2017-02-17 21:31:48 +01:00
|
|
|
buffer.WriteString(colors.Reset)
|
2017-02-12 18:38:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
s = buffer.String()
|
|
|
|
|
|
|
|
// If colorization was requested, compile and execute the directives now.
|
|
|
|
if d.opts.Colors {
|
2017-02-28 19:32:24 +01:00
|
|
|
s = colors.ColorizeText(s)
|
2017-02-12 18:38:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|