Skip to content

Commit

Permalink
return partial errors
Browse files Browse the repository at this point in the history
  • Loading branch information
dashpole committed May 24, 2022
1 parent 98cbf36 commit 441bfe1
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 79 deletions.
153 changes: 77 additions & 76 deletions detectors/gcp/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import (
)

// NewDetector returns a resource detector which detects resource attributes on:
// * Google Compute Engine (GCE)
// * Google Kubernetes Engine (GKE)
// * Google App Engine (GAE)
// * Cloud Run
// * Cloud Functions
// * Google Compute Engine (GCE).
// * Google Kubernetes Engine (GKE).
// * Google App Engine (GAE).
// * Cloud Run.
// * Cloud Functions.
func NewDetector() resource.Detector {
return &detector{detector: gcp.NewDetector()}
}
Expand All @@ -46,90 +46,91 @@ func (d *detector) Detect(ctx context.Context) (*resource.Resource, error) {
if !metadata.OnGCE() {
return nil, nil
}
projectID, err := d.detector.ProjectID()
if err != nil {
return nil, err
}
attributes := []attribute.KeyValue{semconv.CloudProviderGCP, semconv.CloudAccountIDKey.String(projectID)}
b := &resourceBuilder{}
b.attrs = append(b.attrs, semconv.CloudProviderGCP)
b.add(semconv.CloudAccountIDKey, d.detector.ProjectID)

switch d.detector.CloudPlatform() {
case gcp.GKE:
attributes = append(attributes, semconv.CloudPlatformGCPKubernetesEngine)
v, locType, err := d.detector.GKEAvailabilityZoneOrRegion()
if err != nil {
return nil, err
}
switch locType {
case gcp.Zone:
attributes = append(attributes, semconv.CloudAvailabilityZoneKey.String(v))
case gcp.Region:
attributes = append(attributes, semconv.CloudRegionKey.String(v))
default:
return nil, fmt.Errorf("location must be zone or region. Got %v", locType)
}
return detectWithFuncs(attributes, map[attribute.Key]detectionFunc{
semconv.K8SClusterNameKey: d.detector.GKEClusterName,
semconv.HostIDKey: d.detector.GKEHostID,
semconv.HostNameKey: d.detector.GKEHostName,
})
b.attrs = append(b.attrs, semconv.CloudPlatformGCPKubernetesEngine)
b.addZoneOrRegion(d.detector.GKEAvailabilityZoneOrRegion)
b.add(semconv.K8SClusterNameKey, d.detector.GKEClusterName)
b.add(semconv.HostIDKey, d.detector.GKEHostID)
b.add(semconv.HostNameKey, d.detector.GKEHostName)
case gcp.CloudRun:
attributes = append(attributes, semconv.CloudPlatformGCPCloudRun)
return detectWithFuncs(attributes, map[attribute.Key]detectionFunc{
semconv.FaaSNameKey: d.detector.FaaSName,
semconv.FaaSVersionKey: d.detector.FaaSVersion,
semconv.FaaSIDKey: d.detector.FaaSID,
semconv.CloudRegionKey: d.detector.FaaSCloudRegion,
})
b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudRun)
b.add(semconv.FaaSNameKey, d.detector.FaaSName)
b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion)
b.add(semconv.FaaSIDKey, d.detector.FaaSID)
b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion)
case gcp.CloudFunctions:
attributes = append(attributes, semconv.CloudPlatformGCPCloudFunctions)
return detectWithFuncs(attributes, map[attribute.Key]detectionFunc{
semconv.FaaSNameKey: d.detector.FaaSName,
semconv.FaaSVersionKey: d.detector.FaaSVersion,
semconv.FaaSIDKey: d.detector.FaaSID,
semconv.CloudRegionKey: d.detector.FaaSCloudRegion,
})
b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudFunctions)
b.add(semconv.FaaSNameKey, d.detector.FaaSName)
b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion)
b.add(semconv.FaaSIDKey, d.detector.FaaSID)
b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion)
case gcp.AppEngine:
attributes = append(attributes, semconv.CloudPlatformGCPAppEngine)
zone, region, err := d.detector.AppEngineAvailabilityZoneAndRegion()
if err != nil {
return nil, err
}
attributes = append(attributes, semconv.CloudAvailabilityZoneKey.String(zone))
attributes = append(attributes, semconv.CloudRegionKey.String(region))
return detectWithFuncs(attributes, map[attribute.Key]detectionFunc{
semconv.FaaSNameKey: d.detector.AppEngineServiceName,
semconv.FaaSVersionKey: d.detector.AppEngineServiceVersion,
semconv.FaaSIDKey: d.detector.AppEngineServiceInstance,
})
b.attrs = append(b.attrs, semconv.CloudPlatformGCPAppEngine)
b.addZoneAndRegion(d.detector.AppEngineAvailabilityZoneAndRegion)
b.add(semconv.FaaSNameKey, d.detector.AppEngineServiceName)
b.add(semconv.FaaSVersionKey, d.detector.AppEngineServiceVersion)
b.add(semconv.FaaSIDKey, d.detector.AppEngineServiceInstance)
case gcp.GCE:
attributes = append(attributes, semconv.CloudPlatformGCPComputeEngine)
zone, region, err := d.detector.GCEAvailabilityZoneAndRegion()
if err != nil {
return nil, err
}
attributes = append(attributes, semconv.CloudAvailabilityZoneKey.String(zone))
attributes = append(attributes, semconv.CloudRegionKey.String(region))
return detectWithFuncs(attributes, map[attribute.Key]detectionFunc{
semconv.HostTypeKey: d.detector.GCEHostType,
semconv.HostIDKey: d.detector.GCEHostID,
semconv.HostNameKey: d.detector.GCEHostName,
})
b.attrs = append(b.attrs, semconv.CloudPlatformGCPComputeEngine)
b.addZoneAndRegion(d.detector.GCEAvailabilityZoneAndRegion)
b.add(semconv.HostTypeKey, d.detector.GCEHostType)
b.add(semconv.HostIDKey, d.detector.GCEHostID)
b.add(semconv.HostNameKey, d.detector.GCEHostName)
default:
// We don't support this platform yet, so just return with what we have
return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil
}
return b.build()
}

