package gcp import ( "context" "errors" "net/http" "time" "cloud.google.com/go/compute/apiv1/computepb" "google.golang.org/api/googleapi" "google.golang.org/api/iterator" "google.golang.org/protobuf/proto" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) var _ provider.Checker = (*Provider)(nil) // computeAddress is the endpoint the SDK talks to, shown only to humans. const computeAddress = "compute.googleapis.com" // managedFilter selects the instances this operator created. const managedFilter = "labels." + provider.LabelManaged + " = " + provider.LabelManagedYes // Check asks the project for one page holding at most one managed // instance. That exercises the credentials, the Compute API and the // compute.instances.list permission orphan GC depends on, in a single // request whatever the project's size. The target is the project: the // provider config names no zones. func (p *Provider) Check(ctx context.Context) []provider.TargetStatus { start := time.Now() err := p.api.Probe(ctx, &computepb.AggregatedListInstancesRequest{ Project: p.cfg.Project, Filter: proto.String(managedFilter), MaxResults: proto.Uint32(1), ReturnPartialSuccess: proto.Bool(true), }) ts := provider.TargetStatus{Name: p.cfg.Project, Address: computeAddress, Latency: time.Since(start)} if err != nil { logAPIError(p.logger(ctx), "check", err) ts.Reason = checkReason(ctx, err) ts.Message = err.Error() return []provider.TargetStatus{ts} } ts.Ready = true ts.Reason = provider.ReasonReachable return []provider.TargetStatus{ts} } // Probe fetches the first page of an aggregated list and discards it. The // SDK treats MaxResults as the page size, so AggregatedList, which drains // the iterator, would still walk every page of the project. func (r *realInstances) Probe(ctx context.Context, req *computepb.AggregatedListInstancesRequest) error { _, err := r.client.AggregatedList(ctx, req).Next() if err == iterator.Done { return nil } return err } // checkReason maps a failed probe to a reason. A token that cannot be // obtained never reaches the API, so it has no status code and lands on // Unreachable, with the detail in the message. func checkReason(ctx context.Context, err error) string { var gerr *googleapi.Error switch { case ctx.Err() != nil, errors.Is(err, context.DeadlineExceeded): return provider.ReasonTimeout case !errors.As(err, &gerr): return provider.ReasonUnreachable case gerr.Code == http.StatusUnauthorized: return provider.ReasonUnauthorized case gerr.Code == http.StatusForbidden: return provider.ReasonForbidden default: return provider.ReasonAPIError } }