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

Fix resource listing #171

Merged
merged 4 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 68 additions & 87 deletions auth/config.go → api/config/extension.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,43 @@
package auth
package config

import (
"encoding/json"
"fmt"
"os"

"github.com/ninech/nctl/api"
"github.com/ninech/nctl/api/util"
"github.com/ninech/nctl/internal/format"
infrastructure "github.com/ninech/apis/infrastructure/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

const (
extensionKind = "Config"
extensionGroup = "nctl.nine.ch"
extensionVersion = "v1alpha1"
extensionKind = "Config"
extensionGroup = "nctl.nine.ch"
extensionVersion = "v1alpha1"
NctlExtensionContext = "nctl"
)

var (
// ErrConfigNotFound describes a missing nctl config in the kubeconfig
ErrConfigNotFound configError = "nctl config not found"
// ErrExtensionNotFound describes a missing extension in the kubeconfig
ErrExtensionNotFound extensionError = "nctl config not found"
)

type configError string
type extensionError string

func (c configError) Error() string {
func (c extensionError) Error() string {
return string(c)
}

// IsConfigNotFoundError returns true if the nctl config could not be found in
// IsExtensionNotFoundError returns true if the nctl config could not be found in
// the kubconfig context
func IsConfigNotFoundError(err error) bool {
return err == ErrConfigNotFound
func IsExtensionNotFoundError(err error) bool {
return err == ErrExtensionNotFound
}

// ReloginNeeded returns an error which outputs the given err with a message
// saying that a re-login is needed.
func ReloginNeeded(err error) error {
return fmt.Errorf(
"%w, please re-login by executing %q",
err,
format.Command().Login(),
)
}

// Config is used to store information in the kubeconfig context created
type Config struct {
// Extension is used to store custom information in the kubeconfig context
// created
type Extension struct {
metav1.TypeMeta `json:",inline"`
Organization string `json:"organization"`
}
Expand All @@ -57,8 +46,8 @@ func groupVersion() string {
return fmt.Sprintf("%s/%s", extensionGroup, extensionVersion)
}

func NewConfig(organization string) *Config {
return &Config{
func NewExtension(organization string) *Extension {
return &Extension{
TypeMeta: metav1.TypeMeta{
Kind: extensionKind,
APIVersion: groupVersion(),
Expand All @@ -69,7 +58,7 @@ func NewConfig(organization string) *Config {

// ToObject wraps a Config in a runtime.Unknown object which implements
// runtime.Object.
func (e *Config) ToObject() (runtime.Object, error) {
func (e *Extension) ToObject() (runtime.Object, error) {
raw, err := json.Marshal(e)
if err != nil {
return nil, err
Expand All @@ -82,7 +71,7 @@ func (e *Config) ToObject() (runtime.Object, error) {
return &u, nil
}

func parseConfig(o runtime.Object) (*Config, error) {
func parseConfig(o runtime.Object) (*Extension, error) {
u, is := o.(*runtime.Unknown)
if !is {
return nil, fmt.Errorf("can not handle underlying type %T", o)
Expand All @@ -96,55 +85,37 @@ func parseConfig(o runtime.Object) (*Config, error) {
return nil, fmt.Errorf("can not parse extension with type meta %q", u.TypeMeta.String())
}

e := &Config{}
e := &Extension{}
return e, json.Unmarshal(u.Raw, e)
}

func mergeKubeConfig(from, to *clientcmdapi.Config) {
for k, v := range from.Clusters {
to.Clusters[k] = v
func readExtension(kubeconfigContent []byte, contextName string) (*Extension, error) {
kubeconfig, err := clientcmd.Load(kubeconfigContent)
if err != nil {
return nil, fmt.Errorf("kubeconfig not found: %w", err)
}

for k, v := range from.AuthInfos {
to.AuthInfos[k] = v
context, exists := kubeconfig.Contexts[contextName]
if !exists {
return nil, fmt.Errorf("could not find context %q in kubeconfig", contextName)
}

for k, v := range from.Contexts {
to.Contexts[k] = v
extension, exists := context.Extensions[NctlExtensionContext]
if !exists {
return nil, ErrExtensionNotFound
}
}

func RemoveClusterFromKubeConfig(client *api.Client, clusterContext string) error {
kubeconfig, err := clientcmd.LoadFromFile(client.KubeconfigPath)
cfg, err := parseConfig(extension)
if err != nil {
return fmt.Errorf("kubeconfig not found: %w", err)
}

if _, ok := kubeconfig.Clusters[clusterContext]; !ok {
return fmt.Errorf("could not find cluster %q in kubeconfig", clusterContext)
return nil, err
}

delete(kubeconfig.Clusters, clusterContext)
delete(kubeconfig.AuthInfos, clusterContext)
delete(kubeconfig.Contexts, clusterContext)

kubeconfig.CurrentContext = ""

return clientcmd.WriteToFile(*kubeconfig, client.KubeconfigPath)
return cfg, nil
}

// SetContextProject sets the given project in the given context of the kubeconfig
func SetContextProject(kubeconfigPath string, contextName string, project string) error {
kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath)
func ReadExtension(kubeconfigPath string, contextName string) (*Extension, error) {
content, err := os.ReadFile(kubeconfigPath)
if err != nil {
return fmt.Errorf("kubeconfig not found: %w", err)
}
context, exists := kubeconfig.Contexts[contextName]
if !exists {
return fmt.Errorf("could not find context %q in kubeconfig", contextName)
return nil, err
}
context.Namespace = project
return clientcmd.WriteToFile(*kubeconfig, kubeconfigPath)
return readExtension(content, contextName)
}

// SetContextOrganization sets the given organization in the given context of the kubeconfig
Expand All @@ -157,9 +128,9 @@ func SetContextOrganization(kubeconfigPath string, contextName string, organizat
if !exists {
return fmt.Errorf("could not find context %q in kubeconfig", contextName)
}
extension, exists := context.Extensions[util.NctlName]
extension, exists := context.Extensions[NctlExtensionContext]
if !exists {
return ErrConfigNotFound
return ErrExtensionNotFound
}

cfg, err := parseConfig(extension)
Expand All @@ -176,39 +147,49 @@ func SetContextOrganization(kubeconfigPath string, contextName string, organizat
if err != nil {
return err
}
context.Extensions[util.NctlName] = cfgObject
context.Extensions[NctlExtensionContext] = cfgObject

// change project to default for the the given organization:
context.Namespace = organization

return clientcmd.WriteToFile(*kubeconfig, kubeconfigPath)
}

func readConfig(kubeconfigContent []byte, contextName string) (*Config, error) {
kubeconfig, err := clientcmd.Load(kubeconfigContent)
// SetContextProject sets the given project in the given context of the kubeconfig
func SetContextProject(kubeconfigPath string, contextName string, project string) error {
kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath)
if err != nil {
return nil, fmt.Errorf("kubeconfig not found: %w", err)
return fmt.Errorf("kubeconfig not found: %w", err)
}
context, exists := kubeconfig.Contexts[contextName]
if !exists {
return nil, fmt.Errorf("could not find context %q in kubeconfig", contextName)
}
extension, exists := context.Extensions[util.NctlName]
if !exists {
return nil, ErrConfigNotFound
return fmt.Errorf("could not find context %q in kubeconfig", contextName)
}
cfg, err := parseConfig(extension)
context.Namespace = project
return clientcmd.WriteToFile(*kubeconfig, kubeconfigPath)
}

// RemoveClusterFromKubeConfig removes the given context from the kubeconfig
func RemoveClusterFromKubeConfig(kubeconfigPath, clusterContext string) error {
kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath)
if err != nil {
return nil, err
return fmt.Errorf("kubeconfig not found: %w", err)
}

return cfg, nil
if _, ok := kubeconfig.Clusters[clusterContext]; !ok {
return fmt.Errorf("could not find cluster %q in kubeconfig", clusterContext)
}

delete(kubeconfig.Clusters, clusterContext)
delete(kubeconfig.AuthInfos, clusterContext)
delete(kubeconfig.Contexts, clusterContext)

kubeconfig.CurrentContext = ""

return clientcmd.WriteToFile(*kubeconfig, kubeconfigPath)
}

func ReadConfig(kubeconfigPath string, contextName string) (*Config, error) {
content, err := os.ReadFile(kubeconfigPath)
if err != nil {
return nil, err
}
return readConfig(content, contextName)
// ContextName returns the kubeconfig context name for the given cluster
func ContextName(cluster *infrastructure.KubernetesCluster) string {
return fmt.Sprintf("%s/%s", cluster.Name, cluster.Namespace)
}
9 changes: 4 additions & 5 deletions auth/config_test.go → api/config/extension_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package auth
package config

import (
"testing"

"github.com/ninech/nctl/api/util"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
Expand All @@ -12,7 +11,7 @@ import (

func TestConfigParsing(t *testing.T) {
contextName := "test"
cfg := NewConfig("evilcorp")
cfg := NewExtension("evilcorp")
objectCfg, err := cfg.ToObject()
require.NoError(t, err)
for name, testCase := range map[string]struct {
Expand All @@ -23,7 +22,7 @@ func TestConfigParsing(t *testing.T) {
kubeconfig: func() clientcmdapi.Config {
kubeCfg := testKubeconfig(contextName)
kubeCfg.Contexts[contextName].Extensions = map[string]runtime.Object{
util.NctlName: objectCfg,
NctlExtensionContext: objectCfg,
}
return kubeCfg
}(),
Expand All @@ -49,7 +48,7 @@ func TestConfigParsing(t *testing.T) {
content, err := clientcmd.Write(testCase.kubeconfig)
require.NoError(t, err)

parsedCfg, err := readConfig(content, contextName)
parsedCfg, err := readExtension(content, contextName)
if testCase.errorExpected {
require.Error(t, err)
return
Expand Down
11 changes: 11 additions & 0 deletions api/util/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

meta "github.com/ninech/apis/meta/v1alpha1"
"github.com/ninech/nctl/api"
"github.com/ninech/nctl/internal/format"
corev1 "k8s.io/api/core/v1"
)

Expand Down Expand Up @@ -42,3 +43,13 @@ func NewBasicAuthFromSecret(ctx context.Context, secret meta.Reference, client *
string(basicAuthSecret.Data[BasicAuthPasswordKey]),
}, nil
}

// ReloginNeeded returns an error which outputs the given err with a message
// saying that a re-login is needed.
func ReloginNeeded(err error) error {
return fmt.Errorf(
"%w, please re-login by executing %q",
err,
format.Command().Login(),
)
}
14 changes: 4 additions & 10 deletions apply/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (

runtimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
iam "github.com/ninech/apis/iam/v1alpha1"
"github.com/ninech/nctl/api"
"github.com/ninech/nctl/internal/test"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

const (
Expand Down Expand Up @@ -56,15 +56,9 @@ spec: {}
)

func TestFile(t *testing.T) {
scheme, err := api.NewScheme()
if err != nil {
t.Fatal(err)
}

client := fake.NewClientBuilder().WithScheme(scheme).Build()
apiClient := &api.Client{WithWatch: client, Project: "default"}

ctx := context.Background()
apiClient, err := test.SetupClient()
require.NoError(t, err)

tests := map[string]struct {
name string
Expand Down
7 changes: 2 additions & 5 deletions auth/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

infrastructure "github.com/ninech/apis/infrastructure/v1alpha1"
"github.com/ninech/nctl/api"
"github.com/ninech/nctl/api/config"
"github.com/ninech/nctl/api/util"
"k8s.io/apimachinery/pkg/types"
)
Expand Down Expand Up @@ -60,7 +61,7 @@ func (a *ClusterCmd) Run(ctx context.Context, client *api.Client) error {
issuerURL,
command,
cluster.Status.AtProvider.OIDCClientID,
overrideName(ContextName(cluster)),
overrideName(config.ContextName(cluster)),
setCACert(caCert),
)
if err != nil {
Expand Down Expand Up @@ -110,7 +111,3 @@ func clusterName(name, project string) (types.NamespacedName, error) {

return types.NamespacedName{Name: name, Namespace: project}, nil
}

func ContextName(cluster *infrastructure.KubernetesCluster) string {
return fmt.Sprintf("%s/%s", cluster.Name, cluster.Namespace)
}
Loading