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

Add kclient functions for creating deployments #2509

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
35 changes: 35 additions & 0 deletions pkg/kclient/deployments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kclient

import (
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// constants for deployments
const (
DeploymentKind = "Deployment"
Copy link
Contributor

Choose a reason for hiding this comment

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

+1

DeploymentAPIVersion = "apps/v1"
)

// CreateDeployment creates a deployment based on the given deployment spec
func (c *Client) CreateDeployment(name string, deploymentSpec appsv1.DeploymentSpec) (*appsv1.Deployment, error) {
// inherit ObjectMeta from deployment spec so that namespace, labels, owner references etc will be the same
objectMeta := deploymentSpec.Template.ObjectMeta
objectMeta.Name = name

deployment := appsv1.Deployment{
kanchwala-yusuf marked this conversation as resolved.
Show resolved Hide resolved
TypeMeta: metav1.TypeMeta{
girishramnani marked this conversation as resolved.
Show resolved Hide resolved
Kind: DeploymentKind,
APIVersion: DeploymentAPIVersion,
},
ObjectMeta: objectMeta,
Spec: deploymentSpec,
}

deploy, err := c.KubeClient.AppsV1().Deployments(c.Namespace).Create(&deployment)
if err != nil {
return nil, errors.Wrapf(err, "unable to create Deployment %s", name)
}
return deploy, nil
}
86 changes: 86 additions & 0 deletions pkg/kclient/deployments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package kclient

import (
"testing"

"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"

ktesting "k8s.io/client-go/testing"
)

func TestCreateDeployment(t *testing.T) {

container := GenerateContainer("container1", "image1", true, []string{"tail"}, []string{"-f", "/dev/null"}, []corev1.EnvVar{})

labels := map[string]string{
"app": "app",
"component": "frontend",
}

podSpec := GeneratePodTemplateSpec("", "defualt", "default", labels, []corev1.Container{*container})

tests := []struct {
name string
deploymentName string
wantErr bool
}{
{
name: "Case: Valid deployment name",
deploymentName: "pod",
wantErr: false,
},
{
name: "Case: Invalid deployment name",
deploymentName: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// initialising the fakeclient
fkclient, fkclientset := FakeNew()
fkclient.Namespace = "default"

fkclientset.Kubernetes.PrependReactor("create", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) {
if tt.deploymentName == "" {
return true, nil, errors.Errorf("deployment name is empty")
}
deployment := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: DeploymentKind,
APIVersion: DeploymentAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: tt.deploymentName,
},
}
return true, &deployment, nil
})

deploymentSpec := GenerateDeploymentSpec(*podSpec)
createdDeployment, err := fkclient.CreateDeployment(tt.deploymentName, *deploymentSpec)

// Checks for unexpected error cases
if !tt.wantErr == (err != nil) {
t.Errorf("fkclient.CreateDeployment(pod) unexpected error %v, wantErr %v", err, tt.wantErr)
}

if err == nil {

if len(fkclientset.Kubernetes.Actions()) != 1 {
t.Errorf("expected 1 action in StartDeployment got: %v", fkclientset.Kubernetes.Actions())
} else {
if createdDeployment.Name != tt.deploymentName {
t.Errorf("deployment name does not match the expected name, expected: %s, got %s", tt.deploymentName, createdDeployment.Name)
}
}

}

})
}
}
62 changes: 62 additions & 0 deletions pkg/kclient/generators.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package kclient

import (

// api resource types
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GenerateContainer creates a container spec that can be used when creating a pod
func GenerateContainer(name, image string, isPrivileged bool, command, args []string, envVars []corev1.EnvVar) *corev1.Container {
container := &corev1.Container{
Name: name,
Image: image,
ImagePullPolicy: corev1.PullAlways,

Command: command,
Args: args,
Env: envVars,
}

if isPrivileged {
container.SecurityContext = &corev1.SecurityContext{
Privileged: &isPrivileged,
}
}

return container
}

// GeneratePodTemplateSpec creates a pod template spec that can be used to create a deployment spec
func GeneratePodTemplateSpec(podName, namespace, serviceAccountName string, labels map[string]string, containers []corev1.Container) *corev1.PodTemplateSpec {
podTemplateSpec := &corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: labels,
},
Spec: corev1.PodSpec{
ServiceAccountName: serviceAccountName,
Containers: containers,
},
}

return podTemplateSpec
}

