Skip to content

Commit 8b8d55b

Browse files
authored
Merge branch 'master' into unattended-flux-setup
2 parents 9f81430 + 31a19eb commit 8b8d55b

File tree

12 files changed

+312
-113
lines changed

12 files changed

+312
-113
lines changed

pkg/ctl/cmdutils/cmdutils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func AddUpdateAuthConfigMap(fs *pflag.FlagSet, updateAuthConfigMap *bool, descri
184184

185185
// AddCommonFlagsForKubeconfig adds common flags for controlling how output kubeconfig is written
186186
func AddCommonFlagsForKubeconfig(fs *pflag.FlagSet, outputPath, authenticatorRoleARN *string, setContext, autoPath *bool, exampleName string) {
187-
fs.StringVar(outputPath, "kubeconfig", kubeconfig.DefaultPath, "path to write kubeconfig (incompatible with --auto-kubeconfig)")
187+
fs.StringVar(outputPath, "kubeconfig", kubeconfig.DefaultPath(), "path to write kubeconfig (incompatible with --auto-kubeconfig)")
188188
fs.StringVar(authenticatorRoleARN, "authenticator-role-arn", "", "AWS IAM role to assume for authenticator")
189189
fs.BoolVar(setContext, "set-kubeconfig-context", true, "if true then current-context will be set in kubeconfig; if a context is already set then it will be overwritten")
190190
fs.BoolVar(autoPath, "auto-kubeconfig", false, fmt.Sprintf("save kubeconfig file by cluster name, e.g. %q", kubeconfig.AutoPath(exampleName)))

pkg/ctl/create/cluster.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func doCreateCluster(cmd *cmdutils.Cmd, ng *api.NodeGroup, params *cmdutils.Crea
133133
}
134134

135135
if params.AutoKubeconfigPath {
136-
if params.KubeconfigPath != kubeconfig.DefaultPath {
136+
if params.KubeconfigPath != kubeconfig.DefaultPath() {
137137
return fmt.Errorf("--kubeconfig and --auto-kubeconfig %s", cmdutils.IncompatibleFlags)
138138
}
139139
params.KubeconfigPath = kubeconfig.AutoPath(meta.Name)

pkg/ctl/utils/write_kubeconfig.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func doWriteKubeconfigCmd(cmd *cmdutils.Cmd, outputPath, roleARN string, setCont
6060
}
6161

6262
if autoPath {
63-
if outputPath != kubeconfig.DefaultPath {
63+
if outputPath != kubeconfig.DefaultPath() {
6464
return fmt.Errorf("--kubeconfig and --auto-kubeconfig %s", cmdutils.IncompatibleFlags)
6565
}
6666
outputPath = kubeconfig.AutoPath(cfg.Metadata.Name)

pkg/eks/client.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,19 @@ import (
2121

2222
// Client stores information about the client config
2323
type Client struct {
24-
Config *clientcmdapi.Config
25-
ContextName string
24+
Config *clientcmdapi.Config
2625

2726
rawConfig *restclient.Config
2827
}
2928

3029
// NewClient creates a new client config by embedding the STS token
3130
func (c *ClusterProvider) NewClient(spec *api.ClusterConfig) (*Client, error) {
32-
clientConfig, _, contextName := kubeconfig.New(spec, c.GetUsername(), "")
31+
clientConfig := kubeconfig.
32+
NewBuilder(spec.Metadata, spec.Status, c.GetUsername()).
33+
Build()
3334

3435
config := &Client{
35-
Config: clientConfig,
36-
ContextName: contextName,
36+
Config: clientConfig,
3737
}
3838

3939
return config.new(spec, c.Provider.STS())
@@ -73,7 +73,7 @@ func (c *Client) useEmbeddedToken(spec *api.ClusterConfig, stsclient stsiface.ST
7373
return errors.Wrap(err, "could not get token")
7474
}
7575

76-
c.Config.AuthInfos[c.ContextName].Token = tok.Token
76+
c.Config.AuthInfos[c.Config.CurrentContext].Token = tok.Token
7777
return nil
7878
}
7979

pkg/nodebootstrap/userdata.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ func addFilesAndScripts(config *cloudconfig.CloudConfig, files configFiles, scri
6767
}
6868

6969
func makeClientConfigData(spec *api.ClusterConfig, ng *api.NodeGroup, authenticatorCMD string) ([]byte, error) {
70-
clientConfig, _, _ := kubeconfig.New(spec, "kubelet", configDir+"ca.crt")
70+
clientConfig := kubeconfig.
71+
NewBuilder(spec.Metadata, spec.Status, "kubelet").
72+
UseCertificateAuthorityFile(configDir + "ca.crt").
73+
Build()
7174
kubeconfig.AppendAuthenticator(clientConfig, spec, authenticatorCMD, "", "")
7275
clientConfigData, err := clientcmd.Write(*clientConfig)
7376
if err != nil {

pkg/utils/kubeconfig/kubeconfig.go

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path"
7+
"path/filepath"
78
"strings"
89

910
"os/exec"
@@ -17,18 +18,25 @@ import (
1718
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
1819
)
1920

20-
// DefaultPath defines the default path
21-
var DefaultPath = clientcmd.RecommendedHomeFile
22-
2321
const (
2422
// AWSIAMAuthenticator defines the name of the AWS IAM authenticator
2523
AWSIAMAuthenticator = "aws-iam-authenticator"
2624
// HeptioAuthenticatorAWS defines the old name of AWS IAM authenticator
2725
HeptioAuthenticatorAWS = "heptio-authenticator-aws"
2826
// AWSEKSAuthenticator defines the recently added `aws eks get-token` command
2927
AWSEKSAuthenticator = "aws"
28+
// Shadowing the default kubeconfig path environment variable
29+
RecommendedConfigPathEnvVar = clientcmd.RecommendedConfigPathEnvVar
3030
)
3131

32+
// DefaultPath defines the default path
33+
func DefaultPath() string {
34+
if env := os.Getenv(RecommendedConfigPathEnvVar); len(env) > 0 {
35+
return env
36+
}
37+
return clientcmd.RecommendedHomeFile
38+
}
39+
3240
// AuthenticatorCommands returns all of authenticator commands
3341
func AuthenticatorCommands() []string {
3442
return []string{
@@ -38,22 +46,23 @@ func AuthenticatorCommands() []string {
3846
}
3947
}
4048

41-
// New creates Kubernetes client configuration for a given username
42-
// if certificateAuthorityPath is not empty, it is used instead of
43-
// embedded certificate-authority-data
44-
func New(spec *api.ClusterConfig, username, certificateAuthorityPath string) (*clientcmdapi.Config, string, string) {
45-
clusterName := spec.Metadata.String()
46-
contextName := fmt.Sprintf("%s@%s", username, clusterName)
49+
// ConfigBuilder can create a client-go clientcmd Config
50+
type ConfigBuilder struct {
51+
cluster clientcmdapi.Cluster
52+
clusterName string
53+
username string
54+
}
4755

48-
c := &clientcmdapi.Config{
56+
// Build creates the Config with the ConfigBuilder settings
57+
func (cb *ConfigBuilder) Build() *clientcmdapi.Config {
58+
contextName := fmt.Sprintf("%s@%s", cb.username, cb.clusterName)
59+
return &clientcmdapi.Config{
4960
Clusters: map[string]*clientcmdapi.Cluster{
50-
clusterName: {
51-
Server: spec.Status.Endpoint,
52-
},
61+
cb.clusterName: &cb.cluster,
5362
},
5463
Contexts: map[string]*clientcmdapi.Context{
5564
contextName: {
56-
Cluster: clusterName,
65+
Cluster: cb.clusterName,
5766
AuthInfo: contextName,
5867
},
5968
},
@@ -62,19 +71,32 @@ func New(spec *api.ClusterConfig, username, certificateAuthorityPath string) (*c
6271
},
6372
CurrentContext: contextName,
6473
}
74+
}
6575

66-
if certificateAuthorityPath == "" {
67-
c.Clusters[clusterName].CertificateAuthorityData = spec.Status.CertificateAuthorityData
68-
} else {
69-
c.Clusters[clusterName].CertificateAuthority = certificateAuthorityPath
76+
// NewBuilder returns a minimal ConfigBuilder
77+
func NewBuilder(metadata *api.ClusterMeta, status *api.ClusterStatus, username string) *ConfigBuilder {
78+
cluster := clientcmdapi.Cluster{
79+
Server: status.Endpoint,
80+
CertificateAuthorityData: status.CertificateAuthorityData,
7081
}
82+
return &ConfigBuilder{
83+
cluster: cluster,
84+
clusterName: metadata.String(),
85+
username: username,
86+
}
87+
}
7188

72-
return c, clusterName, contextName
89+
// UseCertificateAuthorityFile sets the config to use CA from file instead
90+
// of the CA as retrieved from EKS
91+
func (cb *ConfigBuilder) UseCertificateAuthorityFile(path string) *ConfigBuilder {
92+
cb.cluster.CertificateAuthority = path
93+
cb.cluster.CertificateAuthorityData = []byte{}
94+
return cb
7395
}
7496

7597
// NewForKubectl creates configuration for kubectl using a suitable authenticator
7698
func NewForKubectl(spec *api.ClusterConfig, username, roleARN, profile string) *clientcmdapi.Config {
77-
config, _, _ := New(spec, username, "")
99+
config := NewBuilder(spec.Metadata, spec.Status, username).Build()
78100
authenticator, found := LookupAuthenticator()
79101
if !found {
80102
// fall back to aws-iam-authenticator
@@ -139,21 +161,40 @@ func AppendAuthenticator(config *clientcmdapi.Config, spec *api.ClusterConfig, a
139161
}
140162
}
141163

164+
// ensureDirectory should probably be handled in flock
165+
func ensureDirectory(filePath string) error {
166+
dir := filepath.Dir(filePath)
167+
if _, err := os.Stat(dir); os.IsNotExist(err) {
168+
if err = os.MkdirAll(dir, 0755); err != nil {
169+
return err
170+
}
171+
}
172+
return nil
173+
}
174+
142175
func lockConfigFile(filePath string) error {
176+
// Make sure the directory exists, otherwise flock fails
177+
if err := ensureDirectory(filePath); err != nil {
178+
return err
179+
}
143180
flock := flock.New(filePath)
144181
err := flock.Lock()
145182
if err != nil {
146-
return errors.Wrap(err, "flock: failed to obtain exclusive lock existing kubeconfig file")
183+
return errors.Wrap(err, "flock: failed to obtain exclusive lock on kubeconfig file")
147184
}
148185

149186
return nil
150187
}
151188

152189
func unlockConfigFile(filePath string) error {
190+
// Make sure the directory exists, otherwise flock fails
191+
if err := ensureDirectory(filePath); err != nil {
192+
return err
193+
}
153194
flock := flock.New(filePath)
154195
err := flock.Unlock()
155196
if err != nil {
156-
return errors.Wrap(err, "flock: failed to release exclusive lock on existing kubeconfig file")
197+
return errors.Wrap(err, "flock: failed to release exclusive lock on kubeconfig file")
157198
}
158199

159200
return nil
@@ -199,7 +240,7 @@ func Write(path string, newConfig clientcmdapi.Config, setContext bool) (string,
199240

200241
func getConfigAccess(explicitPath string) clientcmd.ConfigAccess {
201242
pathOptions := clientcmd.NewDefaultPathOptions()
202-
if explicitPath != "" && explicitPath != DefaultPath {
243+
if explicitPath != "" && explicitPath != DefaultPath() {
203244
pathOptions.LoadingRules.ExplicitPath = explicitPath
204245
}
205246

@@ -275,7 +316,7 @@ func MaybeDeleteConfig(meta *api.ClusterMeta) {
275316
return
276317
}
277318

278-
configAccess := getConfigAccess(DefaultPath)
319+
configAccess := getConfigAccess(DefaultPath())
279320
defaultFilename := configAccess.GetDefaultFilename()
280321
err := lockConfigFile(defaultFilename)
281322
if err != nil {
@@ -290,7 +331,7 @@ func MaybeDeleteConfig(meta *api.ClusterMeta) {
290331

291332
config, err := configAccess.GetStartingConfig()
292333
if err != nil {
293-
logger.Debug("error reading kubeconfig file %q: %s", DefaultPath, err.Error())
334+
logger.Debug("error reading kubeconfig file %q: %s", DefaultPath(), err.Error())
294335
return
295336
}
296337

@@ -299,7 +340,7 @@ func MaybeDeleteConfig(meta *api.ClusterMeta) {
299340
}
300341

301342
if err := clientcmd.ModifyConfig(configAccess, *config, true); err != nil {
302-
logger.Debug("ignoring error while failing to update config file %q: %s", DefaultPath, err.Error())
343+
logger.Debug("ignoring error while failing to update config file %q: %s", DefaultPath(), err.Error())
303344
} else {
304345
logger.Success("kubeconfig has been updated")
305346
}

pkg/utils/kubeconfig/kubeconfig_test.go

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package kubeconfig_test
33
import (
44
"io/ioutil"
55
"os"
6+
"path"
67
"sync"
78

89
eksctlapi "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
@@ -70,6 +71,13 @@ var _ = Describe("Kubeconfig", func() {
7071
Expect(filename).To(BeEmpty())
7172
})
7273

74+
It("creating new Kubeconfig in non-existent directory", func() {
75+
tempDir, _ := ioutil.TempDir("", "")
76+
filename, err := kubeconfig.Write(path.Join(tempDir, "nonexistentdir", "kubeconfig"), testConfig, false)
77+
Expect(err).To(BeNil())
78+
Expect(filename).ToNot(BeEmpty())
79+
})
80+
7381
It("sets new Kubeconfig context", func() {
7482
testConfigContext := testConfig
7583
testConfigContext.CurrentContext = "test-context"
@@ -138,6 +146,20 @@ var _ = Describe("Kubeconfig", func() {
138146
Expect(readConfig.CurrentContext).To(Equal("minikube"))
139147
})
140148

149+
var (
150+
kubeconfigPathToRestore string
151+
hasKubeconfigPath bool
152+
)
153+
154+
ChangeKubeconfig := func() {
155+
if _, err := os.Stat(configFile.Name()); os.IsNotExist(err) {
156+
GinkgoT().Fatal(err)
157+
}
158+
159+
kubeconfigPathToRestore, hasKubeconfigPath = os.LookupEnv("KUBECONFIG")
160+
os.Setenv("KUBECONFIG", configFile.Name())
161+
}
162+
141163
Context("delete config", func() {
142164
// Default cluster name is 'foo' and region is 'us-west-2'
143165
var apiClusterConfigSample = eksctlapi.ClusterConfig{
@@ -185,8 +207,6 @@ var _ = Describe("Kubeconfig", func() {
185207
twoClustersAsBytes []byte
186208
oneClusterWithoutContextAsBytes []byte
187209
oneClusterWithStaleContextAsBytes []byte
188-
kubeconfigPathToRestore string
189-
hasKubeconfigPath bool
190210
)
191211

192212
// Returns an ClusterConfig with a cluster name equal to the provided clusterName.
@@ -196,15 +216,6 @@ var _ = Describe("Kubeconfig", func() {
196216
return &apiClusterConfig
197217
}
198218

199-
ChangeKubeconfig := func() {
200-
if _, err := os.Stat(configFile.Name()); os.IsNotExist(err) {
201-
GinkgoT().Fatal(err)
202-
}
203-
204-
kubeconfigPathToRestore, hasKubeconfigPath = os.LookupEnv("KUBECONFIG")
205-
os.Setenv("KUBECONFIG", configFile.Name())
206-
}
207-
208219
RestoreKubeconfig := func() {
209220
if hasKubeconfigPath {
210221
os.Setenv("KUBECONFIG", kubeconfigPathToRestore)
@@ -297,28 +308,45 @@ var _ = Describe("Kubeconfig", func() {
297308
Expect(err).To(BeNil())
298309
Expect(configFileAsBytes).To(MatchYAML(twoClustersAsBytes), "Should not change")
299310
})
311+
})
312+
313+
It("safely handles concurrent read-modify-write operations", func() {
314+
var (
315+
oneClusterAsBytes []byte
316+
twoClustersAsBytes []byte
317+
)
318+
ChangeKubeconfig()
319+
320+
var err error
321+
322+
if oneClusterAsBytes, err = ioutil.ReadFile("testdata/one_cluster.golden"); err != nil {
323+
GinkgoT().Fatalf("failed reading .golden: %v", err)
324+
}
325+
326+
if twoClustersAsBytes, err = ioutil.ReadFile("testdata/two_clusters.golden"); err != nil {
327+
GinkgoT().Fatalf("failed reading .golden: %v", err)
328+
}
300329

301-
It("safely handles concurrent read-modify-write operations", func() {
302-
var wg sync.WaitGroup
303-
multiplier := 3
304-
iters := 100
305-
for i := 0; i < multiplier; i++ {
306-
for k := 0; k < iters; k++ {
307-
wg.Add(2)
308-
go func() {
309-
defer wg.Done()
310-
_, err := configFile.Write(oneClusterAsBytes)
311-
Expect(err).To(BeNil())
312-
}()
313-
go func() {
314-
defer wg.Done()
315-
_, err := configFile.Write(twoClustersAsBytes)
316-
Expect(err).To(BeNil())
317-
}()
318-
}
330+
var wg sync.WaitGroup
331+
multiplier := 3
332+
iters := 100
333+
for i := 0; i < multiplier; i++ {
334+
for k := 0; k < iters; k++ {
335+
wg.Add(2)
336+
go func() {
337+
defer wg.Done()
338+
_, err := configFile.Write(oneClusterAsBytes)
339+
Expect(err).To(BeNil())
340+
}()
341+
go func() {
342+
defer wg.Done()
343+
_, err := configFile.Write(twoClustersAsBytes)
344+
Expect(err).To(BeNil())
345+
}()
319346
}
347+
}
348+
349+
wg.Wait()
320350

321-
wg.Wait()
322-
})
323351
})
324352
})

0 commit comments

Comments
 (0)