Skip to content

Commit

Permalink
feat: enable setting custom labels and container portname and deploym…
Browse files Browse the repository at this point in the history
…ent only mode
  • Loading branch information
vandot committed Jun 15, 2023
1 parent 501d4fc commit 4f3663b
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 65 deletions.
23 changes: 19 additions & 4 deletions cmd/expose.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import (

var Reuse bool
var Force bool
var DeploymentOnly bool
var PortName string
var ServiceType string
var NodeSelectorTags []string
var DeploymentLabels []string

var exposeCmd = &cobra.Command{
Use: "expose [flags] SERVICE_NAME [ports]",
Expand Down Expand Up @@ -50,22 +53,31 @@ ktunnel expose redis 6379
nodeSelectorTags := map[string]string{}
for _, tag := range NodeSelectorTags {
parsed := strings.Split(tag, "=")
log.Error(parsed)
if len(parsed) != 2 {
log.Errorf("failed to parse node selector tag: %v", tag)
} else {
nodeSelectorTags[parsed[0]] = parsed[1]
}
}

deploymentLabels := map[string]string{}
for _, label := range DeploymentLabels {
parsed := strings.Split(label, "=")
if len(parsed) != 2 {
log.Errorf("failed to parse deployment label: %v", label)
} else {
deploymentLabels[parsed[0]] = parsed[1]
}
}

if Force {
err := k8s.TeardownExposedService(Namespace, svcName, &KubeContext)
err := k8s.TeardownExposedService(Namespace, svcName, &KubeContext, DeploymentOnly)
if err != nil {
log.Infof("Force delete: Failed deleting k8s objects: %s", err)
}
}

err := k8s.ExposeAsService(&Namespace, &svcName, port, Scheme, ports, ServerImage, Reuse, readyChan, nodeSelectorTags, CertFile, KeyFile, ServiceType, &KubeContext)
err := k8s.ExposeAsService(&Namespace, &svcName, port, Scheme, ports, PortName, ServerImage, Reuse, DeploymentOnly, readyChan, nodeSelectorTags, deploymentLabels, CertFile, KeyFile, ServiceType, &KubeContext)
if err != nil {
log.Fatalf("Failed to expose local machine as a service: %v", err)
}
Expand All @@ -85,7 +97,7 @@ ktunnel expose redis 6379
}
cancel()
if !Reuse {
err := k8s.TeardownExposedService(Namespace, svcName, &KubeContext)
err := k8s.TeardownExposedService(Namespace, svcName, &KubeContext, DeploymentOnly)
if err != nil {
log.Errorf("Failed deleting k8s objects: %s", err)
}
Expand Down Expand Up @@ -143,8 +155,11 @@ func init() {
exposeCmd.Flags().StringVar(&CertFile, "cert", "", "TLS certificate file")
exposeCmd.Flags().StringVar(&KeyFile, "key", "", "TLS key file")
exposeCmd.Flags().StringVar(&ServiceType, "service-type", "ClusterIP", "exposed service type (ClusterIP, NodePort, LoadBalancer or ExternalName)")
exposeCmd.Flags().StringVar(&PortName, "portname", "", "specify container port name")
exposeCmd.Flags().BoolVarP(&Reuse, "reuse", "r", false, "delete k8s objects before expose")
exposeCmd.Flags().BoolVarP(&Force, "force", "f", false, "deployment & service will be removed before")
exposeCmd.Flags().BoolVarP(&DeploymentOnly, "deployment-only", "d", false, "create only deployment")
exposeCmd.Flags().StringSliceVarP(&NodeSelectorTags, "node-selector-tags", "q", []string{}, "tag and value seperated by the '=' character (i.e kubernetes.io/os=linux)")
exposeCmd.Flags().StringSliceVarP(&DeploymentLabels, "deployment-labels", "l", []string{}, "comma separated list of labels and values seperated by the '=' character (i.e app=application,env=prod)")
rootCmd.AddCommand(exposeCmd)
}
3 changes: 3 additions & 0 deletions docs/ktunnel_expose.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ ktunnel expose redis 6379
-c, --ca-file string TLS cert auth file
--cert string TLS certificate file
--context string Kubernetes Context
-d, --deployment-only create only deployment
-f, --force deployment & service will be removed before
-h, --help help for expose
--key string TLS key file
-l, --deployment-labels strings comma separated list of labels and values seperated by the '=' character (i.e name=app,env=prod)
-n, --namespace string Namespace (default "default")
-q, --node-selector-tags strings tag and value seperated by the '=' character (i.e kubernetes.io/os=linux)
--portname string specify container port name
-r, --reuse delete k8s objects before expose
-s, --scheme string Connection scheme (default "tcp")
-o, --server-host-override string Server name use to verify the hostname returned by the TLS handshake
Expand Down
24 changes: 11 additions & 13 deletions pkg/k8s/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,33 +152,31 @@ func newContainer(port int, image string, containerPorts []apiv1.ContainerPort,
}
}

func newDeployment(namespace, name string, port int, image string, ports []apiv1.ContainerPort, selector map[string]string, cert, key string) *appsv1.Deployment {
func newDeployment(namespace, name string, port int, image string, ports []apiv1.ContainerPort, selector map[string]string, deploymentLabels map[string]string, cert, key string) *appsv1.Deployment {
replicas := int32(1)
labels := map[string]string{
"app.kubernetes.io/name": name,
"app.kubernetes.io/instance": name,
}
for key, value := range deploymentLabels {
labels[key] = value
}
co := newContainer(port, image, ports, cert, key)
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: map[string]string{
"app.kubernetes.io/name": name,
"app.kubernetes.io/instance": name,
},
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app.kubernetes.io/name": name,
"app.kubernetes.io/instance": name,
},
MatchLabels: labels,
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app.kubernetes.io/name": name,
"app.kubernetes.io/instance": name,
},
Labels: labels,
},
Spec: apiv1.PodSpec{
NodeSelector: selector,
Expand Down
103 changes: 55 additions & 48 deletions pkg/k8s/exposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (
)

var supportedSchemes = map[string]v12.Protocol{
"tcp": v12.ProtocolTCP,
"udp": v12.ProtocolUDP,
"tcp": v12.ProtocolTCP,
"udp": v12.ProtocolUDP,
"grpc-web": v12.ProtocolTCP,
}

func ExposeAsService(namespace, name *string, tunnelPort int, scheme string, rawPorts []string, image string, Reuse bool, readyChan chan<- bool, nodeSelectorTags map[string]string, cert, key string, serviceType string, kubecontext *string) error {
func ExposeAsService(namespace, name *string, tunnelPort int, scheme string, rawPorts []string, portName string, image string, Reuse bool, DeploymentOnly bool, readyChan chan<- bool, nodeSelectorTags map[string]string, deploymentLabels map[string]string, cert, key string, serviceType string, kubecontext *string) error {
getClients(namespace, kubecontext)

ports := make([]v12.ServicePort, len(rawPorts))
Expand All @@ -40,6 +40,9 @@ func ExposeAsService(namespace, name *string, tunnelPort int, scheme string, raw
continue
}
portname := fmt.Sprintf("%s-%d", scheme, parsed.Source)
if portName != "" {
portname = portName
}
ports[i] = v12.ServicePort{
Protocol: protocol,
Name: portname,
Expand All @@ -57,7 +60,7 @@ func ExposeAsService(namespace, name *string, tunnelPort int, scheme string, raw
}
}

deployment := newDeployment(*namespace, *name, tunnelPort, image, ctrPorts, nodeSelectorTags, cert, key)
deployment := newDeployment(*namespace, *name, tunnelPort, image, ctrPorts, nodeSelectorTags, deploymentLabels, cert, key)

service := newService(*namespace, *name, ports, v12.ServiceType(serviceType))

Expand Down Expand Up @@ -104,62 +107,66 @@ func ExposeAsService(namespace, name *string, tunnelPort int, scheme string, raw
return errors.New("error creating deployment")
}

var newSvc *v12.Service
serviceCreated := false
existingService, err := svcClient.Get(context.Background(), *name, v1.GetOptions{})
if err != nil && apierrors.IsNotFound(err) {

newSvc, err = svcClient.Create(context.Background(), service, v1.CreateOptions{
TypeMeta: v1.TypeMeta{},
DryRun: nil,
FieldManager: "",
})

if err != nil {
return err
if !DeploymentOnly {
var newSvc *v12.Service
serviceCreated := false
existingService, err := svcClient.Get(context.Background(), *name, v1.GetOptions{})
if err != nil && apierrors.IsNotFound(err) {

newSvc, err = svcClient.Create(context.Background(), service, v1.CreateOptions{
TypeMeta: v1.TypeMeta{},
DryRun: nil,
FieldManager: "",
})

if err != nil {
return err
}
serviceCreated = true
}
serviceCreated = true
}
if !serviceCreated && Reuse {
// Copy labels and selectors to prevent PATCH issue with immutable fields
service.Labels = existingService.Labels
service.Spec.Selector = existingService.Spec.Selector

patch, err := json.Marshal(service)
if err != nil {
return err
if !serviceCreated && Reuse {
// Copy labels and selectors to prevent PATCH issue with immutable fields
service.Labels = existingService.Labels
service.Spec.Selector = existingService.Spec.Selector

patch, err := json.Marshal(service)
if err != nil {
return err
}
newSvc, err = svcClient.Patch(context.Background(), *name, types.MergePatchType, patch, v1.PatchOptions{
TypeMeta: v1.TypeMeta{},
DryRun: nil,
FieldManager: "",
})
time.Sleep(time.Millisecond * 300)
if err != nil {
return err
}
}
newSvc, err = svcClient.Patch(context.Background(), *name, types.MergePatchType, patch, v1.PatchOptions{
TypeMeta: v1.TypeMeta{},
DryRun: nil,
FieldManager: "",
})
time.Sleep(time.Millisecond * 300)
if err != nil {
return err
if newSvc == nil {
if !serviceCreated {
return errors.New("service with same name already exists")
}
return errors.New("error in creating service")
}
}
if newSvc == nil {
if !serviceCreated {
return errors.New("service with same name already exists")
}
return errors.New("error in creating service")
log.Infof("Exposed service's cluster ip is: %s", newSvc.Spec.ClusterIP)
}

log.Infof("Exposed service's cluster ip is: %s", newSvc.Spec.ClusterIP)
watchForReady(deployment, readyChan)
return nil
}

func TeardownExposedService(namespace, name string, kubecontext *string) error {
func TeardownExposedService(namespace, name string, kubecontext *string, DeploymentOnly bool) error {
getClients(&namespace, kubecontext)
log.Infof("Deleting service %s", name)
err := svcClient.Delete(context.Background(), name, v1.DeleteOptions{})
if err != nil {
return err
if !DeploymentOnly {
log.Infof("Deleting service %s", name)
err := svcClient.Delete(context.Background(), name, v1.DeleteOptions{})
if err != nil {
return err
}
}
log.Infof("Deleting deployment %s", name)
err = deploymentsClient.Delete(context.Background(), name, v1.DeleteOptions{})
err := deploymentsClient.Delete(context.Background(), name, v1.DeleteOptions{})
if err != nil {
return err
}
Expand Down

0 comments on commit 4f3663b

Please sign in to comment.