type detectionFunc func() (string, error)
// resourceBuilder simplifies constructing resources using GCP detection
// library functions.
type resourceBuilder struct {
errs []error
attrs []attribute.KeyValue
}

// detectWithFuncs is a helper to reduce the amount of error handling code
func detectWithFuncs(attributes []attribute.KeyValue, funcs map[attribute.Key]detectionFunc) (*resource.Resource, error) {
for key, detect := range funcs {
v, err := detect()
if err != nil {
return nil, err
func (r *resourceBuilder) add(key attribute.Key, detect func() (string, error)) {
if v, err := detect(); err == nil {
r.attrs = append(r.attrs, key.String(v))
} else {
r.errs = append(r.errs, err)
}
}

// zoneAndRegion functions are expected to return zone, region, err.
func (r *resourceBuilder) addZoneAndRegion(detect func() (string, string, error)) {
if zone, region, err := detect(); err == nil {
r.attrs = append(r.attrs, semconv.CloudAvailabilityZoneKey.String(zone))
r.attrs = append(r.attrs, semconv.CloudRegionKey.String(region))
} else {
r.errs = append(r.errs, err)
}
}

func (r *resourceBuilder) addZoneOrRegion(detect func() (string, gcp.LocationType, error)) {
if v, locType, err := detect(); err == nil {
switch locType {
case gcp.Zone:
r.attrs = append(r.attrs, semconv.CloudAvailabilityZoneKey.String(v))
case gcp.Region:
r.attrs = append(r.attrs, semconv.CloudRegionKey.String(v))
default:
r.errs = append(r.errs, fmt.Errorf("location must be zone or region. Got %v", locType))
}
attributes = append(attributes, key.String(v))
} else {
r.errs = append(r.errs, err)
}
}

func (r *resourceBuilder) build() (*resource.Resource, error) {
var err error
if len(r.errs) > 0 {
err = fmt.Errorf("%w: %s", resource.ErrPartialResource, r.errs)
}
return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil
return resource.NewWithAttributes(semconv.SchemaURL, r.attrs...), err
}
7 changes: 4 additions & 3 deletions detectors/gcp/detector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,20 +176,21 @@ func TestDetect(t *testing.T) {
{
desc: "error",
detector: &detector{detector: &fakeGCPDetector{
err: fmt.Errorf("Failed to get metadata"),
err: fmt.Errorf("failed to get metadata"),
}},
expectErr: true,
expectedResource: resource.NewWithAttributes(semconv.SchemaURL,
semconv.CloudProviderGCP,
),
},
} {
t.Run(tc.desc, func(t *testing.T) {

res, err := tc.detector.Detect(context.TODO())
if tc.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}

assert.Equal(t, tc.expectedResource, res, "Resource object returned is incorrect")
})
}
Expand Down

0 comments on commit 441bfe1

Please sign in to comment.