Skip to content

Commit e6fdca8

Browse files
committed
feat: add kubernetes plugin for kubectl, helm, stern, k9s
Add a new kubernetes plugin that provisions kubeconfig files for Kubernetes CLI tools. The plugin supports: - kubectl: Kubernetes command-line tool - helm: Kubernetes package manager - stern: Multi-pod log tailing - k9s: Terminal UI for Kubernetes The plugin uses file-based provisioning to create a temporary kubeconfig file and sets the KUBECONFIG environment variable, allowing seamless authentication with Kubernetes clusters. Credential storage uses the existing "Credential" field type to store the complete kubeconfig YAML content. An importer is included to detect existing kubeconfig files at ~/.kube/config.
1 parent 49810df commit e6fdca8

8 files changed

Lines changed: 248 additions & 0 deletions

File tree

plugins/kubernetes/helm.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package kubernetes
2+
3+
import (
4+
"github.com/1Password/shell-plugins/sdk"
5+
"github.com/1Password/shell-plugins/sdk/needsauth"
6+
"github.com/1Password/shell-plugins/sdk/schema"
7+
)
8+
9+
func HelmCLI() schema.Executable {
10+
return schema.Executable{
11+
Name: "Helm",
12+
Runs: []string{"helm"},
13+
DocsURL: sdk.URL("https://helm.sh/docs/"),
14+
NeedsAuth: needsauth.IfAll(
15+
needsauth.NotForHelpOrVersion(),
16+
needsauth.NotWithoutArgs(),
17+
),
18+
Uses: []schema.CredentialUsage{
19+
{
20+
Name: sdk.CredentialName("Kubeconfig"),
21+
},
22+
},
23+
}
24+
}

plugins/kubernetes/k9s.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package kubernetes
2+
3+
import (
4+
"github.com/1Password/shell-plugins/sdk"
5+
"github.com/1Password/shell-plugins/sdk/needsauth"
6+
"github.com/1Password/shell-plugins/sdk/schema"
7+
)
8+
9+
func K9sCLI() schema.Executable {
10+
return schema.Executable{
11+
Name: "k9s",
12+
Runs: []string{"k9s"},
13+
DocsURL: sdk.URL("https://k9scli.io/"),
14+
NeedsAuth: needsauth.IfAll(
15+
needsauth.NotForHelpOrVersion(),
16+
),
17+
Uses: []schema.CredentialUsage{
18+
{
19+
Name: sdk.CredentialName("Kubeconfig"),
20+
},
21+
},
22+
}
23+
}