// GenerateDeploymentSpec creates a deployment spec
func GenerateDeploymentSpec(podTemplateSpec corev1.PodTemplateSpec) *appsv1.DeploymentSpec {
replicas := int32(1)
kanchwala-yusuf marked this conversation as resolved.
Show resolved Hide resolved
labels := podTemplateSpec.ObjectMeta.Labels
deploymentSpec := &appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: podTemplateSpec,
}

return deploymentSpec
}
154 changes: 154 additions & 0 deletions pkg/kclient/generators_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package kclient

import (
"testing"

corev1 "k8s.io/api/core/v1"
)

func TestGenerateContainer(t *testing.T) {

tests := []struct {
name string
image string
isPrivileged bool
command []string
args []string
envVars []corev1.EnvVar
}{
{
name: "",
image: "",
isPrivileged: false,
command: []string{},
args: []string{},
envVars: []corev1.EnvVar{},
},
{
name: "container1",
image: "quay.io/eclipse/che-java8-maven:nightly",
isPrivileged: true,
command: []string{"tail"},
args: []string{"-f", "/dev/null"},
envVars: []corev1.EnvVar{
{
Name: "test",
Value: "123",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

container := GenerateContainer(tt.name, tt.image, tt.isPrivileged, tt.command, tt.args, tt.envVars)

if container.Name != tt.name {
t.Errorf("expected %s, actual %s", tt.name, container.Name)
}

if container.Image != tt.image {
t.Errorf("expected %s, actual %s", tt.image, container.Image)
}

if tt.isPrivileged {
if *container.SecurityContext.Privileged != tt.isPrivileged {
t.Errorf("expected %t, actual %t", tt.isPrivileged, *container.SecurityContext.Privileged)
}
} else if tt.isPrivileged == false && container.SecurityContext != nil {
t.Errorf("expected security context to be nil but it was defined")
}

if len(container.Command) != len(tt.command) {
t.Errorf("expected %d, actual %d", len(tt.command), len(container.Command))
} else {
for i := range container.Command {
if container.Command[i] != tt.command[i] {
t.Errorf("expected %s, actual %s", tt.command[i], container.Command[i])
}
}
}

if len(container.Args) != len(tt.args) {
t.Errorf("expected %d, actual %d", len(tt.args), len(container.Args))
} else {
for i := range container.Args {
if container.Args[i] != tt.args[i] {
t.Errorf("expected %s, actual %s", tt.args[i], container.Args[i])
}
}
}

if len(container.Env) != len(tt.envVars) {
t.Errorf("expected %d, actual %d", len(tt.envVars), len(container.Env))
} else {
for i := range container.Env {
if container.Env[i].Name != tt.envVars[i].Name {
t.Errorf("expected name %s, actual name %s", tt.envVars[i].Name, container.Env[i].Name)
}
if container.Env[i].Value != tt.envVars[i].Value {
t.Errorf("expected value %s, actual value %s", tt.envVars[i].Value, container.Env[i].Value)
}
}
}

})
}
}

func TestGeneratePodSpec(t *testing.T) {

container := &corev1.Container{
Name: "container1",
Image: "image1",
ImagePullPolicy: corev1.PullAlways,

Command: []string{"tail"},
Args: []string{"-f", "/dev/null"},
Env: []corev1.EnvVar{},
}

tests := []struct {
podName string
namespace string
serviceAccount string
labels map[string]string
}{
{
podName: "podSpecTest",
namespace: "default",
serviceAccount: "default",
labels: map[string]string{
"app": "app",
"component": "frontend",
},
},
}

for _, tt := range tests {
t.Run(tt.podName, func(t *testing.T) {

podSpec := GeneratePodTemplateSpec(tt.podName, tt.namespace, tt.serviceAccount, tt.labels, []corev1.Container{*container})

if podSpec.Name != tt.podName {
t.Errorf("expected %s, actual %s", tt.podName, podSpec.Name)
}

if podSpec.Namespace != tt.namespace {
t.Errorf("expected %s, actual %s", tt.namespace, podSpec.Namespace)
}

if len(podSpec.Labels) != len(tt.labels) {
t.Errorf("expected %d, actual %d", len(tt.labels), len(podSpec.Labels))
} else {
for i := range podSpec.Labels {
if podSpec.Labels[i] != tt.labels[i] {
t.Errorf("expected %s, actual %s", tt.labels[i], podSpec.Labels[i])
}
}
}

})
}
}