Skip to content

Commit

Permalink
refactor resources pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
pPrecel committed Jan 13, 2025
1 parent 9223fbf commit d3f64dc
Show file tree
Hide file tree
Showing 8 changed files with 748 additions and 600 deletions.
155 changes: 155 additions & 0 deletions internal/kube/resources/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package resources

import (
"context"
"fmt"

"github.com/kyma-project/cli.v3/internal/cmdcommon/types"
"github.com/kyma-project/cli.v3/internal/kube"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
SecretMountPathPrefix = "/bindings/secret-"
ConfigmapMountPathPrefix = "/bindings/configmap-"
)

type CreateDeploymentOpts struct {
Name string
Namespace string
Image string
ImagePullSecret string
InjectIstio types.NullableBool
SecretMounts []string
ConfigmapMounts []string
}

func CreateDeployment(ctx context.Context, client kube.Client, opts CreateDeploymentOpts) error {
deployment := buildDeployment(&opts)
_, err := client.Static().AppsV1().Deployments(opts.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
return err
}

func buildDeployment(opts *CreateDeploymentOpts) *appsv1.Deployment {
secretVolumes, secretVolumeMounts := buildSecretVolumes(opts.SecretMounts)
configVolumes, configVolumeMounts := buildConfigmapVolumes(opts.ConfigmapMounts)

deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: opts.Name,
Labels: map[string]string{
"app.kubernetes.io/name": opts.Name,
"app.kubernetes.io/created-by": "kyma-cli",
},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": opts.Name,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: opts.Name,
Labels: map[string]string{
"app": opts.Name,
},
},
Spec: corev1.PodSpec{
Volumes: append(secretVolumes, configVolumes...),
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 80,
},
},
Name: opts.Name,
Image: opts.Image,
Env: []corev1.EnvVar{
{
Name: "SERVICE_BINDING_ROOT",
Value: "/bindings",
},
},
VolumeMounts: append(secretVolumeMounts, configVolumeMounts...),
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("64Mi"),
corev1.ResourceCPU: resource.MustParse("50m"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
corev1.ResourceCPU: resource.MustParse("300m"),
},
},
},
},
},
},
},
}
if opts.InjectIstio.Value != nil {
deployment.Spec.Template.ObjectMeta.Labels["sidecar.istio.io/inject"] = opts.InjectIstio.String()
}

if opts.ImagePullSecret != "" {
deployment.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{
{
Name: opts.ImagePullSecret,
},
}
}

return deployment
}

func buildSecretVolumes(secretNames []string) ([]corev1.Volume, []corev1.VolumeMount) {
volumes := []corev1.Volume{}
volumeMounts := []corev1.VolumeMount{}
for _, secretName := range secretNames {
volumeName := fmt.Sprintf("secret-%s", secretName)
mountPath := fmt.Sprintf("%s%s", SecretMountPathPrefix, secretName)
volumes = append(volumes, corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretName,
},
},
})
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: volumeName,
MountPath: mountPath,
})
}

return volumes, volumeMounts
}

func buildConfigmapVolumes(configmapsNames []string) ([]corev1.Volume, []corev1.VolumeMount) {
volumes := []corev1.Volume{}
volumeMounts := []corev1.VolumeMount{}
for _, configmapName := range configmapsNames {
volumeName := fmt.Sprintf("configmap-%s", configmapName)
mountPath := fmt.Sprintf("%s%s", ConfigmapMountPathPrefix, configmapName)
volumes = append(volumes, corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: configmapName,
},
},
},
})
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: volumeName,
MountPath: mountPath,
})
}
return volumes, volumeMounts
}
149 changes: 149 additions & 0 deletions internal/kube/resources/deployment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package resources

import (
"context"
"testing"

"github.com/kyma-project/cli.v3/internal/cmdcommon/types"
kube_fake "github.com/kyma-project/cli.v3/internal/kube/fake"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8s_fake "k8s.io/client-go/kubernetes/fake"
)

