Skip to content

Instantly share code, notes, and snippets.

@pgavlin
Created January 13, 2020 22:44
Show Gist options
  • Save pgavlin/35c55c320d9904d04aa4674ff3c9a615 to your computer and use it in GitHub Desktop.
Save pgavlin/35c55c320d9904d04aa4674ff3c9a615 to your computer and use it in GitHub Desktop.
diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go
index 621d693..6375fa0 100644
--- a/pkg/provider/provider.go
+++ b/pkg/provider/provider.go
@@ -19,11 +19,13 @@ import (
"context"
"encoding/json"
"fmt"
+ "net"
"net/url"
"os"
"reflect"
"strings"
"sync"
+ "syscall"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
@@ -105,6 +107,8 @@ type kubeProvider struct {
enableDryRun bool
suppressDeprecationWarnings bool
enableSecrets bool
+ configError error
+ clusterUnreachable bool
config *rest.Config // Cluster config, e.g., through $KUBECONFIG file.
@@ -364,7 +368,12 @@ func (k *kubeProvider) Configure(_ context.Context, req *pulumirpc.ConfigureRequ
k.k8sVersion = cluster.GetServerVersion(cs.DiscoveryClientCached)
if _, err = k.getResources(); err != nil {
- return nil, fmt.Errorf("unable to load schema information from the API server: %v", err)
+ wrappedErr := fmt.Errorf("unable to load schema information from the API server: %v", err)
+ if !isClusterUnreachableError(err) {
+ return nil, wrappedErr
+ }
+ k.configError = wrappedErr
+ k.clusterUnreachable = true
}
return &pulumirpc.ConfigureResponse{
@@ -694,6 +703,10 @@ func (k *kubeProvider) StreamInvoke(
// required for correctness, violations thereof can negatively impact the end-user experience, as
// the provider inputs are using for detecting and rendering diffs.
func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
+ if k.configError != nil {
+ return nil, k.configError
+ }
+
//
// Behavior as of v0.12.x: We take two inputs:
//
@@ -899,6 +912,10 @@ func (k *kubeProvider) Check(ctx context.Context, req *pulumirpc.CheckRequest) (
func (k *kubeProvider) Diff(
ctx context.Context, req *pulumirpc.DiffRequest,
) (*pulumirpc.DiffResponse, error) {
+ if k.configError != nil {
+ return nil, k.configError
+ }
+
//
// Behavior as of v0.12.x: We take 2 inputs:
//
@@ -1092,6 +1109,10 @@ func (k *kubeProvider) Diff(
func (k *kubeProvider) Create(
ctx context.Context, req *pulumirpc.CreateRequest,
) (*pulumirpc.CreateResponse, error) {
+ if k.configError != nil {
+ return nil, k.configError
+ }
+
//
// Behavior as of v0.12.x: We take 1 input:
//
@@ -1231,6 +1252,15 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p
label := fmt.Sprintf("%s.Read(%s)", k.label(), urn)
glog.V(9).Infof("%s executing", label)
+ // If the cluster is unreachable, consider the resource deleted and inform the user.
+ if k.clusterUnreachable {
+ _ = k.host.Log(ctx, diag.Warning, urn, fmt.Sprintf("cluster unreachable: %v", k.configError))
+ return deleteResponse, nil
+ }
+ if k.configError != nil {
+ return nil, k.configError
+ }
+
// Obtain new properties, create a Kubernetes `unstructured.Unstructured` that we can pass to the
// validation routines.
oldState, err := plugin.UnmarshalProperties(req.GetProperties(), plugin.MarshalOptions{
@@ -1377,6 +1407,10 @@ func (k *kubeProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*p
func (k *kubeProvider) Update(
ctx context.Context, req *pulumirpc.UpdateRequest,
) (*pulumirpc.UpdateResponse, error) {
+ if k.configError != nil {
+ return nil, k.configError
+ }
+
//
// Behavior as of v0.12.x: We take 2 inputs:
//
@@ -1546,6 +1580,10 @@ func (k *kubeProvider) Update(
func (k *kubeProvider) Delete(
ctx context.Context, req *pulumirpc.DeleteRequest,
) (*pbempty.Empty, error) {
+ if k.configError != nil {
+ return nil, k.configError
+ }
+
urn := resource.URN(req.GetUrn())
label := fmt.Sprintf("%s.Delete(%s)", k.label(), urn)
glog.V(9).Infof("%s executing", label)
@@ -2248,3 +2286,26 @@ func annotateSecrets(outs, ins resource.PropertyMap) {
}
}
}
+
+func isClusterUnreachableError(err error) bool {
+ if urlErr, ok := err.(*url.Error); ok {
+ err = urlErr.Err
+ }
+ if opErr, ok := err.(*net.OpError); ok {
+ err = opErr.Err
+ }
+ if dnsErr, ok := err.(*net.DNSError); ok {
+ // Consider "host not found" errors as "cluster unreachable".
+ return dnsErr.IsNotFound
+ }
+ if osErr, ok := err.(*os.SyscallError); ok {
+ err = osErr.Err
+ }
+ if errno, ok := err.(syscall.Errno); ok {
+ switch errno {
+ case syscall.ECONNABORTED, syscall.ECONNREFUSED, syscall.ECONNRESET, syscall.EHOSTDOWN, syscall.EHOSTUNREACH:
+ return true
+ }
+ }
+ return false
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment