1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-13 19:28:55 +00:00

refactor: introduce resource package in cli (#8256)

* refactor: introduce resource package in cli

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-09-05 01:25:06 +02:00 committed by GitHub
parent b2515154f3
commit b6e18d7f29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 161 additions and 244 deletions

View file

@ -164,7 +164,7 @@ func checkResult(test api.TestResults, fs billy.Filesystem, resoucePath string,
}
// fallback on deprecated field
if test.PatchedResource != "" {
equals, err := getAndCompareResource(test.PatchedResource, response.PatchedResource, fs, resoucePath, false)
equals, err := getAndCompareResource(response.PatchedResource, fs, filepath.Join(resoucePath, test.PatchedResource))
if err != nil {
return false, err.Error(), "Resource error"
}
@ -173,7 +173,7 @@ func checkResult(test api.TestResults, fs billy.Filesystem, resoucePath string,
}
}
if test.GeneratedResource != "" {
equals, err := getAndCompareResource(test.GeneratedResource, rule.GeneratedResource(), fs, resoucePath, true)
equals, err := getAndCompareResource(rule.GeneratedResource(), fs, filepath.Join(resoucePath, test.GeneratedResource))
if err != nil {
return false, err.Error(), "Resource error"
}

View file

@ -0,0 +1,24 @@
package test
import (
"fmt"
"github.com/go-git/go-billy/v5"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource"
unstructuredutils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/unstructured"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func getAndCompareResource(actualResource unstructured.Unstructured, fs billy.Filesystem, path string) (bool, error) {
expectedResource, err := resource.GetResourceFromPath(fs, path)
if err != nil {
return false, fmt.Errorf("Error: failed to load resources (%s)", err)
}
unstructuredutils.FixupGenerateLabels(actualResource)
unstructuredutils.FixupGenerateLabels(*expectedResource)
equals, err := unstructuredutils.Compare(actualResource, *expectedResource, true)
if err != nil {
return false, fmt.Errorf("Error: failed to compare resources (%s)", err)
}
return equals, nil
}

View file

@ -1,63 +0,0 @@
package test
// import (
// "reflect"
// "testing"
// "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/api"
// )
// func Test_loadTest(t *testing.T) {
// tests := []struct {
// name string
// data []byte
// want *api.Test
// wantErr bool
// }{{
// name: "invalid schema",
// data: []byte(`
// - name: mytest
// policies:
// - pol.yaml
// resources:
// - pod.yaml
// results:
// - policy: evil-policy-match-foreign-pods
// rule: evil-validation
// resource: nginx
// status: pass
// `),
// want: nil,
// wantErr: true,
// }, {
// name: "unknown field",
// data: []byte(`
// name: mytest
// policies:
// - pol.yaml
// resources:
// - pod.yaml
// results:
// - policy: evil-policy-match-foreign-pods
// rule: evil-validation
// resource: nginx
// foo: bar
// result: pass
// `),
// want: nil,
// wantErr: true,
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// got, err := loadTest(tt.data)
// if (err != nil) != tt.wantErr {
// t.Errorf("loadTest() error = %v, wantErr %v", err, tt.wantErr)
// return
// }
// if !reflect.DeepEqual(got, tt.want) {
// t.Errorf("loadTest() = %v, want %v", got, tt.want)
// }
// })
// }
// }

View file

@ -3,7 +3,6 @@ package test
import (
"fmt"
"os"
"path/filepath"
"github.com/go-git/go-billy/v5"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
@ -14,7 +13,6 @@ import (
pathutils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/path"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
unstructuredutils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/unstructured"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/background/generate"
"github.com/kyverno/kyverno/pkg/clients/dclient"
@ -290,35 +288,3 @@ func selectResourcesForCheckInternal(resources []*unstructured.Unstructured, val
}
return checkableResources, duplicates, unused
}
// getAndCompareResource --> Get the patchedResource or generatedResource from the path provided by user
// And compare this resource with engine generated resource.
func getAndCompareResource(
path string,
actualResource unstructured.Unstructured,
fs billy.Filesystem,
policyResourcePath string,
isGenerate bool,
) (bool, error) {
resourceType := "patchedResource"
if isGenerate {
resourceType = "generatedResource"
}
// TODO fix the way we handle git vs non-git paths (probably at the loading phase)
if fs == nil {
path = filepath.Join(policyResourcePath, path)
}
expectedResource, err := common.GetResourceFromPath(fs, path, fs != nil, policyResourcePath, resourceType)
if err != nil {
return false, fmt.Errorf("Error: failed to load resources (%s)", err)
}
if isGenerate {
unstructuredutils.FixupGenerateLabels(actualResource)
unstructuredutils.FixupGenerateLabels(expectedResource)
}
equals, err := unstructuredutils.Compare(actualResource, expectedResource, true)
if err != nil {
return false, fmt.Errorf("Error: failed to compare resources (%s)", err)
}
return equals, nil
}

View file

@ -0,0 +1,121 @@
package resource
import (
"context"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/go-git/go-billy/v5"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/source"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned/scheme"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"
)
func GetUnstructuredResources(resourceBytes []byte) ([]*unstructured.Unstructured, error) {
var resources []*unstructured.Unstructured
documents, err := yamlutils.SplitDocuments(resourceBytes)
if err != nil {
return nil, err
}
for _, document := range documents {
resource, err := YamlToUnstructured(document)
if err != nil {
return nil, err
}
resources = append(resources, resource)
}
return resources, nil
}
func YamlToUnstructured(resourceYaml []byte) (*unstructured.Unstructured, error) {
decode := scheme.Codecs.UniversalDeserializer().Decode
_, metaData, decodeErr := decode(resourceYaml, nil, nil)
if decodeErr != nil {
if !strings.Contains(decodeErr.Error(), "no kind") {
return nil, decodeErr
}
}
resourceJSON, err := yaml.YAMLToJSON(resourceYaml)
if err != nil {
return nil, err
}
resource, err := kubeutils.BytesToUnstructured(resourceJSON)
if err != nil {
return nil, err
}
if decodeErr == nil {
resource.SetGroupVersionKind(*metaData)
}
if resource.GetNamespace() == "" {
resource.SetNamespace("default")
}
return resource, nil
}
func GetResourceFromPath(fs billy.Filesystem, path string) (*unstructured.Unstructured, error) {
var resourceBytes []byte
if fs == nil {
data, err := GetFileBytes(path)
if err != nil {
return nil, err
}
resourceBytes = data
} else {
file, err := fs.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return nil, err
}
resourceBytes = data
}
resources, err := GetUnstructuredResources(resourceBytes)
if err != nil {
return nil, err
}
if len(resources) != 1 {
return nil, fmt.Errorf("exactly one resource expected, found %d", len(resources))
}
return resources[0], nil
}
func GetFileBytes(path string) ([]byte, error) {
if source.IsHttp(path) {
// We accept here that a random URL might be called based on user provided input.
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, path, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, err
}
file, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return file, nil
} else {
path = filepath.Clean(path)
// We accept the risk of including a user provided file here.
file, err := os.ReadFile(path) // #nosec G304
if err != nil {
return nil, err
}
return file, nil
}
}

View file

@ -16,6 +16,7 @@ import (
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/commands/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy/annotations"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/source"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
@ -297,7 +298,7 @@ func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []str
}
yamlBytes := []byte(resourceStr)
resources, err = GetResource(yamlBytes)
resources, err = resource.GetUnstructuredResources(yamlBytes)
if err != nil {
return nil, sanitizederror.NewWithError("failed to extract the resources", err)
}
@ -497,36 +498,6 @@ func getSubresourceKind(groupVersion, parentKind, subresourceName string, subres
return "", sanitizederror.NewWithError(fmt.Sprintf("subresource %s not found for parent resource %s", subresourceName, parentKind), nil)
}
// GetResourceFromPath - get patchedResource and generatedResource from given path
func GetResourceFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string, resourceType string) (unstructured.Unstructured, error) {
var resourceBytes []byte
var resource unstructured.Unstructured
var err error
if isGit {
if len(path) > 0 {
filep, fileErr := fs.Open(filepath.Join(policyResourcePath, path))
if fileErr != nil {
fmt.Printf("Unable to open %s file: %s. \nerror: %s", resourceType, path, err)
}
resourceBytes, err = io.ReadAll(filep)
}
} else {
resourceBytes, err = getFileBytes(path)
}
if err != nil {
fmt.Printf("\n----------------------------------------------------------------------\nfailed to load %s: %s. \nerror: %s\n----------------------------------------------------------------------\n", resourceType, path, err)
return resource, err
}
resource, err = GetPatchedAndGeneratedResource(resourceBytes)
if err != nil {
return resource, err
}
return resource, nil
}
// initializeMockController initializes a basic Generate Controller with a fake dynamic client.
func initializeMockController(objects []runtime.Object) (*generate.GenerateController, error) {
client, err := dclient.NewFakeClient(runtime.NewScheme(), nil, objects...)
@ -552,16 +523,16 @@ func initializeMockController(objects []runtime.Object) (*generate.GenerateContr
// handleGeneratePolicy returns a new RuleResponse with the Kyverno generated resource configuration by applying the generate rule.
func handleGeneratePolicy(generateResponse *engineapi.EngineResponse, policyContext engine.PolicyContext, ruleToCloneSourceResource map[string]string) ([]engineapi.RuleResponse, error) {
resource := policyContext.NewResource()
objects := []runtime.Object{&resource}
newResource := policyContext.NewResource()
objects := []runtime.Object{&newResource}
resources := []*unstructured.Unstructured{}
for _, rule := range generateResponse.PolicyResponse.Rules {
if path, ok := ruleToCloneSourceResource[rule.Name()]; ok {
resourceBytes, err := getFileBytes(path)
resourceBytes, err := resource.GetFileBytes(path)
if err != nil {
fmt.Printf("failed to get resource bytes\n")
} else {
resources, err = GetResource(resourceBytes)
resources, err = resource.GetUnstructuredResources(resourceBytes)
if err != nil {
fmt.Printf("failed to convert resource bytes to unstructured format\n")
}

View file

@ -5,6 +5,7 @@ import (
"github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/commands/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -99,7 +100,7 @@ func Test_NamespaceSelector(t *testing.T) {
rc := &ResultCounts{}
for _, tc := range testcases {
policyArray, _, _ := yamlutils.GetPolicy(tc.policy)
resourceArray, _ := GetResource(tc.resource)
resourceArray, _ := resource.GetUnstructuredResources(tc.resource)
applyPolicyConfig := ApplyPolicyConfig{
Policy: policyArray[0],
Resource: resourceArray[0],

View file

@ -2,29 +2,23 @@ package common
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/go-git/go-billy/v5"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/commands/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/source"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/clients/dclient"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
"github.com/kyverno/kyverno/pkg/validatingadmissionpolicy"
"k8s.io/api/admissionregistration/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/yaml"
)
// GetResources gets matched resources by the given policies
@ -104,7 +98,7 @@ func whenClusterIsTrue(resourceTypes []schema.GroupVersionKind, subresourceMap m
func whenClusterIsFalse(resourcePaths []string, policyReport bool) ([]*unstructured.Unstructured, error) {
resources := make([]*unstructured.Unstructured, 0)
for _, resourcePath := range resourcePaths {
resourceBytes, err := getFileBytes(resourcePath)
resourceBytes, err := resource.GetFileBytes(resourcePath)
if err != nil {
if policyReport {
log.V(3).Info(fmt.Sprintf("failed to load resources: %s.", resourcePath), "error", err)
@ -114,7 +108,7 @@ func whenClusterIsFalse(resourcePaths []string, policyReport bool) ([]*unstructu
continue
}
getResources, err := GetResource(resourceBytes)
getResources, err := resource.GetUnstructuredResources(resourceBytes)
if err != nil {
return nil, err
}
@ -147,14 +141,14 @@ func GetResourcesWithTest(fs billy.Filesystem, policies []kyvernov1.PolicyInterf
}
resourceBytes, _ = io.ReadAll(filep)
} else {
resourceBytes, err = getFileBytes(resourcePath)
resourceBytes, err = resource.GetFileBytes(resourcePath)
}
if err != nil {
fmt.Printf("\n----------------------------------------------------------------------\nfailed to load resources: %s. \nerror: %s\n----------------------------------------------------------------------\n", resourcePath, err)
continue
}
getResources, err := GetResource(resourceBytes)
getResources, err := resource.GetUnstructuredResources(resourceBytes)
if err != nil {
return nil, err
}
@ -165,35 +159,6 @@ func GetResourcesWithTest(fs billy.Filesystem, policies []kyvernov1.PolicyInterf
return resources, nil
}
// GetResource converts raw bytes to unstructured object
func GetResource(resourceBytes []byte) ([]*unstructured.Unstructured, error) {
resources := make([]*unstructured.Unstructured, 0)
var getErrString string
files, splitDocError := yamlutils.SplitDocuments(resourceBytes)
if splitDocError != nil {
return nil, splitDocError
}
for _, resourceYaml := range files {
resource, err := convertResourceToUnstructured(resourceYaml)
if err != nil {
if strings.Contains(err.Error(), "Object 'Kind' is missing") {
log.V(3).Info("skipping resource as kind not found")
continue
}
getErrString = getErrString + err.Error() + "\n"
}
resources = append(resources, resource)
}
if getErrString != "" {
return nil, errors.New(getErrString)
}
return resources, nil
}
func getResourcesOfTypeFromCluster(resourceTypes []schema.GroupVersionKind, subresourceMap map[schema.GroupVersionKind]api.Subresource, dClient dclient.Interface, namespace string) (map[string]*unstructured.Unstructured, error) {
r := make(map[string]*unstructured.Unstructured)
for _, kind := range resourceTypes {
@ -241,77 +206,9 @@ func getResourcesOfTypeFromCluster(resourceTypes []schema.GroupVersionKind, subr
return r, nil
}
func getFileBytes(path string) ([]byte, error) {
var (
file []byte
err error
)
if source.IsHttp(path) {
// We accept here that a random URL might be called based on user provided input.
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, path, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, err
}
file, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
} else {
path = filepath.Clean(path)
// We accept the risk of including a user provided file here.
file, err = os.ReadFile(path) // #nosec G304
if err != nil {
return nil, err
}
}
return file, err
}
func convertResourceToUnstructured(resourceYaml []byte) (*unstructured.Unstructured, error) {
decode := scheme.Codecs.UniversalDeserializer().Decode
_, metaData, decodeErr := decode(resourceYaml, nil, nil)
if decodeErr != nil {
if !strings.Contains(decodeErr.Error(), "no kind") {
return nil, decodeErr
}
}
resourceJSON, err := yaml.YAMLToJSON(resourceYaml)
if err != nil {
return nil, err
}
resource, err := kubeutils.BytesToUnstructured(resourceJSON)
if err != nil {
return nil, err
}
if decodeErr == nil {
resource.SetGroupVersionKind(*metaData)
}
if resource.GetNamespace() == "" {
resource.SetNamespace("default")
}
return resource, nil
}
// GetPatchedAndGeneratedResource converts raw bytes to unstructured object
func GetPatchedAndGeneratedResource(resourceBytes []byte) (unstructured.Unstructured, error) {
getResource, err := GetResource(resourceBytes)
getResource, err := resource.GetUnstructuredResources(resourceBytes)
if err != nil {
return unstructured.Unstructured{}, err
}