var (
testDeployment = appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/name": "test-name",
"app.kubernetes.io/created-by": "kyma-cli",
},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "test-name",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: "test-name",
Labels: map[string]string{
"app": "test-name",
"sidecar.istio.io/inject": "true",
},
},
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: "secret-test-name",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "test-name",
},
},
},
{
Name: "configmap-test-name",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "test-name",
},
},
},
},
},
ImagePullSecrets: []corev1.LocalObjectReference{
{
Name: "test-image-pull-secret",
},
},
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 80,
},
},
Name: "test-name",
Image: "test:image",
Env: []corev1.EnvVar{
{
Name: "SERVICE_BINDING_ROOT",
Value: "/bindings",
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "secret-test-name",
MountPath: "/bindings/secret-test-name",
},
{
Name: "configmap-test-name",
MountPath: "/bindings/configmap-test-name",
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("64Mi"),
corev1.ResourceCPU: resource.MustParse("50m"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
corev1.ResourceCPU: resource.MustParse("300m"),
},
},
},
},
},
},
},
}
)

func Test_CreateDeployment(t *testing.T) {
t.Parallel()
t.Run("create deployment", func(t *testing.T) {
kubeClient := &kube_fake.KubeClient{
TestKubernetesInterface: k8s_fake.NewSimpleClientset(),
}
trueValue := true
err := CreateDeployment(context.Background(), kubeClient, CreateDeploymentOpts{
Name: "test-name",
Namespace: "default",
Image: "test:image",
ImagePullSecret: "test-image-pull-secret",
InjectIstio: types.NullableBool{Value: &trueValue},
SecretMounts: []string{"test-name"},
ConfigmapMounts: []string{"test-name"},
})
require.NoError(t, err)

deploy, err := kubeClient.Static().AppsV1().Deployments(testDeployment.GetNamespace()).Get(context.Background(), testDeployment.GetName(), metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, &testDeployment, deploy)
})

t.Run("already exists error", func(t *testing.T) {
kubeClient := &kube_fake.KubeClient{
TestKubernetesInterface: k8s_fake.NewSimpleClientset(&testDeployment),
}
trueValue := true
err := CreateDeployment(context.Background(), kubeClient, CreateDeploymentOpts{
Name: "test-name",
Namespace: "default",
Image: "test:image",
ImagePullSecret: "test-image-pull-secret",
InjectIstio: types.NullableBool{Value: &trueValue},
SecretMounts: []string{"test-name"},
ConfigmapMounts: []string{"test-name"},
})
require.ErrorContains(t, err, `deployments.apps "test-name" already exists`)
})
}
85 changes: 85 additions & 0 deletions internal/kube/resources/rbac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package resources

import (
"context"

"github.com/kyma-project/cli.v3/internal/kube"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func CreateServiceAccount(ctx context.Context, client kube.Client, name, namespace string) error {
sa := buildServiceAccount(name, namespace)
_, err := client.Static().CoreV1().ServiceAccounts(namespace).Create(ctx, sa, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
return nil
}

func CreateServiceAccountToken(ctx context.Context, client kube.Client, name, namespace string) error {
secret := buildServiceAccountToken(name, namespace)
_, err := client.Static().CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
return nil
}

func CreateClusterRoleBinding(ctx context.Context, client kube.Client, name, namespace, clusterRole string) error {
// Check if the cluster role to bind to exists
_, err := client.Static().RbacV1().ClusterRoles().Get(ctx, clusterRole, metav1.GetOptions{})
if err != nil {
return err
}
// Create clusterRoleBinding
cRoleBinding := buildClusterRoleBinding(name, namespace, clusterRole)
_, err = client.Static().RbacV1().ClusterRoleBindings().Create(ctx, cRoleBinding, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
return nil
}

func buildServiceAccountToken(name, namespace string) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
"kubernetes.io/service-account.name": name,
},
},
Type: corev1.SecretTypeServiceAccountToken,
}
}

func buildClusterRoleBinding(name, namespace, clusterRole string) *rbacv1.ClusterRoleBinding {
return &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: name + "-binding",
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: name,
Namespace: namespace,
}},

RoleRef: rbacv1.RoleRef{
Kind: "ClusterRole",
Name: clusterRole,
},
}
}

func buildServiceAccount(name, namespace string) *corev1.ServiceAccount {
return &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
}
Loading

0 comments on commit d3f64dc

Please sign in to comment.