Small cleanups and comments

This commit is contained in:
Alex Clemmer 2018-07-06 15:17:32 -07:00
parent 1cbf8bdc40
commit 456deaf442
4 changed files with 90 additions and 84 deletions

View file

@ -182,6 +182,9 @@ func (s *CreateStep) Apply(preview bool) (resource.Status, error) {
}
s.reg.Done(&RegisterResult{State: s.new})
if resourceError == nil {
return resourceStatus, nil
}
return resourceStatus, resourceError
}
@ -328,6 +331,9 @@ func (s *UpdateStep) Apply(preview bool) (resource.Status, error) {
// Finally, mark this operation as complete.
s.reg.Done(&RegisterResult{State: s.new, Stables: s.stables})
if resourceError == nil {
return resourceStatus, nil
}
return resourceStatus, resourceError
}

View file

@ -262,7 +262,7 @@ func (p *provider) Create(urn resource.URN, props resource.PropertyMap) (resourc
}
var id resource.ID
var properties *_struct.Struct
var liveObject *_struct.Struct
var resourceError *rpcerror.Error
var resourceStatus = resource.StatusOK
resp, err := client.Create(p.ctx.Request(), &pulumirpc.CreateRequest{
@ -270,26 +270,16 @@ func (p *provider) Create(urn resource.URN, props resource.PropertyMap) (resourc
Properties: mprops,
})
if err != nil {
resourceStatus, resourceError = resourceStateAndError(err)
contract.Assert(resourceError != nil)
logging.V(7).Infof("%s failed: err=%v", label, resourceError)
for _, detail := range resourceError.Details() {
// If resource was successfully created but failed to initialize, the error will be packed
// with the live properties of the object.
if initErr, ok := detail.(*pulumirpc.ErrorResourceInitFailed); ok {
id = resource.ID(initErr.GetId())
properties = initErr.GetProperties()
resourceStatus = resource.StatusPartialFailure
}
}
resourceStatus, resourceError, liveObject = parseError(err)
logging.V(7).Infof("%s failed: %v", label, resourceError)
if resourceStatus == resource.StatusUnknown {
return "", nil, resourceStatus, resourceError
}
// Else it's a `StatusPartialFailure`.
} else {
id = resource.ID(resp.GetId())
properties = resp.GetProperties()
liveObject = resp.GetProperties()
}
if id == "" {
@ -297,10 +287,10 @@ func (p *provider) Create(urn resource.URN, props resource.PropertyMap) (resourc
errors.Errorf("plugin for package '%v' returned empty resource.ID from create '%v'", p.pkg, urn)
}
outs, err := UnmarshalProperties(properties, MarshalOptions{
outs, err := UnmarshalProperties(liveObject, MarshalOptions{
Label: fmt.Sprintf("%s.outputs", label), RejectUnknowns: true})
if err != nil {
return "", nil, resource.StatusUnknown, err
return "", nil, resourceStatus, err
}
logging.V(7).Infof("%s success: id=%s; #outs=%d", label, id, len(outs))
@ -389,7 +379,7 @@ func (p *provider) Update(urn resource.URN, id resource.ID,
return nil, resource.StatusOK, err
}
var properties *_struct.Struct
var liveObject *_struct.Struct
var resourceError *rpcerror.Error
var resourceStatus = resource.StatusOK
resp, err := client.Update(p.ctx.Request(), &pulumirpc.UpdateRequest{
@ -399,30 +389,21 @@ func (p *provider) Update(urn resource.URN, id resource.ID,
News: mnews,
})
if err != nil {
resourceStatus, resourceError = resourceStateAndError(err)
contract.Assert(resourceError != nil)
resourceStatus, resourceError, liveObject = parseError(err)
logging.V(7).Infof("%s failed: %v", label, resourceError)
// If resource was successfully created but failed to initialize, the error will be packed
// with the live properties of the object.
for _, detail := range resourceError.Details() {
if initErr, ok := detail.(*pulumirpc.ErrorResourceInitFailed); ok {
properties = initErr.GetProperties()
resourceStatus = resource.StatusPartialFailure
}
}
if resourceStatus == resource.StatusUnknown {
return nil, resourceStatus, resourceError
}
// Else it's a `StatusPartialFailure`.
} else {
properties = resp.GetProperties()
liveObject = resp.GetProperties()
}
outs, err := UnmarshalProperties(properties, MarshalOptions{
outs, err := UnmarshalProperties(liveObject, MarshalOptions{
Label: fmt.Sprintf("%s.outputs", label), RejectUnknowns: true})
if err != nil {
return nil, resource.StatusUnknown, err
return nil, resourceStatus, err
}
logging.V(7).Infof("%s success; #outs=%d", label, len(outs))
@ -592,3 +573,29 @@ func resourceStateAndError(err error) (resource.Status, *rpcerror.Error) {
logging.V(8).Infof("rpc error kind `%s` is well-understood and recoverable", rpcError.Code())
return resource.StatusOK, rpcError
}
// parseError parses a gRPC error into a set of values that represent the state of a resource. They
// are: (1) the `resourceStatus`, indicating the last known state (e.g., `StatusOK`, representing
// success, `StatusUnknown`, representing internal failure); (2) the `*rpcerror.Error`, our internal
// representation for RPC errors; and optionally (3) `liveObject`, containing the last known live
// version of the object that has successfully created but failed to initialize (e.g., because the
// object was created, but app code is continually crashing and the resource never achieves
// liveness).
func parseError(err error) (
resourceStatus resource.Status, resourceErr *rpcerror.Error, liveObject *_struct.Struct,
) {
resourceStatus, resourceErr = resourceStateAndError(err)
contract.Assert(resourceErr != nil)
// If resource was successfully created but failed to initialize, the error will be packed
// with the live properties of the object.
for _, detail := range resourceErr.Details() {
if initErr, ok := detail.(*pulumirpc.ErrorResourceInitFailed); ok {
liveObject = initErr.GetProperties()
resourceStatus = resource.StatusPartialFailure
break
}
}
return resourceStatus, resourceErr, liveObject
}

View file

@ -155,32 +155,7 @@ async function createRPC(call: any, callback: any): Promise<void> {
callback(undefined, resp);
} catch (e) {
// Create response object.
const resp = new statusproto.Status();
resp.setCode(grpc.status.UNKNOWN);
resp.setMessage(e.message);
// Pack initialization failure into details.
const metadata = new grpc.Metadata();
if (e.id) {
const detail = new provproto.ErrorResourceInitFailed();
detail.setId(e.id);
detail.setProperties(structproto.Struct.fromJavaScript(e.properties || {}));
detail.addReasons(e.reasons || []);
const details = new anyproto.Any();
details.pack(detail.serializeBinary(), "pulumirpc.ErrorResourceInitFailed");
// Add details to metadata.
resp.addDetails(details);
metadata.add("grpc-status-details-bin", Buffer.from(resp.serializeBinary()));
}
return callback({
code: grpc.status.UNKNOWN,
message: e.message,
metadata: metadata,
});
return callback(grpcResponseFromError(e));
}
}
@ -233,32 +208,7 @@ async function updateRPC(call: any, callback: any): Promise<void> {
callback(undefined, resp);
} catch (e) {
// Create response object.
const resp = new statusproto.Status();
resp.setCode(grpc.status.UNKNOWN);
resp.setMessage(e.message);
// Pack initialization failure into details.
const metadata = new grpc.Metadata();
if (e.id) {
const detail = new provproto.ErrorResourceInitFailed();
detail.setId(e.id);
detail.setProperties(structproto.Struct.fromJavaScript(e.properties || {}));
detail.addReasons(e.reasons || []);
const details = new anyproto.Any();
details.pack(detail.serializeBinary(), "pulumirpc.ErrorResourceInitFailed");
// Add details to metadata.
resp.addDetails(details);
metadata.add("grpc-status-details-bin", Buffer.from(resp.serializeBinary()));
}
return callback({
code: grpc.status.UNKNOWN,
message: e.message,
metadata: metadata,
});
return callback(grpcResponseFromError(e));
}
}
@ -289,6 +239,45 @@ function resultIncludingProvider(result: any, props: any): any {
});
}
// grpcResponseFromError creates a gRPC response representing an error from a dynamic provider's
// resource. This is typically either a creation error, in which the API server has (virtually)
// rejected the resource, or an initialization error, where the API server has accepted the
// resource, but it failed to initialize (e.g., the app code is continually crashing and the
// resource has failed to become alive).
function grpcResponseFromError(e: {id: string, properties: any, message: string, reasons?: string[]}): any {
// Create response object.
const resp = new statusproto.Status();
resp.setCode(grpc.status.UNKNOWN);
resp.setMessage(e.message);
const metadata = new grpc.Metadata();
if (e.id) {
// Object created successfully, but failed to initialize. Pack initialization failure into
// details.
const detail = new provproto.ErrorResourceInitFailed();
detail.setId(e.id);
detail.setProperties(structproto.Struct.fromJavaScript(e.properties || {}));
detail.addReasons(e.reasons || []);
const details = new anyproto.Any();
details.pack(detail.serializeBinary(), "pulumirpc.ErrorResourceInitFailed");
// Add details to metadata.
resp.addDetails(details);
// NOTE: `grpc-status-details-bin` is a magic field that allows us to send structured
// protobuf data as an error back through gRPC. This notion of details is a first-class in
// the Go gRPC implementation, and the nodejs implementation has not quite caught up to it,
// which is why it's cumbersome here.
metadata.add("grpc-status-details-bin", Buffer.from(resp.serializeBinary()));
}
return {
code: grpc.status.UNKNOWN,
message: e.message,
metadata: metadata,
};
}
export function main(args: string[]): void {
// The program requires a single argument: the address of the RPC endpoint for the engine. It
// optionally also takes a second argument, a reference back to the engine, but this may be missing.

View file

@ -115,6 +115,8 @@ message CreateRequest {
}
message CreateResponse {
// NOTE: The partial-update-error equivalent of this message is `ErrorResourceInitFailed`.
string id = 1; // the ID of the created resource.
google.protobuf.Struct properties = 2; // any properties that were computed during creation.
}
@ -131,6 +133,8 @@ message ReadResponse {
}
message UpdateRequest {
// NOTE: The partial-update-error equivalent of this message is `ErrorResourceInitFailed`.
string id = 1; // the ID of the resource to update.
string urn = 2; // the Pulumi URN for this resource.
google.protobuf.Struct olds = 3; // the old values of provider inputs for the resource to update.