diff --git a/examples/deployment/main.go b/examples/deployment/main.go new file mode 100644 index 0000000..7dcee84 --- /dev/null +++ b/examples/deployment/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "context" + "fmt" + + "github.com/cloud-bulldozer/go-commons/k8s" + log "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" +) + +func main() { + // Usage on common Kubernetes Repository + kubeRepo, err := k8s.NewKubernetesRepository() + if err != nil { + log.Info("Some error occured") + } + log.Infof("kube Repo object : %+v", kubeRepo) + + var res k8s.Resource = &kubeRepo.Deployment + if _, ok := res.(*k8s.DeploymentResource); ok { + fmt.Printf("DeploymentResource implements Resource interface. %+v", res) + } else { + fmt.Printf("DeploymentResource does not implement Resource interface. %+v", res) + } + deploymentParams := k8s.DeploymentParams{ + Name: "testdeployment", + Namespace: "default", + Replicas: 2, + SelectorLabels: "app=test", + MetadataLabels: "app=test", + NodeSelectorLabels: "app=test", + Containers: []v1.Container{ + { + Name: "sleep", + Image: "gcr.io/google_containers/pause-amd64:3.0", + ImagePullPolicy: v1.PullAlways, + }, + }, + } + deployParams, err := kubeRepo.Deployment.Create(context.Background(), deploymentParams, false) + if err != nil { + fmt.Println("Error creating Deployment:", err) + } + log.Infof("Created Deployment: %+v", deployParams.(k8s.DeploymentParams).Name) + + deploymentParams = k8s.DeploymentParams{ + Name: "testdeployment", + Namespace: "default", + SelectorLabels: "app=test", + MetadataLabels: "app=test", + Replicas: 4, + Containers: []v1.Container{ + { + Name: "sleep", + Image: "gcr.io/google_containers/pause-amd64:3.0", + ImagePullPolicy: v1.PullAlways, + }, + }, + } + deployParams, err = kubeRepo.Deployment.Update(context.Background(), deploymentParams, false) + if err != nil { + fmt.Println("Error Updating Deployment:", err) + } + log.Infof("Updated Deployment: %+v", deployParams.(k8s.DeploymentParams).Name) + + deploymentParams = k8s.DeploymentParams{ + Deployment: deployParams.(k8s.DeploymentParams).Deployment, + } + + deployment, error := kubeRepo.Deployment.Get(context.Background(), deploymentParams) + if error != nil { + fmt.Println("Error Getting Deployment:", error) + } + log.Infof("Got Deployment: %+v", deployment.(*appsv1.Deployment)) + + deploymentParams = k8s.DeploymentParams{ + Name: "testdeployment", + Namespace: "default", + } + err = kubeRepo.Deployment.Delete(context.Background(), deploymentParams) + if err != nil { + fmt.Println("Error Deleting Deployment:", err) + } + log.Infof("Deleted Deployment: %+v", deploymentParams.Name) + + // Usage of individual component explicitly + deploymentResource := &k8s.DeploymentResource{} + deployment_dup := k8s.DeploymentParams{ + Name: "testdeployment", + Namespace: "default", + Replicas: 2, + SelectorLabels: "app=test", + MetadataLabels: "app=test", + NodeSelectorLabels: "app=test", + Containers: []v1.Container{ + { + Name: "sleep", + Image: "gcr.io/google_containers/pause-amd64:3.0", + ImagePullPolicy: v1.PullAlways, + }, + }, + } + deployParams, err = deploymentResource.Create(context.Background(), deployment_dup, false) + if err != nil { + fmt.Println("Error creating Deployment:", err) + } + log.Infof("Created Deployment: %+v", deployParams.(k8s.DeploymentParams).Name) + + deploymentParams = k8s.DeploymentParams{ + Name: "testdeployment", + Namespace: "default", + } + err = deploymentResource.Delete(context.Background(), deploymentParams) + if err != nil { + fmt.Println("Error Deleting Deployment:", err) + } + log.Infof("Deleted Deployment: %+v", deploymentParams.Name) +} diff --git a/go.mod b/go.mod index cb5b2df..27b6c13 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/opensearch-project/opensearch-go v1.1.0 github.com/prometheus/client_golang v1.15.1 github.com/prometheus/common v0.44.0 + github.com/sirupsen/logrus v1.9.3 k8s.io/api v0.27.1 k8s.io/apimachinery v0.27.1 k8s.io/client-go v0.27.1 @@ -16,7 +17,9 @@ require ( require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 67d5055..bdd0cef 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -110,7 +112,10 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -164,6 +169,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/k8s/deamonset.go b/k8s/deamonset.go new file mode 100644 index 0000000..fc008d3 --- /dev/null +++ b/k8s/deamonset.go @@ -0,0 +1,38 @@ +package k8s + +import ( + "context" + "fmt" + + "k8s.io/client-go/kubernetes" +) + +// Common params shared across the class +type DeamonSetResource struct { + clientSet *kubernetes.Clientset + // DeamonSet resource attributes and metadata +} + +// DeamonSet specific params +type DeamonSetParams struct { +} + +func (p *DeamonSetResource) Create(ctx context.Context, DeamonSetParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Create DeamonSet here") + return nil, nil +} + +func (p *DeamonSetResource) Update(ctx context.Context, DeamonSetParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Updating DeamonSet here") + return nil, nil +} + +func (p *DeamonSetResource) Get(ctx context.Context, DeamonSetParams interface{}) (interface{}, error) { + fmt.Println("Getting DeamonSet here") + return nil, nil +} + +func (p *DeamonSetResource) Delete(ctx context.Context, DeamonSetParams interface{}) error { + fmt.Println("Deleting DeamonSet here") + return nil +} diff --git a/k8s/deployment.go b/k8s/deployment.go new file mode 100644 index 0000000..d9a053a --- /dev/null +++ b/k8s/deployment.go @@ -0,0 +1,338 @@ +package k8s + +import ( + "context" + "fmt" + + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +// Common params shared across the class +type DeploymentResource struct { + clientSet *kubernetes.Clientset + // Deployment resource attributes and metadata +} + +// Deployment specific params +type DeploymentParams struct { + // Name of the deployment + Name string + // Namespace of the deployment + Namespace string + // Replicas of the deployment + Replicas int32 + // Selector labels deployment.Spec.Selector + // semicolon separated string with labels + // Example: "app1=label1;app2=label2" + SelectorLabels string + // Metadata labels deployment.Spec.Template.ObjectSpec.Labels + // semicolon separated string with labels + // Example: "app1=label1;app2=label2" + MetadataLabels string + // NodeAffinity rules to go in RequiredDuringSchedulingIgnoredDuringExecution section + // semicolon separated string with labels + // Example : `app1=["a","b","c"];app2!=["d","e","f"];app3=;app4!=` + // in the above example labels fall under `OpIn;OpOut;Exists;DoesNotExists` + // cases respectively based on their equality signs. Parsing is done automatically. + NodeAffinityPreferred string + // NodeAffinity rules to go in PreferredDuringSchedulingIgnoredDuringExecution section + // semicolon separated string with labels + // Example : `app1=["a","b","c"];app2!=["d","e","f"];app3=;app4!=` + // in the above example labels fall under `OpIn;OpOut;Exists;DoesNotExists` + // cases respectively based on their equality signs. Parsing is done automatically. + NodeAffinityRequired string + // PodAffinity rules to go in RequiredDuringSchedulingIgnoredDuringExecution section + // semicolon separated string with labels + // Example : `app1=["a","b","c"];app2!=["d","e","f"];app3=;app4!=` + // in the above example labels fall under `OpIn;OpOut;Exists;DoesNotExists` + // cases respectively based on their equality signs. Parsing is done automatically. + PodAffinityPreferred string + // PodAffinity rules to go in PreferredDuringSchedulingIgnoredDuringExecution section + // semicolon separated string with labels + // Example : `app1=["a","b","c"];app2!=["d","e","f"];app3=;app4!=` + // in the above example labels fall under `OpIn;OpOut;Exists;DoesNotExists` + // cases respectively based on their equality signs. Parsing is done automatically. + PodAffinityRequired string + // PodAntiAffinityPreferred rules to go in RequiredDuringSchedulingIgnoredDuringExecution section + // semicolon separated string with labels + // Example : `app1=["a","b","c"];app2!=["d","e","f"];app3=;app4!=` + // in the above example labels fall under `OpIn;OpOut;Exists;DoesNotExists` + // cases respectively based on their equality signs. Parsing is done automatically. + PodAntiAffinityPreferred string + // PodAntiAffinityRequired rules to go in PreferredDuringSchedulingIgnoredDuringExecution section + // semicolon separated string with labels + // Example : `app1=["a","b","c"];app2!=["d","e","f"];app3=;app4!=` + // in the above example labels fall under `OpIn;OpOut;Exists;DoesNotExists` + // cases respectively based on their equality signs. Parsing is done automatically. + PodAntiAffinityRequired string + // NodeSelector labels deployment.Spec.Template.Spec.NodeSelector + // semicolon separated string with labels + // Example: "app1=label1;app2=label2" + NodeSelectorLabels string + // Flag to enable hostnetwork + HostNetwork bool + // Service account name + ServiceAccountName string + // List of container specs + Containers []v1.Container + // Deployment object if you already have on in place. + Deployment *appsv1.Deployment +} + +// Method to create a deployment. +func (d *DeploymentResource) Create(ctx context.Context, deployParams interface{}, dryRun bool) (interface{}, error) { + if d.clientSet == nil { + d.clientSet = getClientSet() + if d.clientSet == nil { + return nil, fmt.Errorf("Error while connecting to the cluster") + } + } + params := deployParams.(DeploymentParams) + if params.Name != "" && params.Deployment != nil { + return nil, fmt.Errorf("Invalid params. deployment.Name and deployment.Deployment are mutually exclusive") + } + if params.Name != "" { + deployment := setupDeployment(deployParams.(DeploymentParams)) + params.Deployment = deployment + } + if !dryRun { + // Create the deployment + _, err := d.clientSet.AppsV1().Deployments("default").Create(ctx, params.Deployment, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("Error creating Deployment: %v", err) + } + } + return params, nil +} + +// Method to update a deployment. +func (d *DeploymentResource) Update(ctx context.Context, deployParams interface{}, dryRun bool) (interface{}, error) { + if d.clientSet == nil { + d.clientSet = getClientSet() + if d.clientSet == nil { + return nil, fmt.Errorf("Error while connecting to the cluster") + } + } + params := deployParams.(DeploymentParams) + if params.Name != "" && params.Deployment != nil { + return nil, fmt.Errorf("Invalid params. deployment.Name and deployment.Deployment are mutually exclusive") + } + var deploymentToUpdate *appsv1.Deployment + if params.Name != "" { + currentDeployment, err := d.Get(ctx, params) + if err != nil { + return nil, fmt.Errorf("Error invalid operation. Deployment not found to update") + } + deployment := currentDeployment.(*appsv1.Deployment) + *deployment.Spec.Replicas = params.Replicas + nodeAffinity, podAffinity, podAntiAffinity := getAffinityRules(params) + deployment.Spec.Template.Spec.Affinity = &v1.Affinity{ + NodeAffinity: nodeAffinity, + PodAffinity: podAffinity, + PodAntiAffinity: podAntiAffinity, + } + deployment.Spec.Template.Spec.Containers = params.Containers + deployment.Spec.Template.Spec.NodeSelector = getLabels(params.NodeSelectorLabels) + deployment.Spec.Template.Spec.HostNetwork = params.HostNetwork + deployment.Spec.Template.Spec.ServiceAccountName = params.ServiceAccountName + deploymentToUpdate = deployment + } else { + deploymentToUpdate = params.Deployment + } + params.Deployment = deploymentToUpdate + if !dryRun { + // Patch the deployment + _, err := d.clientSet.AppsV1().Deployments(params.Namespace).Update(ctx, deploymentToUpdate, metav1.UpdateOptions{}) + if err != nil { + return nil, fmt.Errorf("Error patching deployment: %s\n", err.Error()) + } + } + return params, nil +} + +// Method to get a deployment +func (d *DeploymentResource) Get(ctx context.Context, deployParams interface{}) (interface{}, error) { + if d.clientSet == nil { + d.clientSet = getClientSet() + if d.clientSet == nil { + return nil, fmt.Errorf("Error while connecting to the cluster") + } + } + params := deployParams.(DeploymentParams) + if params.Name != "" && params.Deployment != nil { + return nil, fmt.Errorf("Invalid params. deployment.Name and deployment.Deployment are mutually exclusive") + } + if params.Name == "" { + params.Name = params.Deployment.ObjectMeta.Name + params.Namespace = params.Deployment.ObjectMeta.Namespace + } + // Get the deployment object + deployment, err := d.clientSet.AppsV1().Deployments(params.Namespace).Get(ctx, params.Name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("Error getting deployment: %s\n", err.Error()) + } + return deployment, nil +} + +// Method to delete a deployment +func (d *DeploymentResource) Delete(ctx context.Context, deployParams interface{}) error { + if d.clientSet == nil { + d.clientSet = getClientSet() + if d.clientSet == nil { + return fmt.Errorf("Error while connecting to the cluster") + } + } + params := deployParams.(DeploymentParams) + if params.Name != "" && params.Deployment != nil { + return fmt.Errorf("Invalid params. deployment.Name and deployment.Deployment are mutually exclusive") + } + if params.Name == "" { + params.Name = params.Deployment.ObjectMeta.Name + params.Namespace = params.Deployment.ObjectMeta.Namespace + } + // Delete the deployment + err := d.clientSet.AppsV1().Deployments(params.Namespace).Delete(ctx, params.Name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("Error getting deployment: %s\n", err.Error()) + } + return nil +} + +// Private helper method to handle operations with a deployment. + +func getNodeAffinityRequired(nodeAffinityString string) *v1.NodeAffinity { + return &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: generateNodeSelectorRequirement(parseAffinityRules(nodeAffinityString)), + }, + }, + }, + } +} + +func getPodAffinityRequired(podAffinityString string) *v1.PodAffinity { + return &v1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: generateLabelSelectorRequirement(parseAffinityRules(podAffinityString)), + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + } +} + +func getNodeAffinityPreferred(nodeAffinityString string) *v1.NodeAffinity { + return &v1.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{ + { + Weight: 100, + Preference: v1.NodeSelectorTerm{ + MatchExpressions: generateNodeSelectorRequirement(parseAffinityRules(nodeAffinityString)), + }, + }, + }, + } +} + +func getPodAffinityPreferred(podAffinityString string) *v1.PodAffinity { + return &v1.PodAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ + { + Weight: 100, + PodAffinityTerm: v1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: generateLabelSelectorRequirement(parseAffinityRules(podAffinityString)), + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + } +} + +func getAffinityRules(deployParams DeploymentParams) (*v1.NodeAffinity, *v1.PodAffinity, *v1.PodAntiAffinity) { + NodeAffinity := &v1.NodeAffinity{} + PodAffinity := &v1.PodAffinity{} + PodAntiAffinity := &v1.PodAntiAffinity{} + + if deployParams.NodeAffinityPreferred != "" && deployParams.NodeAffinityRequired != "" { + NodeAffinity = &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: getNodeAffinityRequired(deployParams.NodeAffinityRequired).RequiredDuringSchedulingIgnoredDuringExecution, + PreferredDuringSchedulingIgnoredDuringExecution: getNodeAffinityPreferred(deployParams.NodeAffinityPreferred).PreferredDuringSchedulingIgnoredDuringExecution, + } + } else { + if deployParams.NodeAffinityRequired != "" { + NodeAffinity = getNodeAffinityRequired(deployParams.NodeAffinityRequired) + } else if deployParams.NodeAffinityPreferred != "" { + NodeAffinity = getNodeAffinityPreferred(deployParams.NodeAffinityPreferred) + } + } + + if deployParams.PodAffinityRequired != "" && deployParams.PodAffinityPreferred != "" { + PodAffinity = &v1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: getPodAffinityRequired(deployParams.PodAffinityRequired).RequiredDuringSchedulingIgnoredDuringExecution, + PreferredDuringSchedulingIgnoredDuringExecution: getPodAffinityPreferred(deployParams.PodAffinityPreferred).PreferredDuringSchedulingIgnoredDuringExecution, + } + } else { + if deployParams.PodAffinityRequired != "" { + PodAffinity = getPodAffinityRequired(deployParams.PodAffinityRequired) + } else if deployParams.PodAffinityPreferred != "" { + PodAffinity = getPodAffinityPreferred(deployParams.PodAffinityPreferred) + } + + } + + if deployParams.PodAntiAffinityRequired != "" && deployParams.PodAntiAffinityPreferred != "" { + PodAntiAffinity = &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: getPodAffinityRequired(deployParams.PodAntiAffinityRequired).RequiredDuringSchedulingIgnoredDuringExecution, + PreferredDuringSchedulingIgnoredDuringExecution: getPodAffinityPreferred(deployParams.PodAntiAffinityPreferred).PreferredDuringSchedulingIgnoredDuringExecution, + } + } else { + if deployParams.PodAntiAffinityRequired != "" { + PodAntiAffinity = &v1.PodAntiAffinity{RequiredDuringSchedulingIgnoredDuringExecution: getPodAffinityRequired(deployParams.PodAntiAffinityRequired).RequiredDuringSchedulingIgnoredDuringExecution} + } else if deployParams.PodAntiAffinityPreferred != "" { + PodAntiAffinity = &v1.PodAntiAffinity{PreferredDuringSchedulingIgnoredDuringExecution: getPodAffinityPreferred(deployParams.PodAntiAffinityRequired).PreferredDuringSchedulingIgnoredDuringExecution} + } + } + return NodeAffinity, PodAffinity, PodAntiAffinity +} + +func setupDeployment(deployParams DeploymentParams) *appsv1.Deployment { + + NodeAffinity, PodAffinity, PodAntiAffinity := getAffinityRules(deployParams) + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: deployParams.Name, + Namespace: deployParams.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &deployParams.Replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: getLabels(deployParams.SelectorLabels), + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: getLabels(deployParams.MetadataLabels), + }, + Spec: v1.PodSpec{ + ServiceAccountName: deployParams.ServiceAccountName, + HostNetwork: deployParams.HostNetwork, + Containers: deployParams.Containers, + Affinity: &v1.Affinity{ + NodeAffinity: NodeAffinity, + PodAffinity: PodAffinity, + PodAntiAffinity: PodAntiAffinity, + }, + NodeSelector: getLabels(deployParams.NodeSelectorLabels), + }, + }, + }, + } +} diff --git a/k8s/job.go b/k8s/job.go new file mode 100644 index 0000000..7367965 --- /dev/null +++ b/k8s/job.go @@ -0,0 +1,37 @@ +package k8s + +import ( + "context" + "fmt" + + "k8s.io/client-go/kubernetes" +) + +// Common params shared across the class +type JobResource struct { + clientSet *kubernetes.Clientset + // Job resource attributes and metadata +} + +type JobParams struct { +} + +func (p *JobResource) Create(ctx context.Context, JobParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Create Job here") + return nil, nil +} + +func (p *JobResource) Update(ctx context.Context, JobParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Updating Job here") + return nil, nil +} + +func (p *JobResource) Get(ctx context.Context, JobParams interface{}) (interface{}, error) { + fmt.Println("Getting Job here") + return nil, nil +} + +func (p *JobResource) Delete(ctx context.Context, JobParams interface{}) error { + fmt.Println("Deleting Job here") + return nil +} diff --git a/k8s/k8s.go b/k8s/k8s.go new file mode 100644 index 0000000..42d5204 --- /dev/null +++ b/k8s/k8s.go @@ -0,0 +1,43 @@ +package k8s + +import ( + "context" + "fmt" + + "k8s.io/client-go/kubernetes" +) + +// Define the Resource Interface for CRUD operations +type Resource interface { + Create(context.Context, interface{}, bool) (interface{}, error) + Update(context.Context, interface{}, bool) (interface{}, error) + Get(context.Context, interface{}) (interface{}, error) + Delete(context.Context, interface{}) error + // Other resource-specific methods... +} + +// Implement a single repository struct containing all resource-specific structs +type KubernetesRepository struct { + Pod PodResource + Deployment DeploymentResource + ReplicaSet ReplicaSetResource + DeamonSet DeamonSetResource + Job JobResource + clientSet *kubernetes.Clientset + // Other resource-specific structs... +} + +func NewKubernetesRepository() (*KubernetesRepository, error) { + clientSet := getClientSet() + if clientSet == nil { + return nil, fmt.Errorf("Error while connecting to the cluster") + } + return &KubernetesRepository{ + Pod: PodResource{clientSet: clientSet}, + Deployment: DeploymentResource{clientSet: clientSet}, + ReplicaSet: ReplicaSetResource{clientSet: clientSet}, + DeamonSet: DeamonSetResource{clientSet: clientSet}, + Job: JobResource{clientSet: clientSet}, + clientSet: clientSet, + }, nil +} diff --git a/k8s/pod.go b/k8s/pod.go new file mode 100644 index 0000000..5eb56ec --- /dev/null +++ b/k8s/pod.go @@ -0,0 +1,38 @@ +package k8s + +import ( + "context" + "fmt" + + "k8s.io/client-go/kubernetes" +) + +// Common params shared across the class +type PodResource struct { + clientSet *kubernetes.Clientset + // Pod resource attributes and metadata +} + +// Pod specific params +type PodParams struct { +} + +func (p *PodResource) Create(ctx context.Context, podParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Create Pod here") + return nil, nil +} + +func (p *PodResource) Update(ctx context.Context, podParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Updating Pod here") + return nil, nil +} + +func (p *PodResource) Get(ctx context.Context, podParams interface{}) (interface{}, error) { + fmt.Println("Getting Pod here") + return nil, nil +} + +func (p *PodResource) Delete(ctx context.Context, podParams interface{}) error { + fmt.Println("Deleting Pod here") + return nil +} diff --git a/k8s/replicaset.go b/k8s/replicaset.go new file mode 100644 index 0000000..0c2e76d --- /dev/null +++ b/k8s/replicaset.go @@ -0,0 +1,38 @@ +package k8s + +import ( + "context" + "fmt" + + "k8s.io/client-go/kubernetes" +) + +// Implement individual Go structs for each Kubernetes resource +type ReplicaSetResource struct { + clientSet *kubernetes.Clientset + // ReplicaSet resource attributes and metadata +} + +// ReplicaSet specific params +type ReplicaSetParams struct { +} + +func (p *ReplicaSetResource) Create(ctx context.Context, ReplicaSetParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Create ReplicaSet here") + return nil, nil +} + +func (p *ReplicaSetResource) Update(ctx context.Context, ReplicaSetParams interface{}, dryRun bool) (interface{}, error) { + fmt.Println("Updating ReplicaSet here") + return nil, nil +} + +func (p *ReplicaSetResource) Get(ctx context.Context, ReplicaSetParams interface{}) (interface{}, error) { + fmt.Println("Getting ReplicaSet here") + return nil, nil +} + +func (p *ReplicaSetResource) Delete(ctx context.Context, ReplicaSetParams interface{}) error { + fmt.Println("Deleting ReplicaSet here") + return nil +} diff --git a/k8s/utils.go b/k8s/utils.go new file mode 100644 index 0000000..56c6fcc --- /dev/null +++ b/k8s/utils.go @@ -0,0 +1,154 @@ +package k8s + +import ( + "encoding/json" + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +func getClientSet() *kubernetes.Clientset { + restConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), + &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}}).ClientConfig() + if err != nil { + return nil + } + return kubernetes.NewForConfigOrDie(restConfig) +} + +func getLabels(labelString string) map[string]string { + labelMap := make(map[string]string) + labelPairs := strings.Split(labelString, ";") + + for _, labelPair := range labelPairs { + labelPair = strings.TrimSpace(labelPair) + parts := strings.Split(labelPair, "=") + if len(parts) != 2 { + return nil + } + labelMap[parts[0]] = parts[1] + } + return labelMap +} + +func parseAffinityRules(rulesString string) (map[string][]string, map[string][]string, []string, []string) { + inOps := make(map[string][]string) + notInOps := make(map[string][]string) + var exists []string + var doesNotExists []string + + parts := strings.Split(rulesString, ";") + + for _, part := range parts { + part = strings.TrimSpace(part) + if strings.Contains(part, "!=") { + key, value := extractKeyValue(part, "!=") + if value == "" { + doesNotExists = append(doesNotExists, key) + } else { + fillMap(¬InOps, key, value) + } + } else if strings.Contains(part, "=") { + key, value := extractKeyValue(part, "=") + if value == "" { + exists = append(exists, key) + } else { + fillMap(&inOps, key, value) + } + } + } + return inOps, notInOps, exists, doesNotExists +} + +func extractKeyValue(s, separator string) (string, string) { + parts := strings.SplitN(s, separator, 2) + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) +} + +func fillMap(m *map[string][]string, key, value string) { + var temp []string + err := json.Unmarshal([]byte(value), &temp) + if err != nil { + fmt.Println("Error:", err) + return + } + (*m)[key] = temp +} + +func generateNodeSelectorRequirement(inOps, notInOps map[string][]string, exists, doesNotExists []string) []v1.NodeSelectorRequirement { + + var nodeSelectorRequirements []v1.NodeSelectorRequirement + for key, value := range inOps { + nodeSelectorRequirements = append(nodeSelectorRequirements, v1.NodeSelectorRequirement{ + Key: key, + Operator: v1.NodeSelectorOpIn, + Values: value, + }) + } + + for key, value := range notInOps { + nodeSelectorRequirements = append(nodeSelectorRequirements, v1.NodeSelectorRequirement{ + Key: key, + Operator: v1.NodeSelectorOpNotIn, + Values: value, + }) + } + + for _, value := range exists { + nodeSelectorRequirements = append(nodeSelectorRequirements, v1.NodeSelectorRequirement{ + Key: value, + Operator: v1.NodeSelectorOpExists, + }) + } + + for _, value := range doesNotExists { + nodeSelectorRequirements = append(nodeSelectorRequirements, v1.NodeSelectorRequirement{ + Key: value, + Operator: v1.NodeSelectorOpDoesNotExist, + }) + } + + return nodeSelectorRequirements +} + +func generateLabelSelectorRequirement(inOps, notInOps map[string][]string, exists, doesNotExists []string) []metav1.LabelSelectorRequirement { + + var labelSelectorRequirements []metav1.LabelSelectorRequirement + for key, value := range inOps { + labelSelectorRequirements = append(labelSelectorRequirements, metav1.LabelSelectorRequirement{ + Key: key, + Operator: metav1.LabelSelectorOpIn, + Values: value, + }) + } + + for key, value := range notInOps { + labelSelectorRequirements = append(labelSelectorRequirements, metav1.LabelSelectorRequirement{ + Key: key, + Operator: metav1.LabelSelectorOpNotIn, + Values: value, + }) + } + + for _, value := range exists { + labelSelectorRequirements = append(labelSelectorRequirements, metav1.LabelSelectorRequirement{ + Key: value, + Operator: metav1.LabelSelectorOpExists, + }) + } + + for _, value := range doesNotExists { + labelSelectorRequirements = append(labelSelectorRequirements, metav1.LabelSelectorRequirement{ + Key: value, + Operator: metav1.LabelSelectorOpDoesNotExist, + }) + } + + return labelSelectorRequirements +}