Skip to content

Commit

Permalink
feat: use go uber mock for utils pkg & enhance kube logic (#7)
Browse files Browse the repository at this point in the history
Signed-off-by: minhthong582000 <[email protected]>
  • Loading branch information
minhthong582000 authored Jun 27, 2024
1 parent ff3d664 commit 31f2f43
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 102 deletions.
2 changes: 1 addition & 1 deletion gitops/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.1.0-alpha.7
v0.1.0-alpha.8
6 changes: 2 additions & 4 deletions gitops/example/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ kind: Application
apiVersion: thongdepzai.cloud/v1alpha1
metadata:
name: example-application
labels:
thongdepzai.cloud/app: example-application
spec:
repository: https://github.com/minhthong582000/k8s-controller-pattern.git
revision: feat/kube-util-impl
path: k8s-controller-pattern/gitops/utils/kube/testdata
revision: main
path: gitops/utils/kube/testdata
# ---
# kind: Application
# apiVersion: thongdepzai.cloud/v1alpha1
Expand Down
1 change: 1 addition & 0 deletions gitops/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
go.uber.org/mock v0.4.0
k8s.io/api v0.30.1
k8s.io/apimachinery v0.30.1
k8s.io/client-go v0.30.1
Expand Down
2 changes: 2 additions & 0 deletions gitops/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
72 changes: 38 additions & 34 deletions gitops/internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,39 +217,38 @@ func (c *Controller) createResources(ctx context.Context, app *v1alpha1.Applicat
}
log.Debugf("Checked out revision %s", app.Spec.Revision)

// // Generate manifests
// log.Infof("Generating manifests for application %s", app.Name)
// generatedResources, err := c.k8sUtil.GenerateManifests(path.Join(repoPath, app.Spec.Path))
// if err != nil {
// return fmt.Errorf("error generating manifests: %s", err)
// }

// // Get current resources
// log.Infof("Getting resources for application %s", app.Name)
// label := map[string]string{
// common.LabelKeyAppInstance: app.Name,
// }
// currentResources, err := c.k8sUtil.GetResourceWithLabel(label)
// if err != nil {
// return fmt.Errorf("error getting resources with label: %s, %s", label, err)
// }

// // Calculate diff
// log.Infof("Diffing resources for application %s", app.Name)
// diff, err := c.k8sUtil.DiffResources(generatedResources, currentResources)
// if err != nil {
// return fmt.Errorf("error diffing resources: %s", err)
// }
// if !diff {
// log.Info("No changes in resources, skipping")
// return nil
// }

// // Apply manifests
// err = c.k8sUtil.ApplyResource(path.Join(repoPath, app.Spec.Path))
// if err != nil {
// return fmt.Errorf("error applying resources: %s", err)
// }
// Generate manifests
log.Infof("Generating manifests for application %s", app.Name)
generatedResources, err := c.k8sUtil.GenerateManifests(path.Join(repoPath, app.Spec.Path))
if err != nil {
return fmt.Errorf("error generating manifests: %s", err)
}

// Get current resources
log.Infof("Getting resources for application %s", app.Name)
label := map[string]string{
common.LabelKeyAppInstance: app.Name,
}
currentResources, err := c.k8sUtil.GetResourceWithLabel(label)
if err != nil {
return fmt.Errorf("error getting resources with label: %s, %s", label, err)
}

// Calculate diff
log.Infof("Diffing resources for application %s", app.Name)
diff, err := c.k8sUtil.DiffResources(currentResources, generatedResources)
if err != nil {
return fmt.Errorf("error diffing resources: %s", err)
}
if diff {
// Apply manifests
err = c.k8sUtil.ApplyResource(path.Join(repoPath, app.Spec.Path))
if err != nil {
return fmt.Errorf("error applying resources: %s", err)
}
} else {
log.WithField("application", app.Name).Info("No changes in resources")
}

err = c.updateAppStatus(
ctx,
Expand Down Expand Up @@ -279,7 +278,12 @@ func (c *Controller) deleteResources(app *v1alpha1.Application) error {
repoPath := path.Join(os.TempDir(), app.Name, strings.Replace(app.Spec.Repository, "/", "_", -1))

log.WithField("application", app.Name).Info("Deleting resources")
err := c.gitUtil.CleanUp(repoPath)
err := c.k8sUtil.DeleteResource(path.Join(repoPath, app.Spec.Path))
if err != nil {
return fmt.Errorf("error deleting resources: %s", err)
}

err = c.gitUtil.CleanUp(repoPath)
if err != nil {
return fmt.Errorf("error cleaning up repository: %s", err)
}
Expand Down
117 changes: 81 additions & 36 deletions gitops/internal/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controller
import (
"bytes"
"context"
"fmt"
"os"
"path"
"strings"
Expand All @@ -13,8 +14,11 @@ import (
appclientset "github.com/minhthong582000/k8s-controller-pattern/gitops/pkg/clientset/versioned/fake"
appinformers "github.com/minhthong582000/k8s-controller-pattern/gitops/pkg/informers/externalversions"
"github.com/minhthong582000/k8s-controller-pattern/gitops/utils/git"
k8sutil "github.com/minhthong582000/k8s-controller-pattern/gitops/utils/kube"
gitMock "github.com/minhthong582000/k8s-controller-pattern/gitops/utils/git/mock"
k8sUtil "github.com/minhthong582000/k8s-controller-pattern/gitops/utils/kube"
k8sUtilMock "github.com/minhthong582000/k8s-controller-pattern/gitops/utils/kube/mock"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
Expand All @@ -31,11 +35,9 @@ func newFakeApp(appString string) *v1alpha1.Application {
return &app
}

func newFakeController(apps ...runtime.Object) *Controller {
func newFakeController(gitClient git.GitClient, k8sUtil k8sUtil.K8s, apps ...runtime.Object) *Controller {
kubeClientSet := fake.NewSimpleClientset()
appClientSet := appclientset.NewSimpleClientset(apps...)
gitClient := git.NewGitClient("")
k8sUtil := k8sutil.NewK8s(nil, nil)
appInformerFactory := appinformers.NewSharedInformerFactory(appClientSet, time.Second*30)

return NewController(
Expand All @@ -47,16 +49,20 @@ func newFakeController(apps ...runtime.Object) *Controller {
)
}

var (
createResourcesTestCases = []struct {
func Test_CreateResources(t *testing.T) {
ctrl := gomock.NewController(t)

createResourcesTestCases := []struct {
name string
app string
mockGitClient git.GitClient
mockk8sUtil k8sUtil.K8s
expectedOut string
expectedStatus v1alpha1.HealthStatusCode
expectedErr string
}{
{
name: "Normal application 1",
name: "Should create resources successfully if the repository is valid",
app: `
kind: Application
apiVersion: thongdepzai.cloud/v1alpha1
Expand All @@ -67,24 +73,51 @@ spec:
revision: main
path: k8s-controller-pattern/gitops
`,
mockGitClient: func() git.GitClient {
mock := gitMock.NewMockGitClient(ctrl)
mock.EXPECT().CloneOrFetch(gomock.Any(), gomock.Any()).Return(nil)
mock.EXPECT().Checkout(gomock.Any(), gomock.Any()).Return("randomsha", nil)
return mock
}(),
mockk8sUtil: func() k8sUtil.K8s {
mock := k8sUtilMock.NewMockK8s(ctrl)
mock.EXPECT().GenerateManifests(gomock.Any()).Return(nil, nil)
mock.EXPECT().GetResourceWithLabel(gomock.Any()).Return(nil, nil)
mock.EXPECT().DiffResources(gomock.Any(), gomock.Any()).Return(true, nil)
mock.EXPECT().ApplyResource(gomock.Any()).Return(nil)
return mock
}(),
expectedStatus: v1alpha1.HealthStatusCode(v1alpha1.HealthStatusHealthy),
},
{
name: "Normal application 2",
name: "Should create resources successfully even if there is no diff between the old and new resources",
app: `
kind: Application
apiVersion: thongdepzai.cloud/v1alpha1
metadata:
name: test-example-application-two
name: test-example-application-one
spec:
repository: https://github.com/minhthong582000/k8s-controller-pattern.git
revision: main
path: k8s-controller-pattern/gitops
`,
mockGitClient: func() git.GitClient {
mock := gitMock.NewMockGitClient(ctrl)
mock.EXPECT().CloneOrFetch(gomock.Any(), gomock.Any()).Return(nil)
mock.EXPECT().Checkout(gomock.Any(), gomock.Any()).Return("randomsha", nil)
return mock
}(),
mockk8sUtil: func() k8sUtil.K8s {
mock := k8sUtilMock.NewMockK8s(ctrl)
mock.EXPECT().GenerateManifests(gomock.Any()).Return(nil, nil)
mock.EXPECT().GetResourceWithLabel(gomock.Any()).Return(nil, nil)
mock.EXPECT().DiffResources(gomock.Any(), gomock.Any()).Return(false, nil)
return mock
}(),
expectedStatus: v1alpha1.HealthStatusCode(v1alpha1.HealthStatusHealthy),
},
{
name: "Application with invalid repository",
name: "Should return error if the application has invalid repository",
app: `
kind: Application
apiVersion: thongdepzai.cloud/v1alpha1
Expand All @@ -95,35 +128,28 @@ spec:
revision: main
path: k8s-controller-pattern/gitops
`,
mockGitClient: func() git.GitClient {
mock := gitMock.NewMockGitClient(ctrl)
mock.EXPECT().CloneOrFetch(gomock.Any(), gomock.Any()).Return(
fmt.Errorf("failed to clone repository: authentication required"),
)
return mock
}(),
expectedStatus: v1alpha1.HealthStatusCode(v1alpha1.HealthStatusProgressing),
expectedErr: "error cloning repository: failed to clone repository: authentication required",
},
}
)

func Test_CreateResources(t *testing.T) {
for _, tt := range createResourcesTestCases {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()

app := newFakeApp(tt.app)
controller := newFakeController(app)
controller := newFakeController(tt.mockGitClient, tt.mockk8sUtil, app)

err := controller.createResources(ctx, app)
if err != nil {
assert.Equal(t, tt.expectedErr, err.Error())
} else {
repoPath := path.Join(os.TempDir(), app.Name, strings.Replace(app.Spec.Repository, "/", "_", -1))

// Check if git repository is cloned
assert.DirExists(t, repoPath)

// TODO: check if the revision is checked out

// Delete the git repository
err = os.RemoveAll(repoPath)
assert.NoError(t, err)
assert.NoDirExists(t, repoPath)
}

// Check the status of the application
Expand All @@ -137,16 +163,20 @@ func Test_CreateResources(t *testing.T) {
}
}

var (
deleteResourcesTestCases = []struct {
func Test_DeleteResources(t *testing.T) {
ctrl := gomock.NewController(t)

testCases := []struct {
name string
app string
mockGitClient git.GitClient
mockk8sUtil k8sUtil.K8s
expectedOut string
expectedStatus string
expectedErr string
}{
{
name: "Normal application",
name: "Should delete resources successfully if the application is valid",
app: `
kind: Application
apiVersion: thongdepzai.cloud/v1alpha1
Expand All @@ -157,9 +187,19 @@ spec:
revision: main
path: k8s-controller-pattern/gitops
`,
mockGitClient: func() git.GitClient {
mock := gitMock.NewMockGitClient(ctrl)
mock.EXPECT().CleanUp(gomock.Any()).Return(nil)
return mock
}(),
mockk8sUtil: func() k8sUtil.K8s {
mock := k8sUtilMock.NewMockK8s(ctrl)
mock.EXPECT().DeleteResource(gomock.Any()).Return(nil)
return mock
}(),
},
{
name: "Application with invalid repository",
name: "Should delete resources successfully even if the application has invalid repository",
app: `
kind: Application
apiVersion: thongdepzai.cloud/v1alpha1
Expand All @@ -170,15 +210,23 @@ spec:
revision: main
path: k8s-controller-pattern/gitops
`,
mockGitClient: func() git.GitClient {
mock := gitMock.NewMockGitClient(ctrl)
mock.EXPECT().CleanUp(gomock.Any()).Return(nil)
return mock
}(),
mockk8sUtil: func() k8sUtil.K8s {
mock := k8sUtilMock.NewMockK8s(ctrl)
mock.EXPECT().DeleteResource(gomock.Any()).Return(nil)
return mock
}(),
},
}
)

func Test_DeleteResources(t *testing.T) {
for _, tt := range deleteResourcesTestCases {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
app := newFakeApp(tt.app)
controller := newFakeController(app)
controller := newFakeController(tt.mockGitClient, tt.mockk8sUtil, app)

// Create a fake git repository
repoPath := path.Join(os.TempDir(), app.Name, strings.Replace(app.Spec.Repository, "/", "_", -1))
Expand All @@ -191,9 +239,6 @@ func Test_DeleteResources(t *testing.T) {
if err != nil {
assert.Equal(t, tt.expectedErr, err.Error())
}

// Check if the git repository is deleted
assert.NoDirExists(t, repoPath)
})
}
}
2 changes: 1 addition & 1 deletion gitops/utils/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type gitClient struct {
token string
}

func NewGitClient(token string) GitClient {
func NewGitClient(token string) *gitClient {
return &gitClient{
token: token,
}
Expand Down
7 changes: 7 additions & 0 deletions gitops/utils/git/mock/mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mock

import (
_ "go.uber.org/mock/mockgen/model"
)

//go:generate mockgen -destination=mock_kube.go -package=mock github.com/minhthong582000/k8s-controller-pattern/gitops/utils/git GitClient
Loading

0 comments on commit 31f2f43

Please sign in to comment.