diff --git a/cmd/shared.go b/cmd/shared.go index 865f74115..267ab4118 100644 --- a/cmd/shared.go +++ b/cmd/shared.go @@ -172,7 +172,7 @@ func apply(cmd *cobra.Command, args []string, existing string, opts applyOptions // TODO: we want richer diagnostics in the event that a plan apply fails. For instance, we want to // know precisely what step failed, we want to know whether it was catastrophic, etc. We also // probably want to plumb diag.Sink through apply so it can issue its own rich diagnostics. - result.C.Diag().Errorf(errors.ErrorPlanApplyFailed, err) + sink().Errorf(errors.ErrorPlanApplyFailed, err) } // Print out the total number of steps performed (and their kinds), if any succeeded. @@ -201,6 +201,7 @@ func apply(cmd *cobra.Command, args []string, existing string, opts applyOptions fmt.Printf(colors.Colorize(s)) // Now save the updated snapshot to the specified output file, if any, or the standard location otherwise. + // TODO: perform partial updates if we weren't able to perform the entire planned set of operations. if opts.Delete { contract.Assert(result.Mugfile != "") deleteSnapshot(result.Mugfile) @@ -223,7 +224,7 @@ func applyExisting(cmd *cobra.Command, args []string, opts applyOptions) { // Read in the snapshot argument. // TODO: if not supplied, auto-detect the current one. if len(args) == 0 { - fmt.Fprintf(os.Stderr, "fatal: missing required snapshot argument") + fmt.Fprintf(os.Stderr, "fatal: missing required snapshot argument\n") os.Exit(-1) } diff --git a/pkg/resource/plan.go b/pkg/resource/plan.go index 85223d379..fbdaeef99 100644 --- a/pkg/resource/plan.go +++ b/pkg/resource/plan.go @@ -193,17 +193,19 @@ func (s *step) Apply() (error, ResourceState) { // Now simply perform the operation of the right kind. switch s.op { case OpCreate: + contract.Assertf(!s.res.HasID(), "Resources being created must not have IDs already") id, err, rst := prov.Create(s.res) if err != nil { return err, rst } s.res.SetID(id) case OpDelete: - contract.Assertf(string(s.res.ID()) == "", "Expected resource to be created has no ID") + contract.Assertf(s.res.HasID(), "Resources being deleted must have IDs") if err, rst := prov.Delete(s.res); err != nil { return err, rst } case OpUpdate: + contract.Assertf(s.res.HasID(), "Resources being updated must have IDs") id, err, rst := prov.Update(s.res, s.old) if err != nil { return err, rst diff --git a/pkg/resource/plugin.go b/pkg/resource/plugin.go index b3860d491..34ea18962 100644 --- a/pkg/resource/plugin.go +++ b/pkg/resource/plugin.go @@ -188,8 +188,8 @@ func (p *Plugin) Read(id ID, t tokens.Type) (PropertyMap, error) { // Update updates an existing resource with new values. Only those values in the provided property bag are updated // to new values. The resource ID is returned and may be different if the resource had to be recreated. func (p *Plugin) Update(old Resource, new Resource) (ID, error, ResourceState) { - contract.Requiref(old.ID() != "", "old.ID", "not empty") - contract.Requiref(new.ID() != "", "old.ID", "not empty") + contract.Requiref(old.HasID(), "old.ID", "not empty") + contract.Requiref(new.HasID(), "old.ID", "not empty") contract.Requiref(old.ID() == new.ID(), "old.ID, new.ID", "==") contract.Requiref(old.Type() != "", "old.Type", "not empty") contract.Requiref(new.Type() != "", "new.Type", "not empty") @@ -220,7 +220,7 @@ func (p *Plugin) Update(old Resource, new Resource) (ID, error, ResourceState) { // Delete tears down an existing resource. func (p *Plugin) Delete(res Resource) (error, ResourceState) { - contract.Requiref(res.ID() != "", "res.ID", "not empty") + contract.Requiref(res.HasID(), "res.ID", "not empty") contract.Requiref(res.Type() != "", "res.Type", "not empty") id := string(res.ID()) diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index 3f598477a..7914ccab1 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -25,6 +25,7 @@ type Resource interface { Moniker() Moniker // the resource's object moniker, 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. } @@ -52,8 +53,9 @@ func (r *resource) Moniker() Moniker { return r.moniker } func (r *resource) Type() tokens.Type { return r.t } func (r *resource) Properties() PropertyMap { return r.properties } +func (r *resource) HasID() bool { return (string(r.id) != "") } func (r *resource) SetID(id ID) { - contract.Requiref(string(r.id) == "", "id", "empty") + contract.Requiref(!r.HasID(), "id", "empty") r.id = id }