Skip to content

Commit

Permalink
feat: allow proxy protobuff integration (okteto#4668)
Browse files Browse the repository at this point in the history
* feat: allow proxy protobuff integration

Signed-off-by: Javier Lopez <[email protected]>

* fix: golangci

Signed-off-by: Javier Lopez <[email protected]>

* add support for protobuff

Signed-off-by: Javier Lopez <[email protected]>

* fix: imports

Signed-off-by: Javier Lopez <[email protected]>

* refactor: address feedback

Signed-off-by: Javier Lopez <[email protected]>

* doc: add explanaiton

Signed-off-by: Javier Lopez <[email protected]>

* init translators

Signed-off-by: Javier Lopez <[email protected]>

* fix: golangci

Signed-off-by: Javier Lopez <[email protected]>

* fix: tests

Signed-off-by: Javier Lopez <[email protected]>

* fix: handle unsupported content type in proxy handler

Signed-off-by: Javier Lopez <[email protected]>

* add metrics

Signed-off-by: Javier Lopez <[email protected]>

* refactor: rename tracking function for unsupported content type

Signed-off-by: Javier Lopez <[email protected]>

* fix: update unsupported content type event name for clarity

Signed-off-by: Javier Lopez <[email protected]>

---------

Signed-off-by: Javier Lopez <[email protected]>
  • Loading branch information
jLopezbarb authored Feb 21, 2025
1 parent 84fa450 commit 21efc74
Show file tree
Hide file tree
Showing 11 changed files with 1,470 additions and 293 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG KUBECTL_VERSION=1.31.5
ARG HELM_VERSION=3.16.4
ARG KUBECTL_VERSION=1.32.1
ARG HELM_VERSION=3.17.0
ARG SYNCTHING_VERSION=1.29.2
ARG OKTETO_REMOTE_VERSION=0.6.2
ARG OKTETO_SUPERVISOR_VERSION=0.4.1
Expand Down
26 changes: 26 additions & 0 deletions pkg/analytics/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2025 The Okteto 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 analytics

const (
unsupportedProxyContentTypeEvent = "Unsupported Proxy Content Type"
contentTypeKey = "contentType"
)

// TrackUnsupportedContentType sends a tracking event to mixpanel when the proxy receives a request with an unsupported content type
func (a *Tracker) TrackUnsupportedContentType(contentType string) {
a.trackFn(unsupportedProxyContentTypeEvent, true, map[string]interface{}{
contentTypeKey: contentType,
})
}
34 changes: 34 additions & 0 deletions pkg/analytics/proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2025 The Okteto 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 analytics

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestProxyUnsupportedContentType(t *testing.T) {
contentType := ""
event := ""
tracker := Tracker{
trackFn: func(e string, success bool, props map[string]any) {
event = e
contentType = props[contentTypeKey].(string)
},
}
tracker.TrackUnsupportedContentType("application/json")
assert.Equal(t, unsupportedProxyContentTypeEvent, event)
assert.Equal(t, "application/json", contentType)
}
2 changes: 2 additions & 0 deletions pkg/deployable/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type ProxyInterface interface {
GetToken() string
SetName(name string)
SetDivert(driver divert.Driver)
InitTranslator()
}

// KubeConfigHandler defines the operations to handle the kubeconfig file
Expand Down Expand Up @@ -224,6 +225,7 @@ func (r *DeployRunner) RunDeploy(ctx context.Context, params DeployParameters) e
r.Proxy.SetDivert(driver)
r.DivertDeployer = driver
}
r.Proxy.InitTranslator()

os.Setenv(constants.OktetoNameEnvVar, params.Name)

