storage/kubernetes: fix conflict error detection in TRP creation
PR #815 fixed the Kubernetes storage implementation by correctly returning storage.ErrAlreadyExists on POST conflicts. This caused a regression in TPR creation (#822) when some, but not all, of the resources already existed. E.g. for users upgrading from old versions of dex. Fixes #822
This commit is contained in:
		| @@ -3,7 +3,6 @@ package kubernetes | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -42,15 +41,19 @@ type Config struct { | |||||||
|  |  | ||||||
| // Open returns a storage using Kubernetes third party resource. | // Open returns a storage using Kubernetes third party resource. | ||||||
| func (c *Config) Open(logger logrus.FieldLogger) (storage.Storage, error) { | func (c *Config) Open(logger logrus.FieldLogger) (storage.Storage, error) { | ||||||
| 	cli, err := c.open(logger) | 	cli, err := c.open(logger, false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return cli, nil | 	return cli, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // open returns a client with no garbage collection. | // open returns a kubernetes client, initializing the third party resources used | ||||||
| func (c *Config) open(logger logrus.FieldLogger) (*client, error) { | // by dex. | ||||||
|  | // | ||||||
|  | // errOnTPRs controls if errors creating the resources cause this method to return | ||||||
|  | // immediately (used during testing), or if the client will asynchronously retry. | ||||||
|  | func (c *Config) open(logger logrus.FieldLogger, errOnTPRs bool) (*client, error) { | ||||||
| 	if c.InCluster && (c.KubeConfigFile != "") { | 	if c.InCluster && (c.KubeConfigFile != "") { | ||||||
| 		return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigFile'") | 		return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigFile'") | ||||||
| 	} | 	} | ||||||
| @@ -80,16 +83,18 @@ func (c *Config) open(logger logrus.FieldLogger) (*client, error) { | |||||||
|  |  | ||||||
| 	ctx, cancel := context.WithCancel(context.Background()) | 	ctx, cancel := context.WithCancel(context.Background()) | ||||||
|  |  | ||||||
| 	// Try to synchronously create the third party resources once. This doesn't mean | 	if !cli.createThirdPartyResources() { | ||||||
| 	// they'll immediately be available, but ensures that the client will actually try | 		if errOnTPRs { | ||||||
| 	// once. | 			return nil, fmt.Errorf("failed creating third party resources") | ||||||
| 	if err := cli.createThirdPartyResources(); err != nil { | 		} | ||||||
|  |  | ||||||
|  | 		// Try to synchronously create the third party resources once. This doesn't mean | ||||||
|  | 		// they'll immediately be available, but ensures that the client will actually try | ||||||
|  | 		// once. | ||||||
| 		logger.Errorf("failed creating third party resources: %v", err) | 		logger.Errorf("failed creating third party resources: %v", err) | ||||||
| 		go func() { | 		go func() { | ||||||
| 			for { | 			for { | ||||||
| 				if err := cli.createThirdPartyResources(); err != nil { | 				if cli.createThirdPartyResources() { | ||||||
| 					logger.Errorf("failed creating third party resources: %v", err) |  | ||||||
| 				} else { |  | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| @@ -108,27 +113,33 @@ func (c *Config) open(logger logrus.FieldLogger) (*client, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // createThirdPartyResources attempts to create the third party resources dex | // createThirdPartyResources attempts to create the third party resources dex | ||||||
| // requires or identifies that they're already enabled. | // requires or identifies that they're already enabled. It logs all errors, | ||||||
|  | // returning true if the third party resources were created successfully. | ||||||
| // | // | ||||||
| // Creating a third party resource does not mean that they'll be immediately available. | // Creating a third party resource does not mean that they'll be immediately available. | ||||||
| // | // | ||||||
| // TODO(ericchiang): Provide an option to wait for the third party resources | // TODO(ericchiang): Provide an option to wait for the third party resources | ||||||
| // to actually be available. | // to actually be available. | ||||||
| func (cli *client) createThirdPartyResources() error { | func (cli *client) createThirdPartyResources() (ok bool) { | ||||||
|  | 	ok = true | ||||||
| 	for _, r := range thirdPartyResources { | 	for _, r := range thirdPartyResources { | ||||||
| 		err := cli.postResource("extensions/v1beta1", "", "thirdpartyresources", r) | 		err := cli.postResource("extensions/v1beta1", "", "thirdpartyresources", r) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			if e, ok := err.(httpError); ok { | 			switch err { | ||||||
| 				if e.StatusCode() == http.StatusConflict { | 			case storage.ErrAlreadyExists: | ||||||
| 					cli.logger.Errorf("third party resource already created %q", r.ObjectMeta.Name) | 				cli.logger.Errorf("third party resource already created %s", r.ObjectMeta.Name) | ||||||
| 					continue | 			case storage.ErrNotFound: | ||||||
| 				} | 				cli.logger.Errorf("third party resources not found, please enable API group extensions/v1beta1") | ||||||
|  | 				ok = false | ||||||
|  | 			default: | ||||||
|  | 				cli.logger.Errorf("creating third party resource %s: %v", r.ObjectMeta.Name, err) | ||||||
|  | 				ok = false | ||||||
| 			} | 			} | ||||||
| 			return err | 			continue | ||||||
| 		} | 		} | ||||||
| 		cli.logger.Errorf("create third party resource %q", r.ObjectMeta.Name) | 		cli.logger.Errorf("create third party resource %s", r.ObjectMeta.Name) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return ok | ||||||
| } | } | ||||||
|  |  | ||||||
| func (cli *client) Close() error { | func (cli *client) Close() error { | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ func loadClient(t *testing.T) *client { | |||||||
| 		Formatter: &logrus.TextFormatter{DisableColors: true}, | 		Formatter: &logrus.TextFormatter{DisableColors: true}, | ||||||
| 		Level:     logrus.DebugLevel, | 		Level:     logrus.DebugLevel, | ||||||
| 	} | 	} | ||||||
| 	s, err := config.open(logger) | 	s, err := config.open(logger, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user