Skip to content

Commit

Permalink
feature(main): add cepctl feature (#65)
Browse files Browse the repository at this point in the history
* feature(main) add ctl feature
  • Loading branch information
cuisongliu authored Jan 25, 2022
1 parent 250d215 commit df37bde
Show file tree
Hide file tree
Showing 21 changed files with 539 additions and 118 deletions.
18 changes: 17 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,23 @@ builds:
goarch:
- amd64
- arm64
main: ./cmd
main: ./cmd/endpoints-operator
ldflags:
- -X github.com/sealyun/endpoints-operator/library/version.gitVersion={{.Version}}
- -X github.com/sealyun/endpoints-operator/library/version.gitCommit={{.ShortCommit}}
- -X github.com/sealyun/endpoints-operator/library/version.buildDate={{.Date}}
- -s -w
- env:
- CGO_ENABLED=0
id: cepctl
binary: cepctl
goos:
- linux
- darwin
goarch:
- amd64
- arm64
main: ./cmd/cepctl
ldflags:
- -X github.com/sealyun/endpoints-operator/library/version.gitVersion={{.Version}}
- -X github.com/sealyun/endpoints-operator/library/version.gitCommit={{.ShortCommit}}
Expand Down
51 changes: 51 additions & 0 deletions client/cep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright © 2022 The sealyun Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"context"
"github.com/sealyun/endpoints-operator/api/network/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
)

type Cep struct {
gvr schema.GroupVersionResource
client dynamic.Interface
}

func NewCep(client dynamic.Interface) *Cep {
c := &Cep{}
c.gvr = schema.GroupVersionResource{Group: v1beta1.GroupName, Version: v1beta1.GroupVersion.Version, Resource: "clusterendpoints"}
//NewKubernetesClient(NewKubernetesOptions("", ""))
c.client = client
return c
}

func (c *Cep) CreateCR(ctx context.Context, endpoint *v1beta1.ClusterEndpoint) error {
endpoint.APIVersion = v1beta1.GroupVersion.String()
endpoint.Kind = "ClusterEndpoint"
_, err := c.client.Resource(c.gvr).Namespace(endpoint.Namespace).Create(ctx, runtimeConvertUnstructured(endpoint), v1.CreateOptions{})
return err
}

func (c *Cep) DeleteCR(ctx context.Context, namespace, name string) error {
return c.client.Resource(c.gvr).Namespace(namespace).Delete(ctx, name, v1.DeleteOptions{})
}

func (c *Cep) DeleteCRs(ctx context.Context, namespace string, options v1.ListOptions) error {
return c.client.Resource(c.gvr).Namespace(namespace).DeleteCollection(ctx, v1.DeleteOptions{}, options)
}
100 changes: 100 additions & 0 deletions client/kube.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright © 2022 The sealyun Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"github.com/sealyun/endpoints-operator/library/file"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"os"
"path"
)

type Client interface {
Kubernetes() kubernetes.Interface
KubernetesDynamic() dynamic.Interface
Config() *rest.Config
}

type kubernetesClient struct {
// kubernetes client interface
k8s kubernetes.Interface
k8sDynamic dynamic.Interface
// discovery client
config *rest.Config
}

type KubernetesOptions struct {
// kubernetes clientset qps
// +optional
QPS float32 `json:"qps,omitempty" yaml:"qps"`
// kubernetes clientset burst
// +optional
Burst int `json:"burst,omitempty" yaml:"burst"`
Kubeconfig string
Master string
Config *rest.Config `json:"-" yaml:"-"`
}

// NewKubernetesOptions returns a `zero` instance
func NewKubernetesOptions(kubeConfig, master string) *KubernetesOptions {
kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
if kubeConfig == "" && kubeconfigPath != "" {
kubeConfig = kubeconfigPath
}
if kubeconfigPath == "" {
kubeConfig = path.Join(file.GetUserHomeDir(), clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)
}
return &KubernetesOptions{
QPS: 1e6,
Burst: 1e6,
Kubeconfig: kubeConfig,
Master: master,
}
}

// NewKubernetesClient creates a KubernetesClient
func NewKubernetesClient(options *KubernetesOptions) Client {
config := options.Config
var err error
if config == nil {
config, err = clientcmd.BuildConfigFromFlags(options.Master, options.Kubeconfig)
if err != nil {
return nil
}
}
config.QPS = options.QPS
config.Burst = options.Burst
var k kubernetesClient
k.k8s = kubernetes.NewForConfigOrDie(config)
k.k8sDynamic = dynamic.NewForConfigOrDie(config)
k.config = config

return &k
}

func (k *kubernetesClient) Kubernetes() kubernetes.Interface {
return k.k8s
}

