Small cleanups and comments
This commit is contained in:
parent
1cbf8bdc40
commit
456deaf442
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue