Skip to content

Commit

Permalink
Merge pull request #47 from akgalwas/fetch-dynamic-kubeconfig
Browse files Browse the repository at this point in the history
Fetching dynamic kubeconfig
  • Loading branch information
kyma-bot authored Oct 1, 2023
2 parents 7cf18b1 + e3f80cb commit 9cff167
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 33 deletions.
59 changes: 51 additions & 8 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,24 @@ package main

import (
"flag"
"fmt"
"os"
"time"

"github.com/gardener/gardener/pkg/apis/core/v1beta1"
gardener_apis "github.com/gardener/gardener/pkg/client/core/clientset/versioned/typed/core/v1beta1"
infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1"
"github.com/kyma-project/infrastructure-manager/internal/controller"
"github.com/kyma-project/infrastructure-manager/internal/gardener"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)
Expand All @@ -46,18 +52,24 @@ func init() {
//+kubebuilder:scaffold:scheme
}

const defaultExpirationTime = 24 * time.Hour

func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var gardenerKubeconfigPath string
var gardenerProjectName string
var expirationTime time.Duration

flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.StringVar(&gardenerKubeconfigPath, "gardener-kubeconfig-path", "/gardener/kubeconfig/kubeconfig", "Kubeconfig file for Gardener cluster")
flag.StringVar(&gardenerProjectName, "gardener-project-name", "gardener-project", "Name of the Gardener project")
flag.DurationVar(&expirationTime, "kubeconfig-expiration-time", defaultExpirationTime, "Dynamic kubeconfig expiration time")

opts := zap.Options{
Development: true,
Expand Down Expand Up @@ -93,9 +105,16 @@ func main() {
os.Exit(1)
}

provider := gardener.KubeconfigProvider{}
gardenerNamespace := fmt.Sprintf("garden-%s", gardenerProjectName)
expirationInSeconds := int64(expirationTime.Seconds())
kubeconfigProvider, err := setupKubernetesKubeconfigProvider(gardenerKubeconfigPath, gardenerNamespace, expirationInSeconds)

if err != nil {
setupLog.Error(err, "unable to initialize kubeconfig provider", "controller", "GardenerCluster")
os.Exit(1)
}

if err = (controller.NewGardenerClusterController(mgr, provider, logger)).SetupWithManager(mgr); err != nil {
if err = (controller.NewGardenerClusterController(mgr, kubeconfigProvider, logger)).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "GardenerCluster")
os.Exit(1)
}
Expand All @@ -110,15 +129,39 @@ func main() {
os.Exit(1)
}

_, err = gardener.NewClientFromFile(gardenerKubeconfigPath)
if err != nil {
setupLog.Error(err, "failed to load Gardener kubeconfig")
os.Exit(1)
}

setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}

func setupKubernetesKubeconfigProvider(kubeconfigPath string, namespace string, expirationInSeconds int64) (gardener.KubeconfigProvider, error) {
restConfig, err := gardener.NewRestConfigFromFile(kubeconfigPath)
if err != nil {
return gardener.KubeconfigProvider{}, err
}

gardenerClientSet, err := gardener_apis.NewForConfig(restConfig)
if err != nil {
return gardener.KubeconfigProvider{}, err
}

gardenerClient, err := client.New(restConfig, client.Options{})
if err != nil {
return gardener.KubeconfigProvider{}, err
}

shootClient := gardenerClientSet.Shoots(namespace)
dynamicKubeconfigAPI := gardenerClient.SubResource("adminkubeconfig")

err = v1beta1.AddToScheme(gardenerClient.Scheme())
if err != nil {
return gardener.KubeconfigProvider{}, errors.Wrap(err, "failed to register Gardener schema")
}

return gardener.NewKubeconfigProvider(shootClient,
dynamicKubeconfigAPI,
namespace,
expirationInSeconds), nil
}
2 changes: 2 additions & 0 deletions config/default/manager_gardener_secret_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ spec:
- /manager
args:
- --gardener-kubeconfig-path=/gardener/credentials/kubeconfig
- --gardener-project-name=kyma-dev
- --kubeconfig-expiration-time=24h
volumeMounts:
- name: gardener-kubeconfig
mountPath: /gardener/credentials
17 changes: 10 additions & 7 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,31 @@ metadata:
name: manager-role
rules:
- apiGroups:
- infrastructuremanager.kyma-project.io
- ""
resources:
- gardenerclusters
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- infrastructuremanager.kyma-project.io
resources:
- gardenerclusters/finalizers
- gardenerclusters
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- infrastructuremanager.kyma-project.io
resources:
- gardenerclusters/status
- gardenerclusters/finalizers
verbs:
- get
- patch
- update
Empty file.
2 changes: 1 addition & 1 deletion config/samples/clusterinventory_v1_gardenercluster.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: clusterinventory.kyma-project.io/v1
apiVersion: infrastructuremanager.kyma-project.io/v1
kind: GardenerCluster
metadata:
labels:
Expand Down
9 changes: 2 additions & 7 deletions internal/controller/gardener_cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type KubeconfigProvider interface {
}

//+kubebuilder:rbac:groups=infrastructuremanager.kyma-project.io,resources=gardenerclusters,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=infrastructuremanager.kyma-project.io,resources=gardenerclusters/status,verbs=get;update;patch
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete
//+kubebuilder:rbac:groups=infrastructuremanager.kyma-project.io,resources=gardenerclusters/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
Expand All @@ -74,7 +74,7 @@ type KubeconfigProvider interface {
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.15.0/pkg/reconcile
func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:revive
r.log.Info("Starting reconciliation loop")
r.log.Info(fmt.Sprintf("Starting reconciliation loop for GardenerCluster resource: %v", req.NamespacedName))

var cluster infrastructuremanagerv1.GardenerCluster

Expand All @@ -95,8 +95,6 @@ func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Requ

secret, err := r.getSecret(cluster.Spec.Shoot.Name)
if err != nil {
r.log.Error(err, "could not get the Secret for "+cluster.Spec.Shoot.Name)

if !k8serrors.IsNotFound(err) {
return ctrl.Result{
Requeue: true,
Expand All @@ -106,10 +104,7 @@ func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Requ
}

if secret == nil {
r.log.Error(err, "Secret not found, and will be created")

err = r.createSecret(ctx, cluster)

if err != nil {
return r.ResultWithoutRequeue(), err
}
Expand Down
55 changes: 53 additions & 2 deletions internal/gardener/KubeConfigProvider.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,59 @@
package gardener

import (
"context"

authenticationv1alpha1 "github.com/gardener/gardener/pkg/apis/authentication/v1alpha1"
"github.com/gardener/gardener/pkg/apis/core/v1beta1"
"github.com/pkg/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gardenerClient "sigs.k8s.io/controller-runtime/pkg/client"
)

type KubeconfigProvider struct {
shootNamespace string
shootClient ShootClient
dynamicKubeconfigAPI DynamicKubeconfigAPI
expirationInSeconds int64
}

type ShootClient interface {
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.Shoot, error)
}

type DynamicKubeconfigAPI interface {
Create(ctx context.Context, obj gardenerClient.Object, subResource gardenerClient.Object, opts ...gardenerClient.SubResourceCreateOption) error
}

func (receiver KubeconfigProvider) Fetch(shootName string) (string, error) {
return "kubeconfig-" + shootName, nil
func NewKubeconfigProvider(
shootClient ShootClient,
dynamicKubeconfigAPI DynamicKubeconfigAPI,
shootNamespace string,
expirationInSeconds int64) KubeconfigProvider {
return KubeconfigProvider{
shootClient: shootClient,
dynamicKubeconfigAPI: dynamicKubeconfigAPI,
shootNamespace: shootNamespace,
expirationInSeconds: expirationInSeconds,
}
}

func (kp KubeconfigProvider) Fetch(shootName string) (string, error) {
shoot, err := kp.shootClient.Get(context.Background(), shootName, v1.GetOptions{})
if err != nil {
return "", errors.Wrap(err, "failed to get shoot")
}

adminKubeconfigRequest := authenticationv1alpha1.AdminKubeconfigRequest{
Spec: authenticationv1alpha1.AdminKubeconfigRequestSpec{
ExpirationSeconds: &kp.expirationInSeconds,
},
}

err = kp.dynamicKubeconfigAPI.Create(context.Background(), shoot, &adminKubeconfigRequest)
if err != nil {
return "", errors.Wrap(err, "failed to create AdminKubeconfigRequest")
}

return string(adminKubeconfigRequest.Status.Kubeconfig), nil
}
11 changes: 3 additions & 8 deletions internal/gardener/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"fmt"
"os"

gardener_apis "github.com/gardener/gardener/pkg/client/core/clientset/versioned/typed/core/v1beta1"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

func NewClientFromFile(kubeconfigFilePath string) (*gardener_apis.CoreV1beta1Client, error) {
func NewRestConfigFromFile(kubeconfigFilePath string) (*restclient.Config, error) {
rawKubeconfig, err := os.ReadFile(kubeconfigFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read Gardener Kubeconfig from path %s: %s", kubeconfigFilePath, err.Error())
Expand All @@ -19,10 +19,5 @@ func NewClientFromFile(kubeconfigFilePath string) (*gardener_apis.CoreV1beta1Cli
return nil, err
}

clientset, err := gardener_apis.NewForConfig(restConfig)
if err != nil {
return nil, err
}

return clientset, nil
return restConfig, err
}

0 comments on commit 9cff167

Please sign in to comment.