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 1 commit
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
37 changes: 37 additions & 0 deletions pkg/kclient/deployments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kclient

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

// CreateDeployment creates a deployment based on the given pod
func (c *Client) CreateDeployment(pod *corev1.Pod) (*appsv1.Deployment, error) {

replicas := int32(1)
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: "Deployment",
APIVersion: "apps/v1",
Copy link
Contributor

Choose a reason for hiding this comment

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

should we make this a const?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added constants for the Kind and APIVersion

},
ObjectMeta: pod.ObjectMeta,
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: pod.Labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: pod.ObjectMeta,
Spec: pod.Spec,
},
},
}

deploy, err := c.KubeClient.AppsV1().Deployments(c.Namespace).Create(&deployment)
if err != nil {
return nil, errors.Wrapf(err, "unable to create Deployment for %s", pod.ObjectMeta.Name)
}
return deploy, nil
}
106 changes: 106 additions & 0 deletions pkg/kclient/deployments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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 := &corev1.Container{
Name: "container1",
Image: "image1",
ImagePullPolicy: corev1.PullAlways,

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

pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "",
Namespace: "default",
Labels: map[string]string{
"app": "app",
"component": "frontend",
},
},
Spec: corev1.PodSpec{
ServiceAccountName: "default",
Containers: []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: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: tt.deploymentName,
},
}
return true, &deployment, nil
})

pod.ObjectMeta.Name = tt.deploymentName
createdDeployment, err := fkclient.CreateDeployment(pod)

// 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)
}
}

}

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

import (

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

// GenerateContainerSpec creates a container spec that can be used when creating a pod
func GenerateContainerSpec(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
}

// GeneratePodSpec creates a pod spec
func GeneratePodSpec(podName, namespace, serviceAccountName string, labels map[string]string, containers []corev1.Container) *corev1.Pod {
pod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: labels,
},
Spec: corev1.PodSpec{
ServiceAccountName: serviceAccountName,
Containers: containers,
},
}

return pod
}
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 TestGenerateContainerSpec(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) {

containerSpec := GenerateContainerSpec(tt.name, tt.image, tt.isPrivileged, tt.command, tt.args, tt.envVars)

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

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

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

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

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

if len(containerSpec.Env) != len(tt.envVars) {
t.Errorf("expected %d, actual %d", len(tt.envVars), len(containerSpec.Env))
} else {
for i := range containerSpec.Env {
if containerSpec.Env[i].Name != tt.envVars[i].Name {
t.Errorf("expected name %s, actual name %s", tt.envVars[i].Name, containerSpec.Env[i].Name)
}
if containerSpec.Env[i].Value != tt.envVars[i].Value {
t.Errorf("expected value %s, actual value %s", tt.envVars[i].Value, containerSpec.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 := GeneratePodSpec(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])
}
}
}

})
}
}