plugins/kubernetes/kubeconfig.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package kubernetes
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
7+
"github.com/1Password/shell-plugins/sdk"
8+
"github.com/1Password/shell-plugins/sdk/importer"
9+
"github.com/1Password/shell-plugins/sdk/provision"
10+
"github.com/1Password/shell-plugins/sdk/schema"
11+
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
12+
)
13+
14+
func Kubeconfig() schema.CredentialType {
15+
return schema.CredentialType{
16+
Name: sdk.CredentialName("Kubeconfig"),
17+
DocsURL: sdk.URL("https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/"),
18+
Fields: []schema.CredentialField{
19+
{
20+
Name: fieldname.Credential,
21+
MarkdownDescription: "Base64-encoded kubeconfig YAML file contents.",
22+
Secret: true,
23+
},
24+
},
25+
DefaultProvisioner: provision.TempFile(
26+
kubeconfigFileContents,
27+
provision.Filename("config"),
28+
provision.SetPathAsEnvVar("KUBECONFIG"),
29+
),
30+
Importer: importer.TryAll(
31+
TryKubeconfigFile(),
32+
),
33+
}
34+
}
35+
36+
func kubeconfigFileContents(in sdk.ProvisionInput) ([]byte, error) {
37+
encoded := in.ItemFields[fieldname.Credential]
38+
decoded, err := base64.StdEncoding.DecodeString(encoded)
39+
if err != nil {
40+
return nil, err
41+
}
42+
return decoded, nil
43+
}
44+
45+
func TryKubeconfigFile() sdk.Importer {
46+
return importer.TryFile("~/.kube/config", func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) {
47+
// Import existing kubeconfig as base64-encoded
48+
encoded := base64.StdEncoding.EncodeToString(contents)
49+
out.AddCandidate(sdk.ImportCandidate{
50+
Fields: map[sdk.FieldName]string{
51+
fieldname.Credential: encoded,
52+
},
53+
NameHint: importer.SanitizeNameHint("default"),
54+
})
55+
})
56+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package kubernetes
2+
3+
import (
4+
"encoding/base64"
5+
"testing"
6+
7+
"github.com/1Password/shell-plugins/sdk"
8+
"github.com/1Password/shell-plugins/sdk/plugintest"
9+
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
10+
)
11+
12+
func TestKubeconfigProvisioner(t *testing.T) {
13+
rawConfig := plugintest.LoadFixture(t, "config")
14+
encodedConfig := base64.StdEncoding.EncodeToString([]byte(rawConfig))
15+
16+
plugintest.TestProvisioner(t, Kubeconfig().DefaultProvisioner, map[string]plugintest.ProvisionCase{
17+
"default": {
18+
ItemFields: map[sdk.FieldName]string{
19+
fieldname.Credential: encodedConfig,
20+
},
21+
ExpectedOutput: sdk.ProvisionOutput{
22+
Environment: map[string]string{
23+
"KUBECONFIG": "/tmp/config",
24+
},
25+
Files: map[string]sdk.OutputFile{
26+
"/tmp/config": {
27+
Contents: []byte(rawConfig),
28+
},
29+
},
30+
},
31+
},
32+
})
33+
}
34+
35+
func TestKubeconfigImporter(t *testing.T) {
36+
rawConfig := plugintest.LoadFixture(t, "config")
37+
encodedConfig := base64.StdEncoding.EncodeToString([]byte(rawConfig))
38+
39+
plugintest.TestImporter(t, Kubeconfig().Importer, map[string]plugintest.ImportCase{
40+
"kubeconfig file": {
41+
Files: map[string]string{
42+
"~/.kube/config": rawConfig,
43+
},
44+
ExpectedCandidates: []sdk.ImportCandidate{
45+
{
46+
Fields: map[sdk.FieldName]string{
47+
fieldname.Credential: encodedConfig,
48+
},
49+
NameHint: "",
50+
},
51+
},
52+
},
53+
})
54+
}

plugins/kubernetes/kubectl.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package kubernetes
2+
3+
import (
4+
"github.com/1Password/shell-plugins/sdk"
5+
"github.com/1Password/shell-plugins/sdk/needsauth"
6+
"github.com/1Password/shell-plugins/sdk/schema"
7+
)
8+
9+
func KubectlCLI() schema.Executable {
10+
return schema.Executable{
11+
Name: "kubectl",
12+
Runs: []string{"kubectl"},
13+
DocsURL: sdk.URL("https://kubernetes.io/docs/reference/kubectl/"),
14+
NeedsAuth: needsauth.IfAll(
15+
needsauth.NotForHelpOrVersion(),
16+
needsauth.NotWithoutArgs(),
17+
),
18+
Uses: []schema.CredentialUsage{
19+
{
20+
Name: sdk.CredentialName("Kubeconfig"),
21+
},
22+
},
23+
}
24+
}

plugins/kubernetes/plugin.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package kubernetes
2+
3+
import (
4+
"github.com/1Password/shell-plugins/sdk"
5+
"github.com/1Password/shell-plugins/sdk/schema"
6+
)
7+
8+
func New() schema.Plugin {
9+
return schema.Plugin{
10+
Name: "kubernetes",
11+
Platform: schema.PlatformInfo{
12+
Name: "Kubernetes",
13+
Homepage: sdk.URL("https://kubernetes.io"),
14+
},
15+
Credentials: []schema.CredentialType{
16+
Kubeconfig(),
17+
},
18+
Executables: []schema.Executable{
19+
KubectlCLI(),
20+
HelmCLI(),
21+
SternCLI(),
22+
K9sCLI(),
23+
},
24+
}
25+
}

