-
Notifications
You must be signed in to change notification settings - Fork 38
Add Kube YAML parser/reader #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package parser | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "io" | ||
|
|
||
| "github.com/devfile/library/pkg/testingutil/filesystem" | ||
| "github.com/devfile/library/pkg/util" | ||
| "github.com/pkg/errors" | ||
| "gopkg.in/yaml.v3" | ||
| k8yaml "sigs.k8s.io/yaml" | ||
|
|
||
| routev1 "github.com/openshift/api/route/v1" | ||
| appsv1 "k8s.io/api/apps/v1" | ||
| corev1 "k8s.io/api/core/v1" | ||
| ) | ||
|
|
||
| // YamlSrc specifies the src of the yaml in either Path, URL or Data format | ||
| type YamlSrc struct { | ||
| // Path is a relative or absolute yaml path. | ||
| Path string | ||
| // URL is the URL address of the specific yaml. | ||
| URL string | ||
| // Data is the yaml content in []byte format. | ||
| Data []byte | ||
| } | ||
|
|
||
| // ReadKubernetesYaml reads a yaml Kubernetes file from either the Path, URL or Data provided. | ||
| // It returns Deployments, Services, Routes resources as the primary Kubernetes resources. | ||
| // Other Kubernetes resources are returned as []byte type. Consumers interested in other Kubernetes resources | ||
| // are expected to Unmarshal it to the struct of the respective resource. | ||
| func ReadKubernetesYaml(src YamlSrc, fs filesystem.Filesystem) ([]appsv1.Deployment, []corev1.Service, []routev1.Route, [][]byte, error) { | ||
|
|
||
| var data []byte | ||
| var err error | ||
|
|
||
| if src.URL != "" { | ||
| data, err = util.DownloadFileInMemory(src.URL) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we separate the logic to download / read the Kubernetes yaml file, from the logic to parse the files resources? I.e. maybe rename
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated |
||
| if err != nil { | ||
| return nil, nil, nil, nil, errors.Wrapf(err, "failed to download file %q", src.URL) | ||
|
yangcao77 marked this conversation as resolved.
Outdated
|
||
| } | ||
| } else if src.Path != "" { | ||
| absPath, err := util.GetAbsPath(src.Path) | ||
|
yangcao77 marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| return nil, nil, nil, nil, err | ||
| } | ||
| data, err = fs.ReadFile(absPath) | ||
| if err != nil { | ||
| return nil, nil, nil, nil, errors.Wrapf(err, "failed to read yaml from path %q", src.Path) | ||
| } | ||
| } else if len(src.Data) > 0 { | ||
| data = src.Data | ||
| } | ||
|
|
||
| var values []interface{} | ||
| dec := yaml.NewDecoder(bytes.NewReader(data)) | ||
| for { | ||
| var value interface{} | ||
| err = dec.Decode(&value) | ||
| if err != nil { | ||
| if err == io.EOF { | ||
| break | ||
| } | ||
| return nil, nil, nil, nil, err | ||
| } | ||
| values = append(values, value) | ||
| } | ||
|
|
||
| var deployments []appsv1.Deployment | ||
| var services []corev1.Service | ||
| var routes []routev1.Route | ||
| var otherResources [][]byte | ||
|
|
||
| for _, value := range values { | ||
| var deployment appsv1.Deployment | ||
| var service corev1.Service | ||
| var route routev1.Route | ||
|
|
||
| byteData, err := k8yaml.Marshal(value) | ||
| if err != nil { | ||
| return nil, nil, nil, nil, err | ||
| } | ||
|
|
||
| kubernetesMap := value.(map[string]interface{}) | ||
| kind := kubernetesMap["kind"] | ||
|
|
||
| switch kind { | ||
| case "Deployment": | ||
| err = k8yaml.Unmarshal(byteData, &deployment) | ||
| deployments = append(deployments, deployment) | ||
| case "Service": | ||
| err = k8yaml.Unmarshal(byteData, &service) | ||
| services = append(services, service) | ||
| case "Route": | ||
| err = k8yaml.Unmarshal(byteData, &route) | ||
| routes = append(routes, route) | ||
| default: | ||
| otherResources = append(otherResources, byteData) | ||
| } | ||
|
|
||
| if err != nil { | ||
| return nil, nil, nil, nil, err | ||
| } | ||
| } | ||
|
|
||
| return deployments, services, routes, otherResources, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| package parser | ||
|
|
||
| import ( | ||
| "net" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/devfile/library/pkg/testingutil/filesystem" | ||
| "github.com/devfile/library/pkg/util" | ||
| "github.com/stretchr/testify/assert" | ||
| corev1 "k8s.io/api/core/v1" | ||
| k8yaml "sigs.k8s.io/yaml" | ||
| ) | ||
|
|
||
| func TestReadKubernetesYaml(t *testing.T) { | ||
| const serverIP = "127.0.0.1:9080" | ||
| var data []byte | ||
|
|
||
| fs := filesystem.DefaultFs{} | ||
| absPath, err := util.GetAbsPath("../../../tests/yamls/resources.yaml") | ||
| if err != nil { | ||
| t.Error(err) | ||
| return | ||
| } | ||
|
|
||
| data, err = fs.ReadFile(absPath) | ||
| if err != nil { | ||
| t.Error(err) | ||
| return | ||
| } | ||
|
|
||
| // Mocking the YAML file endpoint on a very basic level | ||
| testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| _, err = w.Write(data) | ||
| if err != nil { | ||
| t.Errorf("Unexpected error while writing data: %v", err) | ||
| } | ||
| })) | ||
| // create a listener with the desired port. | ||
| l, err := net.Listen("tcp", serverIP) | ||
| if err != nil { | ||
| t.Errorf("Unexpected error while creating listener: %v", err) | ||
| return | ||
| } | ||
|
|
||
| // NewUnstartedServer creates a listener. Close that listener and replace | ||
| // with the one we created. | ||
| testServer.Listener.Close() | ||
| testServer.Listener = l | ||
|
|
||
| testServer.Start() | ||
| defer testServer.Close() | ||
|
|
||
| badData := append(data, 59) | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| src YamlSrc | ||
| fs filesystem.Filesystem | ||
| wantErr bool | ||
| wantDeploymentNames []string | ||
| wantServiceNames []string | ||
| wantRouteNames []string | ||
| wantOtherNames []string | ||
| }{ | ||
| { | ||
| name: "Read the YAML from the URL", | ||
| src: YamlSrc{ | ||
| URL: "http://" + serverIP, | ||
| }, | ||
| fs: filesystem.DefaultFs{}, | ||
| wantDeploymentNames: []string{"deploy-sample"}, | ||
| wantServiceNames: []string{"service-sample"}, | ||
| wantRouteNames: []string{"route-sample"}, | ||
| wantOtherNames: []string{"pvc-sample"}, | ||
| }, | ||
| { | ||
| name: "Read the YAML from the Path", | ||
| src: YamlSrc{ | ||
| Path: "../../../tests/yamls/resources.yaml", | ||
| }, | ||
| fs: filesystem.DefaultFs{}, | ||
| wantDeploymentNames: []string{"deploy-sample"}, | ||
| wantServiceNames: []string{"service-sample"}, | ||
| wantRouteNames: []string{"route-sample"}, | ||
| wantOtherNames: []string{"pvc-sample"}, | ||
| }, | ||
| { | ||
| name: "Read the YAML from the Data", | ||
| src: YamlSrc{ | ||
| Data: data, | ||
| }, | ||
| fs: filesystem.DefaultFs{}, | ||
| wantDeploymentNames: []string{"deploy-sample"}, | ||
| wantServiceNames: []string{"service-sample"}, | ||
| wantRouteNames: []string{"route-sample"}, | ||
| wantOtherNames: []string{"pvc-sample"}, | ||
| }, | ||
| { | ||
| name: "Bad URL", | ||
| src: YamlSrc{ | ||
| URL: "http://badurl", | ||
| }, | ||
| fs: filesystem.DefaultFs{}, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "Bad Path", | ||
| src: YamlSrc{ | ||
| Path: "$%^&", | ||
| }, | ||
| fs: filesystem.DefaultFs{}, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "Bad Data", | ||
| src: YamlSrc{ | ||
| Data: badData, | ||
| }, | ||
| fs: filesystem.DefaultFs{}, | ||
| wantErr: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| deployments, services, routes, others, err := ReadKubernetesYaml(tt.src, tt.fs) | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("unexpected error: %v", err) | ||
| return | ||
| } | ||
| for _, deploy := range deployments { | ||
| assert.Contains(t, tt.wantDeploymentNames, deploy.Name) | ||
| } | ||
| for _, svc := range services { | ||
| assert.Contains(t, tt.wantServiceNames, svc.Name) | ||
| } | ||
| for _, route := range routes { | ||
| assert.Contains(t, tt.wantRouteNames, route.Name) | ||
| } | ||
| for _, other := range others { | ||
| pvc := corev1.PersistentVolumeClaim{} | ||
| err = k8yaml.Unmarshal(other, &pvc) | ||
| if err != nil { | ||
| t.Error(err) | ||
| } | ||
| assert.Contains(t, tt.wantOtherNames, pvc.Name) | ||
| } | ||
| }) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.