From 076d689a05afb302e292e6f707f76a44bbaf62d9 Mon Sep 17 00:00:00 2001 From: joeduffy Date: Thu, 2 Mar 2017 17:10:10 -0800 Subject: [PATCH] Rename Monikers to URNs This change is mostly just a rename of Moniker to URN. It does also prefix resource URNs to have a standard URN namespace; in other words, "urn:coconut:", where is the same as the prior Moniker. This is a minor step that helps to prepare us for pulumi/coconut#109. --- cmd/husk.go | 14 ++--- pkg/compiler/errors/planapply.go | 2 +- pkg/graph/dotconv/print.go | 2 +- pkg/resource/context.go | 18 +++---- pkg/resource/deployment.go | 48 ++++++++--------- pkg/resource/moniker.go | 65 ----------------------- pkg/resource/plan.go | 88 ++++++++++++++++---------------- pkg/resource/plugin.go | 10 ++-- pkg/resource/properties.go | 50 +++++++++--------- pkg/resource/resource.go | 36 ++++++------- pkg/resource/resource_graph.go | 22 ++++---- pkg/resource/rpc.go | 20 ++++---- pkg/resource/snapshot.go | 36 ++++++------- pkg/resource/urn.go | 77 ++++++++++++++++++++++++++++ 14 files changed, 250 insertions(+), 238 deletions(-) delete mode 100644 pkg/resource/moniker.go create mode 100644 pkg/resource/urn.go diff --git a/cmd/husk.go b/cmd/husk.go index a9582a3a2..9a0251127 100644 --- a/cmd/husk.go +++ b/cmd/husk.go @@ -631,12 +631,12 @@ func printStep(b *bytes.Buffer, step resource.Step, summary bool, indent string) // First print out the operation's prefix. b.WriteString(step.Op().Prefix()) - // Next print the resource moniker, properties, etc. + // Next print the resource URN, properties, etc. printResourceHeader(b, step.Old(), step.New(), indent) b.WriteString(step.Op().Suffix()) var replaces []resource.PropertyKey if step.Old() != nil { - m := step.Old().Moniker() + m := step.Old().URN() replaceMap := step.Plan().Replaces() replaces = replaceMap[m] } @@ -662,20 +662,20 @@ func printResourceProperties(b *bytes.Buffer, old resource.Resource, new resourc computed resource.PropertyMap, replaces []resource.PropertyKey, summary bool, indent string) { indent += detailsIndent - // Print out the moniker and, if present, the ID, as "pseudo-properties". + // Print out the URN and, if present, the ID, as "pseudo-properties". var id resource.ID - var moniker resource.Moniker + var URN resource.URN if old == nil { id = new.ID() - moniker = new.Moniker() + URN = new.URN() } else { id = old.ID() - moniker = old.Moniker() + URN = old.URN() } if id != "" { b.WriteString(fmt.Sprintf("%s[id=%s]\n", indent, string(id))) } - b.WriteString(fmt.Sprintf("%s[mk=%s]\n", indent, string(moniker))) + b.WriteString(fmt.Sprintf("%s[urn=%s]\n", indent, URN.Name())) if !summary { // Print all of the properties associated with this resource. diff --git a/pkg/compiler/errors/planapply.go b/pkg/compiler/errors/planapply.go index 5df34128d..7d229e5ba 100644 --- a/pkg/compiler/errors/planapply.go +++ b/pkg/compiler/errors/planapply.go @@ -10,7 +10,7 @@ var ( ErrorPlanApplyFailed = newError(2003, "Plan apply failed: %v") ErrorIllegalMarkupExtension = newError(2004, "Resource serialization failed; illegal markup extension '%v'") ErrorCantReadDeployment = newError(2005, "Could not read deployment file '%v': %v") - ErrorDuplicateMonikerNames = newError(2006, "Duplicate objects with the same name: %v") + ErrorDuplicateURNNames = newError(2006, "Duplicate objects with the same URN: %v") ErrorInvalidHuskName = newError(2007, "Invalid husk '%v'; could not be found in the workspace") ErrorIllegalConfigToken = newError(2008, "Configs may only target module properties and class static properties; %v is neither") diff --git a/pkg/graph/dotconv/print.go b/pkg/graph/dotconv/print.go index 96c864e9d..125191658 100644 --- a/pkg/graph/dotconv/print.go +++ b/pkg/graph/dotconv/print.go @@ -35,7 +35,7 @@ func Print(g graph.Graph, w io.Writer) error { } // For now, we auto-generate IDs. - // TODO[pulumi/coconut#76]: use the object monikers instead, once we have them. + // TODO[pulumi/coconut#76]: use the object URNs instead, once we have them. c := 0 ids := make(map[graph.Vertex]string) getID := func(v graph.Vertex) string { diff --git a/pkg/resource/context.go b/pkg/resource/context.go index 32177196a..3ce70429f 100644 --- a/pkg/resource/context.go +++ b/pkg/resource/context.go @@ -17,24 +17,24 @@ type Context struct { Diag diag.Sink // the diagnostics sink to use for messages. Plugins map[tokens.Package]*Plugin // a cache of plugins and their processes. ObjRes objectResourceMap // the resources held inside of this snapshot. - ObjMks objectMonikerMap // a convenient lookup map for object to moniker. - MksRes monikerResourceMap // a convenient lookup map for moniker to resource. - MksOldIDs monikerIDMap // a convenient lookup map for monikers to old IDs. + ObjURN objectURNMap // a convenient lookup map for object to urn. + URNRes urnResourceMap // a convenient lookup map for urn to resource. + URNOldIDs urnIDMap // a convenient lookup map for urns to old IDs. } -type objectMonikerMap map[*rt.Object]Moniker +type objectURNMap map[*rt.Object]URN type objectResourceMap map[*rt.Object]Resource -type monikerResourceMap map[Moniker]Resource -type monikerIDMap map[Moniker]ID +type urnResourceMap map[URN]Resource +type urnIDMap map[URN]ID func NewContext(d diag.Sink) *Context { return &Context{ Diag: d, Plugins: make(map[tokens.Package]*Plugin), ObjRes: make(objectResourceMap), - ObjMks: make(objectMonikerMap), - MksRes: make(monikerResourceMap), - MksOldIDs: make(monikerIDMap), + ObjURN: make(objectURNMap), + URNRes: make(urnResourceMap), + URNOldIDs: make(urnIDMap), } } diff --git a/pkg/resource/deployment.go b/pkg/resource/deployment.go index 8fc461432..6c8e56b89 100644 --- a/pkg/resource/deployment.go +++ b/pkg/resource/deployment.go @@ -21,7 +21,7 @@ type DeploymentRecord struct { Reftag *string `json:"reftag,omitempty"` // the ref alias, if any (`#ref` by default). Package tokens.Package `json:"package"` // the nut that this husk belongs to. Args *core.Args `json:"args,omitempty"` // the blueprint args for graph creation. - Resources *ResourceDeploymentMap `json:"resources,omitempty"` // a map of monikers to resource vertices. + Resources *ResourceDeploymentMap `json:"resources,omitempty"` // a map of URNs to resource vertices. } // DefaultDeploymentReftag is the default ref tag for intra-graph edges. @@ -51,9 +51,9 @@ func serializeDeploymentRecord(snap Snapshot, reftag string) *DeploymentRecord { if snapres := snap.Resources(); len(snapres) > 0 { resm = NewResourceDeploymentMap() for _, res := range snap.Resources() { - m := res.Moniker() - contract.Assertf(string(m) != "", "Unexpected empty resource moniker") - contract.Assertf(!resm.Has(m), "Unexpected duplicate resource moniker '%v'", m) + m := res.URN() + contract.Assertf(string(m) != "", "Unexpected empty resource URN") + contract.Assertf(!resm.Has(m), "Unexpected duplicate resource URN '%v'", m) resm.Add(m, serializeResourceDeployment(res, reftag)) } } @@ -136,7 +136,7 @@ func serializeProperty(prop PropertyValue, reftag string) (interface{}, bool) { return serializeProperties(prop.ObjectValue(), reftag) } - // Morph resources into their equivalent `{ "#ref": "" }` form. + // Morph resources into their equivalent `{ "#ref": "" }` form. if prop.IsResource() { return map[string]string{ reftag: string(prop.ResourceValue()), @@ -171,11 +171,11 @@ func deserializeProperty(v interface{}, reftag string) PropertyValue { } return NewPropertyArray(arr) case map[string]interface{}: - // If the map has a single entry and it is the reftag, this is a moniker. + // If the map has a single entry and it is the reftag, this is a URN. if len(w) == 1 { if tag, has := w[reftag]; has { if tagstr, isstring := tag.(string); isstring { - return NewPropertyResource(Moniker(tagstr)) + return NewPropertyResource(URN(tagstr)) } } } @@ -191,29 +191,29 @@ func deserializeProperty(v interface{}, reftag string) PropertyValue { return NewPropertyNull() } -// ResourceDeploymentMap is a map of moniker to resource, that also preserves a stable order of its keys. This ensures +// ResourceDeploymentMap is a map of URN to resource, that also preserves a stable order of its keys. This ensures // enumerations are ordered deterministically, versus Go's built-in map type whose enumeration is randomized. // Additionally, because of this stable ordering, marshaling to and from JSON also preserves the order of keys. type ResourceDeploymentMap struct { - m map[Moniker]*ResourceDeployment - keys []Moniker + m map[URN]*ResourceDeployment + keys []URN } func NewResourceDeploymentMap() *ResourceDeploymentMap { - return &ResourceDeploymentMap{m: make(map[Moniker]*ResourceDeployment)} + return &ResourceDeploymentMap{m: make(map[URN]*ResourceDeployment)} } -func (m *ResourceDeploymentMap) Keys() []Moniker { return m.keys } -func (m *ResourceDeploymentMap) Len() int { return len(m.keys) } +func (m *ResourceDeploymentMap) Keys() []URN { return m.keys } +func (m *ResourceDeploymentMap) Len() int { return len(m.keys) } -func (m *ResourceDeploymentMap) Add(k Moniker, v *ResourceDeployment) { +func (m *ResourceDeploymentMap) Add(k URN, v *ResourceDeployment) { _, has := m.m[k] contract.Assertf(!has, "Unexpected duplicate key '%v' added to map") m.m[k] = v m.keys = append(m.keys, k) } -func (m *ResourceDeploymentMap) Delete(k Moniker) { +func (m *ResourceDeploymentMap) Delete(k URN) { _, has := m.m[k] contract.Assertf(has, "Unexpected delete of non-existent key key '%v'") delete(m.m, k) @@ -227,29 +227,29 @@ func (m *ResourceDeploymentMap) Delete(k Moniker) { } } -func (m *ResourceDeploymentMap) Get(k Moniker) (*ResourceDeployment, bool) { +func (m *ResourceDeploymentMap) Get(k URN) (*ResourceDeployment, bool) { v, has := m.m[k] return v, has } -func (m *ResourceDeploymentMap) Has(k Moniker) bool { +func (m *ResourceDeploymentMap) Has(k URN) bool { _, has := m.m[k] return has } -func (m *ResourceDeploymentMap) Must(k Moniker) *ResourceDeployment { +func (m *ResourceDeploymentMap) Must(k URN) *ResourceDeployment { v, has := m.m[k] contract.Assertf(has, "Expected key '%v' to exist in this map", k) return v } -func (m *ResourceDeploymentMap) Set(k Moniker, v *ResourceDeployment) { +func (m *ResourceDeploymentMap) Set(k URN, v *ResourceDeployment) { _, has := m.m[k] contract.Assertf(has, "Expected key '%v' to exist in this map for setting an element", k) m.m[k] = v } -func (m *ResourceDeploymentMap) SetOrAdd(k Moniker, v *ResourceDeployment) { +func (m *ResourceDeploymentMap) SetOrAdd(k URN, v *ResourceDeployment) { if _, has := m.m[k]; has { m.Set(k, v) } else { @@ -258,7 +258,7 @@ func (m *ResourceDeploymentMap) SetOrAdd(k Moniker, v *ResourceDeployment) { } type ResourceDeploymentKeyValue struct { - Key Moniker + Key URN Value *ResourceDeployment } @@ -299,7 +299,7 @@ func (m *ResourceDeploymentMap) MarshalJSON() ([]byte, error) { func (m *ResourceDeploymentMap) UnmarshalJSON(b []byte) error { contract.Assert(m.m == nil) - m.m = make(map[Moniker]*ResourceDeployment) + m.m = make(map[URN]*ResourceDeployment) // Do a pass and read keys and values in the right order. rdr := bytes.NewReader(b) @@ -313,7 +313,7 @@ func (m *ResourceDeploymentMap) UnmarshalJSON(b []byte) error { } contract.Assert(opencurly.(json.Delim) == '{') - // Parse out every resource key (Moniker) and element (*ResourceDeployment): + // Parse out every resource key (URN) and element (*ResourceDeployment): for dec.More() { // See if we've reached the closing '}'; if yes, chew on it and break. token, err := dec.Token() @@ -325,7 +325,7 @@ func (m *ResourceDeploymentMap) UnmarshalJSON(b []byte) error { break } - k := Moniker(token.(string)) + k := URN(token.(string)) contract.Assert(dec.More()) var v *ResourceDeployment if err := dec.Decode(&v); err != nil { diff --git a/pkg/resource/moniker.go b/pkg/resource/moniker.go deleted file mode 100644 index 4c0717a44..000000000 --- a/pkg/resource/moniker.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2016 Pulumi, Inc. All rights reserved. - -package resource - -import ( - "strings" - - "github.com/pulumi/coconut/pkg/tokens" - "github.com/pulumi/coconut/pkg/util/contract" -) - -// Moniker is a friendly, but unique, name for a resource, most often auto-assigned by Coconut. These monikers -// are used as unique IDs for objects, and help to to perform graph diffing and resolution of resource object changes. -// -// In theory, we could support manually assigned monikers in the future (e.g., think UUIDs). For the time being, -// however, we have opted to simplify developers' lives by mostly automating the process. The one caveat where it isn't -// truly automatic is that a developer -- or resource provider -- must provide a semi-unique name. -// -// Each moniker is of the form: -// -// :::::: -// -// wherein each element is the following: -// -// The namespace being deployed into -// The module token in which the object was allocated -// The object type's full type token -// The human-friendly name identifier assigned by the developer or provider -// -// In the future, we may add elements to the moniker; it is more important that it is unique than it is human-typable. -type Moniker string - -const MonikerDelimiter = "::" // the delimiter between elements of the moniker. - -// NewMoniker creates a unique moniker for the given object. -func NewMoniker(ns tokens.QName, alloc tokens.Module, t tokens.Type, name tokens.QName) Moniker { - m := Moniker( - string(ns) + - MonikerDelimiter + string(alloc) + - MonikerDelimiter + string(t) + - MonikerDelimiter + string(name), - ) - contract.Assert(!m.Replacement()) - return m -} - -// replaceMonikerSuffix is the suffix for monikers referring to resources that are being replaced. -const replaceMonikerSuffix = Moniker("#") - -// Replace returns a new, modified replacement moniker (used to tag resources that are meant to be replaced). -func (m Moniker) Replace() Moniker { - contract.Assert(!m.Replacement()) - return m + replaceMonikerSuffix -} - -// Unreplace returns the underlying replacement's moniker. -func (m Moniker) Unreplace() Moniker { - contract.Assert(m.Replacement()) - return m[:len(m)-len(replaceMonikerSuffix)] -} - -// Replacement returns true if this moniker refers to a resource that is meant to be replaced. -func (m Moniker) Replacement() bool { - return strings.HasSuffix(string(m), string(replaceMonikerSuffix)) -} diff --git a/pkg/resource/plan.go b/pkg/resource/plan.go index 72709442a..2687d9be2 100644 --- a/pkg/resource/plan.go +++ b/pkg/resource/plan.go @@ -22,7 +22,7 @@ import ( type Plan interface { Empty() bool // true if the plan is empty. Steps() Step // the first step to perform, linked to the rest. - Replaces() map[Moniker][]PropertyKey // resources being replaced and their properties. + Replaces() map[URN][]PropertyKey // resources being replaced and their properties. Unchanged() map[Resource]Resource // the resources untouched by this plan. Apply(prog Progress) (Snapshot, error, Step, ResourceState) // performs the operations specified in this plan. } @@ -126,20 +126,20 @@ func NewPlan(ctx *Context, old Snapshot, new Snapshot) (Plan, error) { } type plan struct { - ctx *Context // this plan's context. - ns tokens.QName // the husk/namespace target being deployed into. - pkg tokens.Package // the package from which this snapshot came. - args core.Args // the arguments used to compile this package. - first *step // the first step to take. - replaces map[Moniker][]PropertyKey // resources being replaced and their properties. - unchanged map[Resource]Resource // the resources that are remaining the same without modification. + ctx *Context // this plan's context. + ns tokens.QName // the husk/namespace target being deployed into. + pkg tokens.Package // the package from which this snapshot came. + args core.Args // the arguments used to compile this package. + first *step // the first step to take. + replaces map[URN][]PropertyKey // resources being replaced and their properties. + unchanged map[Resource]Resource // the resources that are remaining the same without modification. } var _ Plan = (*plan)(nil) -func (p *plan) Replaces() map[Moniker][]PropertyKey { return p.replaces } -func (p *plan) Unchanged() map[Resource]Resource { return p.unchanged } -func (p *plan) Empty() bool { return p.Steps() == nil } +func (p *plan) Replaces() map[URN][]PropertyKey { return p.replaces } +func (p *plan) Unchanged() map[Resource]Resource { return p.unchanged } +func (p *plan) Empty() bool { return p.Steps() == nil } func (p *plan) Steps() Step { if p.first == nil { @@ -254,7 +254,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { // // Any property changes that require replacement are applied, recursively, in a cascading manner. for _, old := range pb.OldRes { - m := old.Moniker() + m := old.URN() pb.Olds[m] = old contract.Assert(old.HasID()) // Keep track of which dependents exist for all resources. @@ -263,12 +263,12 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { } } for _, new := range pb.NewRes { - pb.News[new.Moniker()] = new + pb.News[new.URN()] = new } // Find those things in old but not new, and add them to the delete queue. for _, old := range pb.OldRes { - m := old.Moniker() + m := old.URN() if _, hasnew := pb.News[m]; !hasnew { step := newDeleteStep(pb.P, old) pb.Deletes[m] = newPlanVertex(step) @@ -278,19 +278,19 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { // Find creates and updates: creates are those in new but not old, and updates are those in both. for _, new := range pb.NewRes { - m := new.Moniker() + m := new.URN() if old, hasold := pb.Olds[m]; hasold { // The resource exists in both new and old; it could be an update. This resource is an update if one of // these two conditions exist: 1) either the old and new properties don't match or 2) the update impact // is assessed as having to replace the resource, in which case the ID will change. This might have a // cascading impact on subsequent updates too, since those IDs must trigger recreations, etc. contract.Assert(old.Type() == new.Type()) - computed := new.Properties().ReplaceResources(func(r Moniker) Moniker { + computed := new.Properties().ReplaceResources(func(r URN) URN { if pb.Replace(r) { - // If the resource is being replaced, simply mangle the moniker so that it's different; this value + // If the resource is being replaced, simply mangle the URN so that it's different; this value // won't actually be used for anything other than the diffing algorithms below. r = r.Replace() - glog.V(7).Infof("Patched resource '%v's moniker property: %v", m, r) + glog.V(7).Infof("Patched resource '%v's URN property: %v", m, r) } return r }) @@ -359,7 +359,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { // Clearly we must prohibit cycles in this overall graph of resource operations (hence the DAG part). To ensure // this ordering, we will produce a plan graph whose vertices are operations and whose edges encode dependencies. for _, old := range pb.OldRes { - m := old.Moniker() + m := old.URN() if delete, isdelete := pb.Deletes[m]; isdelete { pb.ConnectDelete(m, delete) // connect this delete so it happens before dependencies. } else if update, isupdate := pb.Updates[m]; isupdate { @@ -367,7 +367,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { } } for _, new := range pb.NewRes { - m := new.Moniker() + m := new.URN() if create, iscreate := pb.Creates[m]; iscreate { pb.ConnectCreate(m, create) // connect this create so it happens after dependencies are created/updated. } @@ -379,17 +379,17 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) (*plan, error) { // planBuilder records a lot of the necessary information during the creation of a plan. type planBuilder struct { - P *plan // the plan under construction. - Olds map[Moniker]Resource // a map of moniker to old resource. - OldRes []Resource // a flat list of old resources (in topological order). - News map[Moniker]Resource // a map of moniker to new resource. - NewRes []Resource // a flat list of new resources (in topological order). - Depends map[Moniker][]Moniker // a map of moniker to all existing (old) dependencies. - Creates map[Moniker]*planVertex // a map of pending creates to their associated vertex. - Updates map[Moniker]*planVertex // a map of pending updates to their associated vertex. - Deletes map[Moniker]*planVertex // a map of pending deletes to their associated vertex. - Replaces map[Moniker][]PropertyKey // a map of monikers scheduled for replacement to properties being replaced. - Unchanged map[Resource]Resource // a map of unchanged resources to their ID-stamped state. + P *plan // the plan under construction. + Olds map[URN]Resource // a map of URN to old resource. + OldRes []Resource // a flat list of old resources (in topological order). + News map[URN]Resource // a map of URN to new resource. + NewRes []Resource // a flat list of new resources (in topological order). + Depends map[URN][]URN // a map of URN to all existing (old) dependencies. + Creates map[URN]*planVertex // a map of pending creates to their associated vertex. + Updates map[URN]*planVertex // a map of pending updates to their associated vertex. + Deletes map[URN]*planVertex // a map of pending deletes to their associated vertex. + Replaces map[URN][]PropertyKey // a map of URNs scheduled for replacement to properties being replaced. + Unchanged map[Resource]Resource // a map of unchanged resources to their ID-stamped state. } // newPlanBuilder initializes a fresh plan state instance, ready to use for planning. @@ -427,32 +427,32 @@ func newPlanBuilder(ctx *Context, old Snapshot, new Snapshot) *planBuilder { return &planBuilder{ P: p, - Olds: make(map[Moniker]Resource), + Olds: make(map[URN]Resource), OldRes: oldres, - News: make(map[Moniker]Resource), + News: make(map[URN]Resource), NewRes: newres, - Depends: make(map[Moniker][]Moniker), - Creates: make(map[Moniker]*planVertex), - Updates: make(map[Moniker]*planVertex), - Deletes: make(map[Moniker]*planVertex), - Replaces: make(map[Moniker][]PropertyKey), + Depends: make(map[URN][]URN), + Creates: make(map[URN]*planVertex), + Updates: make(map[URN]*planVertex), + Deletes: make(map[URN]*planVertex), + Replaces: make(map[URN][]PropertyKey), Unchanged: make(map[Resource]Resource), } } -func (pb *planBuilder) Replace(m Moniker) bool { +func (pb *planBuilder) Replace(m URN) bool { return len(pb.Replaces[m]) > 0 } -func (pb *planBuilder) ConnectCreate(m Moniker, v *planVertex) { +func (pb *planBuilder) ConnectCreate(m URN, v *planVertex) { pb.connectCreateUpdate(m, v, false) } -func (pb *planBuilder) ConnectUpdate(m Moniker, v *planVertex) { +func (pb *planBuilder) ConnectUpdate(m URN, v *planVertex) { pb.connectCreateUpdate(m, v, true) } -func (pb *planBuilder) connectCreateUpdate(m Moniker, v *planVertex, update bool) { +func (pb *planBuilder) connectCreateUpdate(m URN, v *planVertex, update bool) { var label string if update { label = "Updating" @@ -482,7 +482,7 @@ func (pb *planBuilder) connectCreateUpdate(m Moniker, v *planVertex, update bool } } -func (pb *planBuilder) ConnectDelete(m Moniker, v *planVertex) { +func (pb *planBuilder) ConnectDelete(m URN, v *planVertex) { // Add edges to: // - any dependents that used to refer to this (and are necessarily being deleted or updated) for _, dep := range pb.Depends[m] { @@ -501,7 +501,7 @@ func (pb *planBuilder) ConnectDelete(m Moniker, v *planVertex) { func (pb *planBuilder) Plan() (*plan, error) { // For all plan vertices with no ins, make them root nodes. var roots []*planEdge - for _, vs := range []map[Moniker]*planVertex{pb.Creates, pb.Updates, pb.Deletes} { + for _, vs := range []map[URN]*planVertex{pb.Creates, pb.Updates, pb.Deletes} { for _, v := range vs { if len(v.Ins()) == 0 { roots = append(roots, &planEdge{to: v}) diff --git a/pkg/resource/plugin.go b/pkg/resource/plugin.go index 5f9c332da..7678ddf1d 100644 --- a/pkg/resource/plugin.go +++ b/pkg/resource/plugin.go @@ -145,8 +145,8 @@ func (p *Plugin) Name(t tokens.Type, props PropertyMap) (tokens.QName, error) { req := &cocorpc.NameRequest{ Type: string(t), Properties: MarshalProperties(p.ctx, props, MarshalOptions{ - PermitOlds: true, // permit old monikers, since this is pre-update. - RawMonikers: true, // often used during moniker creation; IDs won't be ready. + PermitOlds: true, // permit old URNs, since this is pre-update. + RawURNs: true, // often used during URN creation; IDs won't be ready. }), } @@ -215,7 +215,7 @@ func (p *Plugin) Update(id ID, t tokens.Type, olds PropertyMap, news PropertyMap Id: string(id), Type: string(t), Olds: MarshalProperties(p.ctx, olds, MarshalOptions{ - PermitOlds: true, // permit old monikers since these are the old values. + PermitOlds: true, // permit old URNs since these are the old values. }), News: MarshalProperties(p.ctx, news, MarshalOptions{}), } @@ -242,10 +242,10 @@ func (p *Plugin) UpdateImpact(id ID, t tokens.Type, Id: string(id), Type: string(t), Olds: MarshalProperties(p.ctx, olds, MarshalOptions{ - RawMonikers: true, // often used during moniker creation; IDs won't be ready. + RawURNs: true, // often used during URN creation; IDs won't be ready. }), News: MarshalProperties(p.ctx, news, MarshalOptions{ - RawMonikers: true, // often used during moniker creation; IDs won't be ready. + RawURNs: true, // often used during URN creation; IDs won't be ready. }), } diff --git a/pkg/resource/properties.go b/pkg/resource/properties.go index 21037bbd2..173faed73 100644 --- a/pkg/resource/properties.go +++ b/pkg/resource/properties.go @@ -150,7 +150,7 @@ func (m PropertyMap) ObjectOrErr(k PropertyKey, req bool) (*PropertyMap, error) } // ResourceOrErr checks that the given property is a resource, issuing an error if not; req indicates if required. -func (m PropertyMap) ResourceOrErr(k PropertyKey, req bool) (*Moniker, error) { +func (m PropertyMap) ResourceOrErr(k PropertyKey, req bool) (*URN, error) { if v, has := m[k]; has && !v.IsNull() { if !v.IsResource() { return nil, fmt.Errorf("property '%v' is not an object (%v)", k, reflect.TypeOf(v.V)) @@ -226,11 +226,11 @@ func (m PropertyMap) ReqObjectOrErr(k PropertyKey) (PropertyMap, error) { return *o, nil } -// ReqResourceOrErr checks that the given property exists and has the type moniker. -func (m PropertyMap) ReqResourceOrErr(k PropertyKey) (Moniker, error) { +// ReqResourceOrErr checks that the given property exists and has the type URN. +func (m PropertyMap) ReqResourceOrErr(k PropertyKey) (URN, error) { r, err := m.ResourceOrErr(k, true) if err != nil { - return Moniker(""), err + return URN(""), err } return *r, nil } @@ -270,25 +270,25 @@ func (m PropertyMap) OptObjectOrErr(k PropertyKey) (*PropertyMap, error) { return m.ObjectOrErr(k, false) } -// OptResourceOrErr checks that the given property has the type moniker, if it exists. -func (m PropertyMap) OptResourceOrErr(k PropertyKey) (*Moniker, error) { +// OptResourceOrErr checks that the given property has the type URN, if it exists. +func (m PropertyMap) OptResourceOrErr(k PropertyKey) (*URN, error) { return m.ResourceOrErr(k, false) } -// AllResources finds all resource monikers, transitively throughout the property map, and returns them. -func (props PropertyMap) AllResources() map[Moniker]bool { - monikers := make(map[Moniker]bool) +// AllResources finds all resource URNs, transitively throughout the property map, and returns them. +func (props PropertyMap) AllResources() map[URN]bool { + URNs := make(map[URN]bool) for _, k := range StablePropertyKeys(props) { for m, v := range props[k].AllResources() { - monikers[m] = v + URNs[m] = v } } - return monikers + return URNs } // ReplaceResources finds all resources and lets an updater function update them if necessary. This is often used -// during a "replacement"-style updated, to replace all monikers of a certain value with another. -func (props PropertyMap) ReplaceResources(updater func(Moniker) Moniker) PropertyMap { +// during a "replacement"-style updated, to replace all URNs of a certain value with another. +func (props PropertyMap) ReplaceResources(updater func(URN) URN) PropertyMap { result := make(PropertyMap) for _, k := range StablePropertyKeys(props) { result[k] = props[k].ReplaceResources(updater) @@ -302,14 +302,14 @@ func NewPropertyNumber(v float64) PropertyValue { return PropertyValue{v} func NewPropertyString(v string) PropertyValue { return PropertyValue{v} } func NewPropertyArray(v []PropertyValue) PropertyValue { return PropertyValue{v} } func NewPropertyObject(v PropertyMap) PropertyValue { return PropertyValue{v} } -func NewPropertyResource(v Moniker) PropertyValue { return PropertyValue{v} } +func NewPropertyResource(v URN) PropertyValue { return PropertyValue{v} } func (v PropertyValue) BoolValue() bool { return v.V.(bool) } func (v PropertyValue) NumberValue() float64 { return v.V.(float64) } func (v PropertyValue) StringValue() string { return v.V.(string) } func (v PropertyValue) ArrayValue() []PropertyValue { return v.V.([]PropertyValue) } func (v PropertyValue) ObjectValue() PropertyMap { return v.V.(PropertyMap) } -func (v PropertyValue) ResourceValue() Moniker { return v.V.(Moniker) } +func (v PropertyValue) ResourceValue() URN { return v.V.(URN) } func (b PropertyValue) IsNull() bool { return b.V == nil @@ -335,32 +335,32 @@ func (b PropertyValue) IsObject() bool { return is } func (b PropertyValue) IsResource() bool { - _, is := b.V.(Moniker) + _, is := b.V.(URN) return is } -// AllResources finds all resource monikers, transitively throughout the property value, and returns them. -func (v PropertyValue) AllResources() map[Moniker]bool { - monikers := make(map[Moniker]bool) +// AllResources finds all resource URNs, transitively throughout the property value, and returns them. +func (v PropertyValue) AllResources() map[URN]bool { + URNs := make(map[URN]bool) if v.IsResource() { - monikers[v.ResourceValue()] = true + URNs[v.ResourceValue()] = true } else if v.IsArray() { for _, elem := range v.ArrayValue() { for m, v := range elem.AllResources() { - monikers[m] = v + URNs[m] = v } } } else if v.IsObject() { for m, v := range v.ObjectValue().AllResources() { - monikers[m] = v + URNs[m] = v } } - return monikers + return URNs } // ReplaceResources finds all resources and lets an updater function update them if necessary. This is often used -// during a "replacement"-style updated, to replace all monikers of a certain value with another. -func (v PropertyValue) ReplaceResources(updater func(Moniker) Moniker) PropertyValue { +// during a "replacement"-style updated, to replace all URNs of a certain value with another. +func (v PropertyValue) ReplaceResources(updater func(URN) URN) PropertyValue { if v.IsResource() { m := v.ResourceValue() return NewPropertyResource(updater(m)) diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index d83d4d3c9..c16cbdd8c 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -24,13 +24,13 @@ type ID string // Resource is an instance of a resource with an ID, type, and bag of state. type Resource interface { ID() ID // the resource's unique ID, assigned by the resource provider (or blank if uncreated). - Moniker() Moniker // the resource's object moniker, a human-friendly, unique name for the resource. + URN() URN // the resource's object urn, a human-friendly, unique name for the resource. Type() tokens.Type // the resource's type. Properties() PropertyMap // the resource's property map. HasID() bool // returns true if the resource has been assigned an ID. SetID(id ID) // assignes an ID to this resource, for those under creation. - HasMoniker() bool // returns true if the resource has been assigned moniker. - SetMoniker(m Moniker) // assignes a moniker to this resource, for those under creation. + HasURN() bool // returns true if the resource has been assigned urn. + SetURN(m URN) // assignes a urn to this resource, for those under creation. } // ResourceState is returned when an error has occurred during a resource provider operation. It indicates whether the @@ -47,13 +47,13 @@ func IsResourceVertex(v *heapstate.ObjectVertex) bool { return IsResourceType(v. type resource struct { id ID // the resource's unique ID, assigned by the resource provider (or blank if uncreated). - moniker Moniker // the resource's object moniker, a human-friendly, unique name for the resource. + urn URN // the resource's object urn, a human-friendly, unique name for the resource. t tokens.Type // the resource's type. properties PropertyMap // the resource's property map. } func (r *resource) ID() ID { return r.id } -func (r *resource) Moniker() Moniker { return r.moniker } +func (r *resource) URN() URN { return r.urn } func (r *resource) Type() tokens.Type { return r.t } func (r *resource) Properties() PropertyMap { return r.properties } @@ -63,17 +63,17 @@ func (r *resource) SetID(id ID) { r.id = id } -func (r *resource) HasMoniker() bool { return (string(r.moniker) != "") } -func (r *resource) SetMoniker(m Moniker) { - contract.Requiref(!r.HasMoniker(), "moniker", "empty") - r.moniker = m +func (r *resource) HasURN() bool { return (string(r.urn) != "") } +func (r *resource) SetURN(m URN) { + contract.Requiref(!r.HasURN(), "urn", "empty") + r.urn = m } // NewResource creates a new resource from the information provided. -func NewResource(id ID, moniker Moniker, t tokens.Type, properties PropertyMap) Resource { +func NewResource(id ID, urn URN, t tokens.Type, properties PropertyMap) Resource { return &resource{ id: id, - moniker: moniker, + urn: urn, t: t, properties: properties, } @@ -85,9 +85,9 @@ func NewObjectResource(ctx *Context, obj *rt.Object) Resource { t := obj.Type() contract.Assert(IsResourceType(t)) - // Extract the moniker. This must already exist. - m, hasm := ctx.ObjMks[obj] - contract.Assertf(!hasm, "Object already assigned a moniker '%v'; double allocation detected", m) + // Extract the urn. This must already exist. + urn, hasm := ctx.ObjURN[obj] + contract.Assertf(!hasm, "Object already assigned a urn '%v'; double allocation detected", urn) // Do a deep copy of the resource properties. This ensures property serializability. props := cloneObject(ctx, obj) @@ -101,7 +101,7 @@ func NewObjectResource(ctx *Context, obj *rt.Object) Resource { // cloneObject creates a property map out of a runtime object. The result is fully serializable in the sense that it // can be stored in a JSON or YAML file, serialized over an RPC interface, etc. In particular, any references to other -// resources are replaced with their moniker equivalents, which the runtime understands. +// resources are replaced with their urn equivalents, which the runtime understands. func cloneObject(ctx *Context, obj *rt.Object) PropertyMap { contract.Assert(obj != nil) src := obj.PropertyValues() @@ -120,10 +120,10 @@ func cloneObject(ctx *Context, obj *rt.Object) PropertyMap { func cloneObjectValue(ctx *Context, obj *rt.Object) (PropertyValue, bool) { t := obj.Type() if IsResourceType(t) { - // For resources, simply look up the moniker from the resource map. - m, hasm := ctx.ObjMks[obj] + // For resources, simply look up the urn from the resource map. + urn, hasm := ctx.ObjURN[obj] contract.Assertf(hasm, "Missing object reference; possible out of order dependency walk") - return NewPropertyResource(m), true + return NewPropertyResource(urn), true } switch t { diff --git a/pkg/resource/resource_graph.go b/pkg/resource/resource_graph.go index a12f06f66..7827eb901 100644 --- a/pkg/resource/resource_graph.go +++ b/pkg/resource/resource_graph.go @@ -14,28 +14,28 @@ type resourceGraph struct { var _ graph.Graph = (*resourceGraph)(nil) -// newResourceGraph produces a DAG using the resources' properties embedded moniker information. +// newResourceGraph produces a DAG using the resources' properties embedded URN information. func newResourceGraph(resources []Resource) *resourceGraph { - // First make two maps: one with monikers to resources, the other with resources to vertices. - mks := make(map[Moniker]Resource) + // First make two maps: one with URNs to resources, the other with resources to vertices. + urns := make(map[URN]Resource) verts := make(map[Resource]*resourceVertex) for _, res := range resources { contract.Assert(res != nil) - m := res.Moniker() - contract.Assertf(mks[m] == nil, "Unexpected duplicate entry '%v' in resource list", m) - mks[m] = res + urn := res.URN() + contract.Assertf(urns[urn] == nil, "Unexpected duplicate entry '%v' in resource list", urn) + urns[urn] = res verts[res] = newResourceVertex(res) } // Now walk the list of resources and connect them to their dependencies. for _, res := range resources { - m := res.Moniker() + urn := res.URN() fromv := verts[res] - for ref := range res.Properties().AllResources() { - to := mks[ref] - contract.Assertf(to != nil, "Missing resource for target; from=%v to=%v", m, ref) + for dep := range res.Properties().AllResources() { + to := urns[dep] + contract.Assertf(to != nil, "Missing resource for target; from=%v to=%v", urn, dep) tov := verts[to] - contract.Assertf(tov != nil, "Missing vertex entry for target; from=%v to=%v", m, ref) + contract.Assertf(tov != nil, "Missing vertex entry for target; from=%v to=%v", urn, dep) fromv.connectTo(tov) } } diff --git a/pkg/resource/rpc.go b/pkg/resource/rpc.go index 13ce9b589..f78cc6b8d 100644 --- a/pkg/resource/rpc.go +++ b/pkg/resource/rpc.go @@ -14,12 +14,12 @@ import ( // MarshalOptions controls the marshaling of RPC structures. type MarshalOptions struct { - PermitOlds bool // true to permit old monikers in the properties (e.g., for pre-update). - RawMonikers bool // true to marshal monikers "as-is"; often used when ID mappings aren't known yet. + PermitOlds bool // true to permit old URNs in the properties (e.g., for pre-update). + RawURNs bool // true to marshal URNs "as-is"; often used when ID mappings aren't known yet. } -// MarshalProperties marshals a resource's property map as a "JSON-like" protobuf structure. Any monikers are replaced -// with their resource IDs during marshaling; it is an error to marshal a moniker for a resource without an ID. +// MarshalProperties marshals a resource's property map as a "JSON-like" protobuf structure. Any URNs are replaced +// with their resource IDs during marshaling; it is an error to marshal a URN for a resource without an ID. func MarshalProperties(ctx *Context, props PropertyMap, opts MarshalOptions) *structpb.Struct { result := &structpb.Struct{ Fields: make(map[string]*structpb.Value), @@ -79,21 +79,21 @@ func MarshalPropertyValue(ctx *Context, v PropertyValue, opts MarshalOptions) (* } else if v.IsResource() { var wire string m := v.ResourceValue() - if opts.RawMonikers { + if opts.RawURNs { wire = string(m) } else { var id ID - if res, has := ctx.MksRes[m]; has { + if res, has := ctx.URNRes[m]; has { id = res.ID() // found a new resource with this ID, use it. - } else if oldid, has := ctx.MksOldIDs[m]; opts.PermitOlds && has { + } else if oldid, has := ctx.URNOldIDs[m]; opts.PermitOlds && has { id = oldid // found an old resource, maybe deleted, so use that. } else { - contract.Failf("Expected resource moniker '%v' to exist at marshal time", m) + contract.Failf("Expected resource URN '%v' to exist at marshal time", m) } - contract.Assertf(id != "", "Expected resource moniker '%v' to have an ID at marshal time", m) + contract.Assertf(id != "", "Expected resource URN '%v' to have an ID at marshal time", m) wire = string(id) } - glog.V(7).Infof("Serializing resource moniker '%v' as '%v' (raw=%v)", m, wire, opts.RawMonikers) + glog.V(7).Infof("Serializing resource URN '%v' as '%v' (raw=%v)", m, wire, opts.RawURNs) return &structpb.Value{ Kind: &structpb.Value_StringValue{ StringValue: wire, diff --git a/pkg/resource/snapshot.go b/pkg/resource/snapshot.go index 814b54c44..a00810685 100644 --- a/pkg/resource/snapshot.go +++ b/pkg/resource/snapshot.go @@ -24,7 +24,7 @@ type Snapshot interface { Args() core.Args // the arguments used to compile this package. Resources() []Resource // a topologically sorted list of resources (based on dependencies). ResourceByID(id ID, t tokens.Type) Resource // looks up a resource by ID and type. - ResourceByMoniker(m Moniker) Resource // looks up a resource by its moniker. + ResourceByURN(urn URN) Resource // looks up a resource by its URN. ResourceByObject(obj *rt.Object) Resource // looks up a resource by its object. } @@ -43,7 +43,7 @@ func NewGraphSnapshot(ctx *Context, ns tokens.QName, pkg tokens.Package, args co if old != nil { for _, res := range old.Resources() { contract.Assert(res.HasID()) - ctx.MksOldIDs[res.Moniker()] = res.ID() + ctx.URNOldIDs[res.URN()] = res.ID() } } @@ -53,8 +53,8 @@ func NewGraphSnapshot(ctx *Context, ns tokens.QName, pkg tokens.Package, args co return nil, err } - // Next, name all resources, create their monikers and objects, and maps that we will use. Note that we must do - // this in DAG order (guaranteed by our topological sort above), so that referenced monikers are available. + // Next, name all resources, create their URNs and objects, and maps that we will use. Note that we must do + // this in DAG order (guaranteed by our topological sort above), so that referenced URNs are available. resources, err := createResources(ctx, ns, heap, resobjs) if err != nil { return nil, err @@ -82,15 +82,15 @@ func (s *snapshot) ResourceByID(id ID, t tokens.Type) Resource { return nil } -func (s *snapshot) ResourceByMoniker(m Moniker) Resource { return s.ctx.MksRes[m] } +func (s *snapshot) ResourceByURN(urn URN) Resource { return s.ctx.URNRes[urn] } func (s *snapshot) ResourceByObject(obj *rt.Object) Resource { return s.ctx.ObjRes[obj] } -// createResources uses a graph to create monikers and resource objects for every resource within. It -// returns two maps for further use: a map of vertex to its new resource object, and a map of vertex to its moniker. +// createResources uses a graph to create URNs and resource objects for every resource within. It +// returns two maps for further use: a map of vertex to its new resource object, and a map of vertex to its URN. func createResources(ctx *Context, husk tokens.QName, heap *heapstate.Heap, resobjs []*rt.Object) ([]Resource, error) { var resources []Resource for _, resobj := range resobjs { - // Create an object resource without a moniker. + // Create an object resource without a URN. res := NewObjectResource(ctx, resobj) // Now fetch this resource's name by looking up its provider and doing an RPC. @@ -104,20 +104,20 @@ func createResources(ctx *Context, husk tokens.QName, heap *heapstate.Heap, reso return nil, err } - // Now compute a unique moniker for this object and ensure we haven't had any collisions. + // Now compute a unique URN for this object and ensure we haven't had any collisions. alloc := heap.Alloc(resobj) - moniker := NewMoniker(husk, alloc.Mod.Tok, t, name) - glog.V(7).Infof("Resource moniker computed: %v", moniker) - if _, exists := ctx.MksRes[moniker]; exists { - // If this moniker is already in use, issue an error, ignore this one, and break. The break is necessary - // because subsequent resources might contain references to this moniker and would fail to find it. - ctx.Diag.Errorf(errors.ErrorDuplicateMonikerNames.At(alloc.Loc), moniker) + urn := NewURN(husk, alloc.Mod.Tok, t, name) + glog.V(7).Infof("Resource URN computed: %v", urn) + if _, exists := ctx.URNRes[urn]; exists { + // If this URN is already in use, issue an error, ignore this one, and break. The break is necessary + // because subsequent resources might contain references to this URN and would fail to find it. + ctx.Diag.Errorf(errors.ErrorDuplicateURNNames.At(alloc.Loc), urn) break } else { - res.SetMoniker(moniker) + res.SetURN(urn) ctx.ObjRes[resobj] = res - ctx.MksRes[moniker] = res - ctx.ObjMks[resobj] = moniker + ctx.URNRes[urn] = res + ctx.ObjURN[resobj] = urn } resources = append(resources, res) } diff --git a/pkg/resource/urn.go b/pkg/resource/urn.go new file mode 100644 index 000000000..f889a882a --- /dev/null +++ b/pkg/resource/urn.go @@ -0,0 +1,77 @@ +// Copyright 2016 Pulumi, Inc. All rights reserved. + +package resource + +import ( + "strings" + + "github.com/pulumi/coconut/pkg/tokens" + "github.com/pulumi/coconut/pkg/util/contract" +) + +// URN is a friendly, but unique, URN for a resource, most often auto-assigned by Coconut. These are +// used as unique IDs for objects, and help us to perform graph diffing and resolution of resource objects. +// +// In theory, we could support manually assigned URIs in the future. For the time being, however, we have opted to +// simplify developers' lives by mostly automating the generation of them algorithmically. The one caveat where it +// isn't truly automatic is that a developer -- or resource provider -- must provide a semi-unique name part. +// +// Each resource URN is of the form: +// +// urn:coconut::::::: +// +// wherein each element is the following: +// +// The namespace being deployed into +// The module token in which the object was allocated +// The object type's full type token +// The human-friendly name identifier assigned by the developer or provider +// +// In the future, we may add elements to the URN; it is more important that it is unique than it is human-typable. +type URN string + +const ( + URNPrefix = "urn:" + URNNamespaceID + ":" // the standard URN prefix + URNNamespaceID = "coconut" // the URN namespace + URNNameDelimiter = "::" // the delimiter between URN name elements +) + +// NewURN creates a unique resource URN for the given resource object. +func NewURN(ns tokens.QName, alloc tokens.Module, t tokens.Type, name tokens.QName) URN { + urn := URN( + URNPrefix + + string(ns) + + URNNameDelimiter + string(alloc) + + URNNameDelimiter + string(t) + + URNNameDelimiter + string(name), + ) + contract.Assert(!urn.Replacement()) + return urn +} + +// replaceURNSuffix is the suffix for URNs referring to resources that are being replaced. +const replaceURNSuffix = URN("#") + +// Name returns the name part of a URN. +func (urn URN) Name() string { + urns := string(urn) + contract.Assert(strings.HasPrefix(urns, URNPrefix)) + return urns[len(URNPrefix)+1:] +} + +// Replace returns a new, modified replacement URN (used to tag resources that are meant to be replaced). +func (urn URN) Replace() URN { + contract.Assert(!urn.Replacement()) + return urn + replaceURNSuffix +} + +// Unreplace returns the underlying replacement's URN. +func (urn URN) Unreplace() URN { + contract.Assert(urn.Replacement()) + return urn[:len(urn)-len(replaceURNSuffix)] +} + +// Replacement returns true if this URN refers to a resource that is meant to be replaced. +func (urn URN) Replacement() bool { + return strings.HasSuffix(string(urn), string(replaceURNSuffix)) +}