func (k *kubernetesClient) Config() *rest.Config {
return k.config
}

func (k *kubernetesClient) KubernetesDynamic() dynamic.Interface {
return k.k8sDynamic
}
32 changes: 32 additions & 0 deletions client/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright © 2022 The sealyun Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"github.com/sealyun/endpoints-operator/library/convert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

func runtimeConvertUnstructured(from runtime.Object) *unstructured.Unstructured {
to, ok := from.(*unstructured.Unstructured)
if ok {
return to
}
if to, err := convert.ResourceToUnstructured(from); err == nil {
return to
}
return nil
}
137 changes: 137 additions & 0 deletions cmd/cepctl/app/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
Copyright 2022 The sealyun Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package app

import (
"context"
"errors"
"fmt"
"github.com/sealyun/endpoints-operator/api/network/v1beta1"
"github.com/sealyun/endpoints-operator/client"
"github.com/sealyun/endpoints-operator/cmd/cepctl/app/options"
"github.com/spf13/cobra"
v1 "k8s.io/api/core/v1"
v1opts "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/json"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/term"
"k8s.io/klog/v2"
"os"
"sigs.k8s.io/yaml"
)

func NewCommand() *cobra.Command {
s := options.NewOptions()

cmd := &cobra.Command{
Use: "cepctl",
Short: "cepctl is cli for cluster-endpoint",
Run: func(cmd *cobra.Command, args []string) {
if errs := s.Validate(); len(errs) != 0 {
klog.Error(utilerrors.NewAggregate(errs))
os.Exit(1)
}
if err := run(s, context.Background()); err != nil {
klog.Error(err)
os.Exit(1)
}
},
SilenceUsage: true,
}

fs := cmd.Flags()
namedFlagSets := s.Flags()

for _, f := range namedFlagSets.FlagSets {
fs.AddFlagSet(f)
}

usageFmt := "Usage:\n %s\n"
cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
cliflag.PrintSections(cmd.OutOrStdout(), namedFlagSets, cols)
})
return cmd
}

func run(s *options.Options, ctx context.Context) error {
cli := client.NewKubernetesClient(client.NewKubernetesOptions(s.KubeConfig, s.Master))
if cli == nil {
return errors.New("build kube client error")
}
cep := &v1beta1.ClusterEndpoint{}
cep.Namespace = s.Namespace
cep.Name = s.Name
cep.Spec.PeriodSeconds = s.PeriodSeconds
svc, err := cli.Kubernetes().CoreV1().Services(s.Namespace).Get(ctx, s.Name, v1opts.GetOptions{})
if err != nil {
return err
}
klog.V(4).InfoS("get service", "name", s.Name, "namespace", s.Namespace, "spec", svc.Spec)
if svc.Spec.ClusterIP == v1.ClusterIPNone {
return errors.New("not support clusterIP=None service")
}
ports := make([]v1beta1.ServicePort, len(svc.Spec.Ports))
for i, p := range svc.Spec.Ports {
enable := s.Probe
ports[i] = v1beta1.ServicePort{
Handler: v1beta1.Handler{
TCPSocket: &v1beta1.TCPSocketAction{Enable: enable},
},
TimeoutSeconds: 1,
SuccessThreshold: 1,
FailureThreshold: 3,
Name: p.Name,
Protocol: p.Protocol,
Port: p.Port,
TargetPort: p.TargetPort.IntVal,
}
}
cep.Spec.Ports = ports
cep.Spec.ClusterIP = svc.Spec.ClusterIP
ep, _ := cli.Kubernetes().CoreV1().Endpoints(s.Namespace).Get(ctx, s.Name, v1opts.GetOptions{})
if ep != nil {
klog.V(4).InfoS("get endpoint", "name", s.Name, "namespace", s.Namespace, "subsets", ep.Subsets)
if len(ep.Subsets) > 1 {
return errors.New("not support endpoint subsets length more than 1. Please spilt it")
}
cep.Spec.Hosts = convertAddress(ep.Subsets[0].Addresses)
}
configJson, _ := json.Marshal(cep)
configYaml, _ := yaml.Marshal(cep)
klog.V(4).InfoS("generator cep", "name", s.Name, "namespace", s.Namespace, "config", string(configJson))

if s.Output == "yaml" {
println(string(configYaml))
return nil
}
if s.Output == "json" {
println(string(configJson))
return nil
}
return nil
}

func convertAddress(addresses []v1.EndpointAddress) []string {
eas := make([]string, 0)
for _, s := range addresses {
eas = append(eas, s.IP)
}
return eas
}
Loading

0 comments on commit df37bde

Please sign in to comment.