plugins/kubernetes/stern.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package kubernetes
2+
3+
import (
4+
"github.com/1Password/shell-plugins/sdk"
5+
"github.com/1Password/shell-plugins/sdk/needsauth"
6+
"github.com/1Password/shell-plugins/sdk/schema"
7+
)
8+
9+
func SternCLI() schema.Executable {
10+
return schema.Executable{
11+
Name: "stern",
12+
Runs: []string{"stern"},
13+
DocsURL: sdk.URL("https://github.com/stern/stern"),
14+
NeedsAuth: needsauth.IfAll(
15+
needsauth.NotForHelpOrVersion(),
16+
),
17+
Uses: []schema.CredentialUsage{
18+
{
19+
Name: sdk.CredentialName("Kubeconfig"),
20+
},
21+
},
22+
}
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: v1
2+
kind: Config
3+
clusters:
4+
- cluster:
5+
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTURBeU16TTJOREV3SGhjTk1qTXhNVEU1TVRBeU56SXhXaGNOTXpNeE1URTJNVEF5TnpJeApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTURBeU16TTJOREV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMUZiY29ycjhBQ1NkcmNqQk5xQ3JSa0M2Y2tMYmZUQ0NSSUN2UndhN2IKUjZiNUxRZHZuOXZVYkhiVHN4Z3FJQ2pYYjZRTEtIRTRrQXlQQ3BDNnBPUEpvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWVlUllndjZBbGFCNVc0dDV0Rm9ZCm9OSHVkVDB3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUk0TlQyR3hxNHYwZExHRlFkNkdtbHdaZ0dBckVZRWoKL1FkeWZsOU12QTZyQWlFQWljVjdiVzlqcVJwNjh4cHdJeDlYak9OeUt2V3dZMVdQVWYzQU1kQTd6T2s9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
6+
server: https://10.0.0.1:6443
7+
name: default
8+
contexts:
9+
- context:
10+
cluster: default
11+
user: default
12+
name: default
13+
current-context: default
14+
preferences: {}
15+
users:
16+
- name: default
17+
user:
18+
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrVENDQVRlZ0F3SUJBZ0lJVHVNdTNtNE8waTh3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOekF3TWpNek5qUXhNQjRYRFRJek1URXhPVEV3TWpjeU1Wb1hEVEkwTVRFeApPREV3TWpjeU1Wb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJNL0hvRHZUZmg2dEtoVVUKM3lHZHBrU05EWVhGWjBYYjVVMHRKRzh4TlFiQmEzaDhRQjJMMVN0MDVNRlQ2dEhxUWpsUkJ6cG1qRnJ2L1haQQp1RUl4WmRlalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUmYwYnh0NTVSdlE4M3pxMGxrdnRXU28zYUNDREFLQmdncWhrak9QUVFEQWdOSUFEQkYKQWlFQXo1K2txTWJpMjJKMW1LNGFldlVYUVFGVm9hSXFWRFBzTjZHcW1iZGxaRWtDSUJqeWVxc0tkL0VxdFBXbgpaSXRCL21STC9WdDRHRUhJbGpUckxLNytlN2ZOCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTURBeU16TTJOREV3SGhjTk1qTXhNVEU1TVRBeU56SXhXaGNOTXpNeE1URTJNVEF5TnpJeApXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTURBeU16TTJOREV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSMU9ucCsxUnpLYVVVN0lJNTVhTXZJRUFXeE9udmNqd0E0NnRZSDQ3amkKb0dIVFViTHJXc3ZxMHFZeEpJZlkzRmdZekZYMVlkLzhBbVJxWGJrTldkUW9vMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVVg5RzhiZWVVYjBQTjg2dEpaTC9WCmtxTjJnZ2d3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQUxoSER3UlllVXBoS0ZSOURKZ2EycjlLRlBmQ2dLSnUKd0JOK2ptOW4wdm1YQWlBenM5Q0hDN1F5aElFSnNmVjVLaGF1MHhqQ1pYdEJpblQ5aEt4d0RGMTJGUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
19+
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IUUNBUUVFSUFTbE5FV3lPRWRuSWxYVWpRdG1Pc1cwWjdwYWxpTUlKdENMcmhKSGZRYlBvQWNHQlN1QkJBQUsKb1VRRFFnQUV6OGVnTzlOK0hxMHFGUlRmSVoybVJJME5oY1ZuUmR2bFRTMGtieksxQnNGcmVIeEFIWXZWSzNUawp3VlBxMGVwQ09WRUhPbWFNV3UvOWRrQzRRakZsMXc9PQotLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCg==

0 commit comments

Comments
 (0)