Expand Down
1 change: 1 addition & 0 deletions pkg/deployable/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func (f *fakeProxy) SetName(name string) {
func (f *fakeProxy) SetDivert(driver divert.Driver) {
f.Called(driver)
}
func (f *fakeProxy) InitTranslator() {}

type fakeExecutor struct {
mock.Mock
Expand Down
265 changes: 265 additions & 0 deletions pkg/deployable/json_translator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// Copyright 2025 The Okteto 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 deployable

import (
"encoding/json"
"fmt"

"github.com/okteto/okteto/cmd/utils"
"github.com/okteto/okteto/pkg/divert"
"github.com/okteto/okteto/pkg/k8s/labels"
oktetoLog "github.com/okteto/okteto/pkg/log"
"github.com/okteto/okteto/pkg/model"
istioNetworkingV1beta1 "istio.io/api/networking/v1beta1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type jsonTranslator struct {
name string
divertDriver divert.Driver
}

func newJSONTranslator(name string, divertDriver divert.Driver) *jsonTranslator {
return &jsonTranslator{
name: name,
divertDriver: divertDriver,
}
}

func (j *jsonTranslator) Translate(b []byte) ([]byte, error) {
var body map[string]json.RawMessage
if err := json.Unmarshal(b, &body); err != nil {
oktetoLog.Infof("error unmarshalling resource body on proxy: %s", err.Error())
return nil, nil
}

if err := j.translateMetadata(body); err != nil {
return nil, err
}

var typeMeta metav1.TypeMeta
if err := json.Unmarshal(b, &typeMeta); err != nil {
oktetoLog.Infof("error unmarshalling typemeta on proxy: %s", err.Error())
return nil, nil
}

switch typeMeta.Kind {
case "Deployment":
if err := j.translateDeploymentSpec(body); err != nil {
return nil, err
}
case "StatefulSet":
if err := j.translateStatefulSetSpec(body); err != nil {
return nil, err
}
case "Job":
if err := j.translateJobSpec(body); err != nil {
return nil, err
}
case "CronJob":
if err := j.translateCronJobSpec(body); err != nil {
return nil, err
}
case "DaemonSet":
if err := j.translateDaemonSetSpec(body); err != nil {
return nil, err
}
case "ReplicationController":
if err := j.translateReplicationControllerSpec(body); err != nil {
return nil, err
}
case "ReplicaSet":
if err := j.translateReplicaSetSpec(body); err != nil {
return nil, err
}
case "VirtualService":
if err := j.translateVirtualServiceSpec(body); err != nil {
return nil, err
}
}

return json.Marshal(body)
}

func (j *jsonTranslator) translateMetadata(body map[string]json.RawMessage) error {
m, ok := body["metadata"]
if !ok {
return fmt.Errorf("request body doesn't have metadata field")
}

var metadata metav1.ObjectMeta
if err := json.Unmarshal(m, &metadata); err != nil {
oktetoLog.Infof("error unmarshalling objectmeta on proxy: %s", err.Error())
return nil
}

labels.SetInMetadata(&metadata, model.DeployedByLabel, j.name)

if metadata.Annotations == nil {
metadata.Annotations = map[string]string{}
}
if utils.IsOktetoRepo() {
metadata.Annotations[model.OktetoSampleAnnotation] = "true"
}

metadataAsByte, err := json.Marshal(metadata)
if err != nil {
return fmt.Errorf("could not process resource's metadata: %w", err)
}

body["metadata"] = metadataAsByte

return nil
}

func (j *jsonTranslator) translateDeploymentSpec(body map[string]json.RawMessage) error {
var spec appsv1.DeploymentSpec
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling deployment spec on proxy: %s", err.Error())
return nil
}
labels.SetInMetadata(&spec.Template.ObjectMeta, model.DeployedByLabel, j.name)
spec.Template.Spec = j.applyDivertToPod(spec.Template.Spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process deployment's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}

func (j *jsonTranslator) translateStatefulSetSpec(body map[string]json.RawMessage) error {
var spec appsv1.StatefulSetSpec
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling statefulset spec on proxy: %s", err.Error())
return nil
}
labels.SetInMetadata(&spec.Template.ObjectMeta, model.DeployedByLabel, j.name)
spec.Template.Spec = j.applyDivertToPod(spec.Template.Spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process statefulset's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}

func (j *jsonTranslator) translateJobSpec(body map[string]json.RawMessage) error {
var spec batchv1.JobSpec
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling job spec on proxy: %s", err.Error())
return nil
}
labels.SetInMetadata(&spec.Template.ObjectMeta, model.DeployedByLabel, j.name)
spec.Template.Spec = j.applyDivertToPod(spec.Template.Spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process job's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}

func (j *jsonTranslator) translateCronJobSpec(body map[string]json.RawMessage) error {
var spec batchv1.CronJobSpec
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling cronjob spec on proxy: %s", err.Error())
return nil
}
labels.SetInMetadata(&spec.JobTemplate.Spec.Template.ObjectMeta, model.DeployedByLabel, j.name)
spec.JobTemplate.Spec.Template.Spec = j.applyDivertToPod(spec.JobTemplate.Spec.Template.Spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process cronjob's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}

func (j *jsonTranslator) translateDaemonSetSpec(body map[string]json.RawMessage) error {
var spec appsv1.DaemonSetSpec
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling daemonset spec on proxy: %s", err.Error())
return nil
}
labels.SetInMetadata(&spec.Template.ObjectMeta, model.DeployedByLabel, j.name)
spec.Template.Spec = j.applyDivertToPod(spec.Template.Spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process daemonset's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}

func (j *jsonTranslator) translateReplicationControllerSpec(body map[string]json.RawMessage) error {
var spec apiv1.ReplicationControllerSpec
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling replicationcontroller on proxy: %s", err.Error())
return nil
}
labels.SetInMetadata(&spec.Template.ObjectMeta, model.DeployedByLabel, j.name)
spec.Template.Spec = j.applyDivertToPod(spec.Template.Spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process replicationcontroller's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}

func (j *jsonTranslator) translateReplicaSetSpec(body map[string]json.RawMessage) error {
var spec appsv1.ReplicaSetSpec
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling replicaset on proxy: %s", err.Error())
return nil
}
labels.SetInMetadata(&spec.Template.ObjectMeta, model.DeployedByLabel, j.name)
spec.Template.Spec = j.applyDivertToPod(spec.Template.Spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process replicaset's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}
func (j *jsonTranslator) applyDivertToPod(podSpec apiv1.PodSpec) apiv1.PodSpec {
if j.divertDriver == nil {
return podSpec
}
return j.divertDriver.UpdatePod(podSpec)
}

func (j *jsonTranslator) translateVirtualServiceSpec(body map[string]json.RawMessage) error {
if j.divertDriver == nil {
return nil
}

var spec *istioNetworkingV1beta1.VirtualService
if err := json.Unmarshal(body["spec"], &spec); err != nil {
oktetoLog.Infof("error unmarshalling replicaset on proxy: %s", err.Error())
return nil
}
j.divertDriver.UpdateVirtualService(spec)
specAsByte, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("could not process virtual service's spec: %w", err)
}
body["spec"] = specAsByte
return nil
}
Loading

0 comments on commit 21efc74

Please sign in to comment.