Skip to content

Commit

Permalink
feat: implement kubectl apply, delete
Browse files Browse the repository at this point in the history
Signed-off-by: minhthong582000 <[email protected]>
  • Loading branch information
minhthong582000 committed Jun 28, 2024
1 parent 9a9c359 commit 9278fe7
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 46 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.9
v0.1.0-alpha.10
13 changes: 1 addition & 12 deletions gitops/example/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,4 @@ metadata:
spec:
repository: https://github.com/minhthong582000/k8s-controller-pattern.git
revision: main
path: gitops/utils/kube/testdata
# ---
# kind: Application
# apiVersion: thongdepzai.cloud/v1alpha1
# metadata:
# name: example-application-two
# labels:
# thongdepzai.cloud/app: example-application-two
# spec:
# repository: https://github.com/minhthong582000/k8s-controller-pattern.git
# revision: main
# path: k8s-controller-pattern/gitops
path: gitops/example/nginx
26 changes: 26 additions & 0 deletions gitops/example/nginx/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
13 changes: 13 additions & 0 deletions gitops/example/nginx/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: nginx
type: ClusterIP
19 changes: 15 additions & 4 deletions gitops/internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func (c *Controller) createResources(ctx context.Context, app *v1alpha1.Applicat
}
if diff {
// Apply manifests
err = c.k8sUtil.ApplyResource(path.Join(repoPath, app.Spec.Path))
err = c.k8sUtil.ApplyResources(path.Join(repoPath, app.Spec.Path))
if err != nil {
return fmt.Errorf("error applying resources: %s", err)
}
Expand Down Expand Up @@ -277,10 +277,21 @@ 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.k8sUtil.DeleteResource(path.Join(repoPath, app.Spec.Path))
// Get all resources with the label
label := map[string]string{
common.LabelKeyAppInstance: app.Name,
}
resources, err := c.k8sUtil.GetResourceWithLabel(label)
if err != nil {
return fmt.Errorf("error deleting resources: %s", err)
return fmt.Errorf("error getting resources with label: %s, %s", label, err)
}

log.WithField("application", app.Name).Info("Deleting resources")
for _, r := range resources {
err := c.k8sUtil.DeleteResource(context.Background(), r, r.GetNamespace())
if err != nil {
log.Errorf("error deleting resources: %s", err)
}
}

err = c.gitUtil.CleanUp(repoPath)
Expand Down
19 changes: 6 additions & 13 deletions gitops/internal/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import (
"bytes"
"context"
"fmt"
"os"
"path"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -84,7 +81,7 @@ spec:
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)
mock.EXPECT().ApplyResources(gomock.Any()).Return(nil)
return mock
}(),
expectedStatus: v1alpha1.HealthStatusCode(v1alpha1.HealthStatusHealthy),
Expand Down Expand Up @@ -194,7 +191,8 @@ spec:
}(),
mockk8sUtil: func() k8sUtil.K8s {
mock := k8sUtilMock.NewMockK8s(ctrl)
mock.EXPECT().DeleteResource(gomock.Any()).Return(nil)
mock.EXPECT().GetResourceWithLabel(gomock.Any()).Return(nil, nil)
mock.EXPECT().DeleteResource(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
return mock
}(),
},
Expand All @@ -217,7 +215,8 @@ spec:
}(),
mockk8sUtil: func() k8sUtil.K8s {
mock := k8sUtilMock.NewMockK8s(ctrl)
mock.EXPECT().DeleteResource(gomock.Any()).Return(nil)
mock.EXPECT().GetResourceWithLabel(gomock.Any()).Return(nil, nil)
mock.EXPECT().DeleteResource(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
return mock
}(),
},
Expand All @@ -228,14 +227,8 @@ spec:
app := newFakeApp(tt.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))
err := os.MkdirAll(repoPath, os.ModePerm)
assert.NoError(t, err)
assert.DirExists(t, repoPath)

// Delete resources
err = controller.deleteResources(app)
err := controller.deleteResources(app)
if err != nil {
assert.Equal(t, tt.expectedErr, err.Error())
}
Expand Down
78 changes: 72 additions & 6 deletions gitops/utils/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"sync"

log "github.com/sirupsen/logrus"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
Expand All @@ -20,8 +23,8 @@ import (
)

type K8s interface {
ApplyResource(path string) error
DeleteResource(path string) error
ApplyResources(path string) error
DeleteResource(ctx context.Context, currentObj *unstructured.Unstructured, namespace string) error
GenerateManifests(path string) ([]*unstructured.Unstructured, error)
GetResourceWithLabel(label map[string]string) ([]*unstructured.Unstructured, error)
DiffResources(old []*unstructured.Unstructured, new []*unstructured.Unstructured) (bool, error)
Expand All @@ -40,12 +43,33 @@ func NewK8s(discoveryClient discovery.DiscoveryInterface, dynClientSet dynamic.I
}
}

func (k *k8s) ApplyResource(path string) error {
return nil
func (k *k8s) ApplyResources(path string) error {
// Run kubeclt apply -f path
cmd := exec.Command("kubectl", "apply", "-Rf", path)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

return cmd.Run()
}

func (k *k8s) DeleteResource(path string) error {
return nil
func (k *k8s) DeleteResource(ctx context.Context, currentObj *unstructured.Unstructured, namespace string) error {
gvk := currentObj.GroupVersionKind()
apiResource, err := ServerResourceForGroupVersionKind(
k.discoveryClient,
gvk,
"delete",
)
if err != nil {
return err
}

resource := gvk.GroupVersion().WithResource(apiResource.Name)

var dynInterface dynamic.ResourceInterface = k.dynClientSet.Resource(resource)
if apiResource.Namespaced {
dynInterface = k.dynClientSet.Resource(resource).Namespace(namespace)
}
return dynInterface.Delete(ctx, currentObj.GetName(), metav1.DeleteOptions{})
}

func (k *k8s) GenerateManifests(path string) ([]*unstructured.Unstructured, error) {
Expand Down Expand Up @@ -184,3 +208,45 @@ func (k *k8s) SetLabelsForResources(resources []*unstructured.Unstructured, labe

return nil
}

func ServerResourceForGroupVersionKind(disco discovery.DiscoveryInterface, gvk schema.GroupVersionKind, verb string) (*metav1.APIResource, error) {
// default is to return a not found for the requested resource
retErr := apierr.NewNotFound(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, "")
resources, err := disco.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
if err != nil {
return nil, err
}
for _, r := range resources.APIResources {
if r.Kind == gvk.Kind {
if isSupportedVerb(&r, verb) {
return &r, nil
} else {
// We have a match, but the API does not support the action
// that was requested. Memorize this.
retErr = apierr.NewMethodNotSupported(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, verb)
}
}
}
return nil, retErr
}

// isSupportedVerb returns whether or not a APIResource supports a specific verb.
// The verb will be matched case-insensitive.
func isSupportedVerb(apiResource *metav1.APIResource, verb string) bool {
if verb == "" || verb == "*" {
return true
}
for _, v := range apiResource.Verbs {
if strings.EqualFold(v, verb) {
return true
}
}
return false
}

func ToResourceInterface(dynamicIf dynamic.Interface, apiResource *metav1.APIResource, resource schema.GroupVersionResource, namespace string) dynamic.ResourceInterface {
if apiResource.Namespaced {
return dynamicIf.Resource(resource).Namespace(namespace)
}
return dynamicIf.Resource(resource)
}
21 changes: 11 additions & 10 deletions gitops/utils/kube/mock/mock_kube.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9278fe7

Please sign in to comment.