Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect kubectl version during kubeconfig generation and avoid known non-working configurations #5288

Merged
merged 8 commits into from
May 24, 2022
48 changes: 48 additions & 0 deletions pkg/utils/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,19 @@ func AppendAuthenticator(config *clientcmdapi.Config, clusterMeta *api.ClusterMe
args = append(args, "--region", clusterMeta.Region)
}
}
// If the alpha API version is selected, check the kubectl version
// If kubectl 1.24.0 or above is detected, override with the beta API version
// kubectl 1.24.0 removes the alpha API version, so it will never work
// Therefore as a best effort try the beta version even if it might not work
if execConfig.APIVersion == alphaAPIVersion {
if kubectlVersion := getKubectlVersion(); kubectlVersion != "" {
// Silently ignore errors because kubectl is not required to run eksctl
compareVersions, err := utils.CompareVersions(kubectlVersion, "1.24.0")
if err == nil && compareVersions >= 0 {
execConfig.APIVersion = betaAPIVersion
}
}
}
if roleARN != "" {
args = append(args, roleARNFlag, roleARN)
}
Expand Down Expand Up @@ -238,6 +251,41 @@ func getAWSIAMAuthenticatorVersion() (string, error) {
return parsedVersion.Version, nil
}

/* KubectlVersionFormat is the format used by kubectl version --format=json, example output:
{
"clientVersion": {
"major": "1",
"minor": "23",
"gitVersion": "v1.23.6",
"gitCommit": "ad3338546da947756e8a88aa6822e9c11e7eac22",
"gitTreeState": "archive",
"buildDate": "2022-04-29T06:39:16Z",
"goVersion": "go1.18.1",
"compiler": "gc",
"platform": "linux/amd64"
}
} */
type KubectlVersionData struct {
Version string `json:"gitVersion"`
Copy link

@jglick jglick May 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically more robust to compare against ${major}.${minor} rather than assuming a format for gitVersion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jglick I think it's a safe bet that kubernetes will continue to use semver for the foreseeable future. The only real worry I would have is if they drop the v prefix, but the code should deal with a prefix-less version correctly.

}

type KubectlVersionFormat struct {
ClientVersion KubectlVersionData `json:"clientVersion"`
}

func getKubectlVersion() string {
cmd := execCommand("kubectl", "version", "--client", "--output=json")
output, err := cmd.Output()
if err != nil {
return ""
}
var parsedVersion KubectlVersionFormat
if err := json.Unmarshal(output, &parsedVersion); err != nil {
return ""
}
return strings.TrimLeft(parsedVersion.ClientVersion.Version, "v")
}

func lockFileName(filePath string) string {
return filePath + ".eksctl.lock"
}
Expand Down
29 changes: 23 additions & 6 deletions pkg/utils/kubeconfig/kubeconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,42 +373,59 @@ var _ = Describe("Kubeconfig", func() {
})
It("writes the right api version if aws-iam-authenticator version is below 0.5.3", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command(filepath.Join("testdata", "aws-iam-authenticator"), `{"Version":"0.5.1","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
return exec.Command(filepath.Join("testdata", "fake-version"), `{"Version":"0.5.1","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSIAMAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1alpha1"))
})
It("writes the right api version if aws-iam-authenticator version is above 0.5.3", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command(filepath.Join("testdata", "aws-iam-authenticator"), `{"Version":"0.5.5","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
return exec.Command(filepath.Join("testdata", "fake-version"), `{"Version":"0.5.5","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSIAMAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1beta1"))
})
It("writes the right api version if aws-iam-authenticator version equals 0.5.3", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command(filepath.Join("testdata", "aws-iam-authenticator"), `{"Version":"0.5.3","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
return exec.Command(filepath.Join("testdata", "fake-version"), `{"Version":"0.5.3","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSIAMAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1beta1"))
})
It("defaults to alpha1 if we fail to detect aws-iam-authenticator version", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command(filepath.Join("testdata", "aws-iam-authenticator"), "fail")
return exec.Command(filepath.Join("testdata", "fake-version"), "fail")
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSIAMAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1alpha1"))
})
It("defaults to alpha1 if we fail to parse the output", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command(filepath.Join("testdata", "aws-iam-authenticator"), "not-json-output")
return exec.Command(filepath.Join("testdata", "fake-version"), "not-json-output")
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSIAMAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1alpha1"))
})
It("defaults to alpha1 if we can't parse the version because it's a dev version", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command(filepath.Join("testdata", "aws-iam-authenticator"), `{"Version":"git-85e50980","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
return exec.Command(filepath.Join("testdata", "fake-version"), `{"Version":"git-85e50980","Commit":"85e50980d9d916ae95882176c18f14ae145f916f"}`)
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSIAMAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1alpha1"))
})
It("defaults to beta1 if we detect kubectl 1.24.0 or above", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command(filepath.Join("testdata", "fake-version"), `{"clientVersion": {"gitVersion": "v1.24.0"}}`)
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSEKSAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1beta1"))
})
It("doesn't default to beta1 if we detect kubectl 1.23.0 or below", func() {
kubeconfig.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
if name == "kubectl" {
return exec.Command(filepath.Join("testdata", "fake-version"), `{"clientVersion": {"gitVersion": "v1.23.6"}}`)
}
return exec.Command(filepath.Join("testdata", "fake-version"), "fail")
})
kubeconfig.AppendAuthenticator(config, clusterMeta, kubeconfig.AWSIAMAuthenticator, "", "")
Expect(config.AuthInfos["test"].Exec.APIVersion).To(Equal("client.authentication.k8s.io/v1alpha1"))
Expand Down