Skip to content
This repository was archived by the owner on Jul 10, 2024. It is now read-only.

Commit bc8c500

Browse files
committed
Add injector
1 parent 4ff1655 commit bc8c500

File tree

3 files changed

+291
-0
lines changed

3 files changed

+291
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package injector
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"github.com/pkg/errors"
7+
"io"
8+
appsv1 "k8s.io/api/apps/v1"
9+
v1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
"k8s.io/apimachinery/pkg/runtime/schema"
14+
kyamlutil "k8s.io/apimachinery/pkg/util/yaml"
15+
"k8s.io/client-go/kubernetes/scheme"
16+
"os"
17+
"path/filepath"
18+
kyaml "sigs.k8s.io/yaml"
19+
)
20+
21+
type (
22+
Injector interface {
23+
// Injects the given sidecar into all valid Deployment Objects and returns the result as a list of unstructured objects.
24+
Inject(sidecar v1.Container) ([]*unstructured.Unstructured, error)
25+
}
26+
injectorImpl struct {
27+
// The list of Kubernetes objects to traverse during injection. This is a list of
28+
// unstructured objects because we likely won't know the type of all objects
29+
// ahead of time (e.g., when reading multiple objects from a YAML file).
30+
objects []*unstructured.Unstructured
31+
}
32+
)
33+
34+
// Constructs a new Injector with Kubernetes objects derived from the given file path.
35+
func FromYAML(filePath string) (Injector, error) {
36+
yamlContent, err := getFile(filePath)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
// Read the YAML file into a list of unstructured objects.
42+
// This is necessary because the YAML file may contain multiple Kubernetes objects.
43+
// We only want to inject the sidecar into Deployment objects, but we still need to parse all resources.
44+
multidocReader := kyamlutil.NewYAMLReader(bufio.NewReader(bytes.NewReader(yamlContent)))
45+
46+
var objList []*unstructured.Unstructured
47+
for {
48+
raw, err := multidocReader.Read()
49+
if err != nil {
50+
if errors.Is(err, io.EOF) {
51+
break
52+
}
53+
return nil, err
54+
}
55+
56+
obj, err := fromRawObject(raw)
57+
if err != nil && !runtime.IsNotRegisteredError(err) {
58+
return nil, err
59+
}
60+
61+
result, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
objList = append(objList, &unstructured.Unstructured{Object: result})
67+
}
68+
69+
return injectorImpl{objects: objList}, nil
70+
}
71+
72+
func (i injectorImpl) Inject(sidecar v1.Container) ([]*unstructured.Unstructured, error) {
73+
onMap := func(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
74+
if obj.GetKind() != "Deployment" {
75+
return obj, nil
76+
}
77+
78+
var deployment *appsv1.Deployment
79+
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deployment)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
injectedDeployment := deployment.DeepCopy()
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
containers := injectedDeployment.Spec.Template.Spec.Containers
90+
injectedDeployment.Spec.Template.Spec.Containers = append(containers, sidecar)
91+
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
obj.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(injectedDeployment)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
return obj, nil
102+
}
103+
104+
return mapUnstructured(i.objects, onMap)
105+
}
106+
107+
// fromRawObject converts the given raw Kubernetes object into a runtime.Object.
108+
func fromRawObject(raw []byte) (runtime.Object, error) {
109+
var typeMeta metav1.TypeMeta
110+
if err := kyaml.Unmarshal(raw, &typeMeta); err != nil {
111+
return nil, err
112+
}
113+
114+
gvk := schema.FromAPIVersionAndKind(typeMeta.APIVersion, typeMeta.Kind)
115+
obj, err := scheme.Scheme.New(gvk)
116+
if err != nil {
117+
return nil, err
118+
}
119+
120+
if err := kyaml.Unmarshal(raw, obj); err != nil {
121+
return nil, err
122+
}
123+
124+
return obj, nil
125+
}
126+
127+
func getFile(filePath string) ([]byte, error) {
128+
fileDir, fileName := filepath.Split(filePath)
129+
130+
absOutputDir, err := filepath.Abs(fileDir)
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
// Check for directory existence
136+
if _, staterr := os.Stat(absOutputDir); os.IsNotExist(staterr) {
137+
return nil, errors.Wrapf(staterr, "directory %s does not exist", absOutputDir)
138+
}
139+
140+
absPath := filepath.Join(absOutputDir, fileName)
141+
142+
// Check for existence of file
143+
if _, staterr := os.Stat(absPath); os.IsNotExist(staterr) {
144+
return nil, errors.Wrapf(staterr, "file %s does not exist", absPath)
145+
}
146+
147+
return os.ReadFile(absPath)
148+
}
149+
150+
// mapUnstructured applies the given transformer function to each unstructured Kubernetes item in the given list.
151+
// If the transformer function returns an error, the error is returned immediately.
152+
func mapUnstructured(
153+
objList []*unstructured.Unstructured,
154+
transformer func(*unstructured.Unstructured) (*unstructured.Unstructured, error),
155+
) ([]*unstructured.Unstructured, error) {
156+
if objList == nil {
157+
return nil, nil
158+
}
159+
160+
results := make([]*unstructured.Unstructured, 0, len(objList))
161+
for _, item := range objList {
162+
result, err := transformer(item)
163+
if err != nil {
164+
return nil, err
165+
}
166+
results = append(results, result)
167+
}
168+
169+
return results, nil
170+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package injector
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
appsv1 "k8s.io/api/apps/v1"
6+
v1 "k8s.io/api/core/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
"k8s.io/apimachinery/pkg/runtime"
10+
"testing"
11+
)
12+
13+
func Test_Inject(t *testing.T) {
14+
toUnstructured := func(obj runtime.Object) *unstructured.Unstructured {
15+
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj)
16+
if err != nil {
17+
panic(err)
18+
}
19+
20+
return &unstructured.Unstructured{Object: u}
21+
}
22+
23+
appendContainer := func(deployment *appsv1.Deployment, container v1.Container) *appsv1.Deployment {
24+
injectedDeployment := deployment.DeepCopy()
25+
containers := injectedDeployment.Spec.Template.Spec.Containers
26+
injectedDeployment.Spec.Template.Spec.Containers = append(containers, container)
27+
28+
return injectedDeployment
29+
}
30+
31+
// GIVEN
32+
dummyDeployment1 := &appsv1.Deployment{
33+
TypeMeta: metav1.TypeMeta{
34+
Kind: "Deployment",
35+
APIVersion: "apps/v1",
36+
},
37+
ObjectMeta: metav1.ObjectMeta{
38+
Name: "test-deploy-1",
39+
},
40+
Spec: appsv1.DeploymentSpec{
41+
Template: v1.PodTemplateSpec{
42+
Spec: v1.PodSpec{
43+
Containers: []v1.Container{
44+
{
45+
Name: "nginx",
46+
Image: "nginx",
47+
},
48+
},
49+
},
50+
},
51+
},
52+
}
53+
dummyDeployment2 := &appsv1.Deployment{
54+
TypeMeta: metav1.TypeMeta{
55+
Kind: "Deployment",
56+
APIVersion: "apps/v1",
57+
},
58+
ObjectMeta: metav1.ObjectMeta{
59+
Name: "test-deploy-2",
60+
},
61+
Spec: appsv1.DeploymentSpec{
62+
Template: v1.PodTemplateSpec{
63+
Spec: v1.PodSpec{
64+
Containers: []v1.Container{
65+
{
66+
Name: "echo-server",
67+
Image: "ghcr.io/wzshiming/echoserver/echoserver:v0.0.1",
68+
},
69+
},
70+
},
71+
},
72+
},
73+
}
74+
75+
sidecar := v1.Container{Name: "sidecar", Image: "fake-image"}
76+
expectedDeployment1 := appendContainer(dummyDeployment1, sidecar)
77+
expectedDeployment2 := appendContainer(dummyDeployment2, sidecar)
78+
79+
injector := injectorImpl{
80+
objects: []*unstructured.Unstructured{
81+
toUnstructured(dummyDeployment1),
82+
toUnstructured(dummyDeployment2),
83+
},
84+
}
85+
86+
expected := []*unstructured.Unstructured{
87+
toUnstructured(expectedDeployment1),
88+
toUnstructured(expectedDeployment2),
89+
}
90+
91+
// WHEN
92+
actual, err := injector.Inject(sidecar)
93+
94+
// THEN
95+
if assert.NoError(t, err) {
96+
assert.Equal(t, expected, actual)
97+
}
98+
}

cmd/internal/kube/test_resource/resources.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,26 @@ spec:
1616
containers:
1717
- name: test-container
1818
image: ghcr.io/wzshiming/echoserver/echoserver:v0.0.1
19+
---
20+
apiVersion: apps/v1
21+
kind: Deployment
22+
metadata:
23+
name: patch-demo
24+
spec:
25+
replicas: 2
26+
selector:
27+
matchLabels:
28+
app: nginx
29+
template:
30+
metadata:
31+
labels:
32+
app: nginx
33+
spec:
34+
containers:
35+
- name: patch-demo-ctr
36+
image: nginx
37+
tolerations:
38+
- effect: NoSchedule
39+
key: dedicated
40+
value: test-team
41+

0 commit comments

Comments
 (0)