diff --git a/acceptance.bats b/acceptance.bats index 99548b5..c460b0d 100755 --- a/acceptance.bats +++ b/acceptance.bats @@ -12,16 +12,28 @@ [ "$output" = "Summary: 7 resources found in 2 files - Valid: 7, Invalid: 0, Errors: 0 Skipped: 0" ] } +@test "Pass when parsing a valid Kubernetes config file with int_to_string vars" { + run bin/kubeconform -verbose fixtures/int_or_string.yaml + [ "$status" -eq 0 ] + [ "$output" = "fixtures/int_or_string.yaml - Service heapster is valid" ] +} + +@test "Pass when parsing a valid Kubernetes config YAML file with generate name" { + run bin/kubeconform -verbose fixtures/generate_name.yaml + [ "$status" -eq 0 ] + [ "$output" = "fixtures/generate_name.yaml - Job pi-{{ generateName }} is valid" ] +} + @test "Pass when parsing a Kubernetes file with string and integer quantities" { run bin/kubeconform -verbose fixtures/quantity.yaml [ "$status" -eq 0 ] - [ "$output" = "fixtures/quantity.yaml - LimitRange is valid" ] + [ "$output" = "fixtures/quantity.yaml - LimitRange mem-limit-range is valid" ] } @test "Pass when parsing a valid Kubernetes config file with null arrays" { run bin/kubeconform -verbose fixtures/null_string.yaml [ "$status" -eq 0 ] - [ "$output" = "fixtures/null_string.yaml - Service is valid" ] + [ "$output" = "fixtures/null_string.yaml - Service frontend is valid" ] } @test "Pass when parsing a multi-document config file" { @@ -43,7 +55,19 @@ @test "Return relevant error for non-existent file" { run bin/kubeconform fixtures/not-here [ "$status" -eq 1 ] - [ "$output" = "fixtures/not-here - failed validation: open fixtures/not-here: no such file or directory" ] + [ "$output" = "fixtures/not-here - failed validation: open fixtures/not-here: no such file or directory" ] +} + +@test "Pass when parsing a blank config file" { + run bin/kubeconform -summary fixtures/blank.yaml + [ "$status" -eq 0 ] + [ "$output" = "Summary: 0 resource found in 1 file - Valid: 0, Invalid: 0, Errors: 0 Skipped: 0" ] +} + +@test "Pass when parsing a blank config file with a comment" { + run bin/kubeconform -summary fixtures/comment.yaml + [ "$status" -eq 0 ] + [ "$output" = "Summary: 0 resource found in 1 file - Valid: 0, Invalid: 0, Errors: 0 Skipped: 0" ] } @test "Fail when parsing a config with additional properties and strict set" { @@ -65,3 +89,29 @@ run bin/kubeconform -registry './fixtures/registry/{{ .ResourceKind }}{{ .KindSuffix }}.json' fixtures/test_crd.yaml [ "$status" -eq 0 ] } + +@test "Pass when parsing a config with additional properties" { + run bin/kubeconform -summary fixtures/extra_property.yaml + [ "$status" -eq 0 ] + [ "$output" = "Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0" ] +} + +@test "Pass when using a valid, preset --registry" { + run bin/kubeconform --registry kubernetesjsonschema.dev fixtures/valid.yaml + [ "$status" -eq 0 ] +} + +@test "Pass when using a valid HTTP --registry" { + run bin/kubeconform --registry 'https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json' fixtures/valid.yaml + [ "$status" -eq 0 ] +} + +@test "Fail when using an invalid HTTP --registry" { + run bin/kubeconform --registry 'http://foo' fixtures/valid.yaml + [ "$status" -eq 1 ] +} + +@test "Fail when using an invalid non-HTTP --registry" { + run bin/kubeconform --registry 'foo' fixtures/valid.yaml + [ "$status" -eq 1 ] +} diff --git a/cmd/kubeconform/main.go b/cmd/kubeconform/main.go index 82f0fb8..1dcf969 100644 --- a/cmd/kubeconform/main.go +++ b/cmd/kubeconform/main.go @@ -20,9 +20,9 @@ import ( ) type validationResult struct { - filename, kind, version string - err error - skipped bool + filename, kind, version, Name string + err error + skipped bool } func resourcesFromReader(r io.Reader) ([][]byte, error) { @@ -65,21 +65,27 @@ func ValidateStream(r io.Reader, regs []registry.Registry, k8sVersion string, c return []validationResult{{err: fmt.Errorf("failed reading file: %s", err)}} } + validationResults := []validationResult{} + if len(rawResources) == 0 { + // In case a file has no resources, we want to capture that the file was parsed - and therefore send a message with an empty resource and no error + validationResults = append(validationResults, validationResult{kind: "", version: "", Name: "", err: nil, skipped: false}) + } for _, rawResource := range rawResources { var sig resource.Signature if sig, err = resource.SignatureFromBytes(rawResource); err != nil { - validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: fmt.Errorf("error while parsing: %s", err)}) + validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, Name: sig.Name, err: fmt.Errorf("error while parsing: %s", err)}) continue } if sig.Kind == "" { + validationResults = append(validationResults, validationResult{kind: "", version: "", Name: "", err: nil, skipped: false}) continue // We skip resoures that don't have a Kind defined } if skip(sig) { - validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: nil, skipped: true}) + validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, Name: sig.Name, err: nil, skipped: true}) continue } @@ -95,7 +101,7 @@ func ValidateStream(r io.Reader, regs []registry.Registry, k8sVersion string, c if !ok { schema, err = downloadSchema(regs, sig.Kind, sig.Version, k8sVersion) if err != nil { - validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: err, skipped: false}) + validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, Name: sig.Name, err: err, skipped: false}) continue } @@ -106,14 +112,14 @@ func ValidateStream(r io.Reader, regs []registry.Registry, k8sVersion string, c if schema == nil { if ignoreMissingSchemas { - validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: nil, skipped: true}) + validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, Name: sig.Name, err: nil, skipped: true}) } else { - validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: fmt.Errorf("could not find schema for %s", sig.Kind), skipped: false}) + validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, Name: sig.Name, err: fmt.Errorf("could not find schema for %s", sig.Kind), skipped: false}) } } err = validator.Validate(rawResource, schema) - validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: err}) + validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, Name: sig.Name, err: err}) } return validationResults @@ -162,7 +168,7 @@ func processResults(o output.Output, validationResults chan []validationResult, success = false } - if err := o.Write(result.filename, result.kind, result.version, result.err, result.skipped); err != nil { + if err := o.Write(result.filename, result.kind, result.Name, result.version, result.err, result.skipped); err != nil { fmt.Fprint(os.Stderr, "failed writing log\n") } } diff --git a/fixtures/extra_property.yaml b/fixtures/extra_property.yaml index c01fba1..f59db4f 100644 --- a/fixtures/extra_property.yaml +++ b/fixtures/extra_property.yaml @@ -1,10 +1,13 @@ --- -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: DaemonSet metadata: name: nginx-ds spec: replicas: 2 + selector: + matchLabels: + k8s-app: nginx-ds template: spec: containers: diff --git a/fixtures/generate_name.yaml b/fixtures/generate_name.yaml new file mode 100644 index 0000000..d960ee2 --- /dev/null +++ b/fixtures/generate_name.yaml @@ -0,0 +1,13 @@ +apiVersion: batch/v1 +kind: Job +metadata: + generateName: pi- +spec: + template: + spec: + containers: + - name: pi + image: perl + command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] + restartPolicy: Never + backoffLimit: 4 \ No newline at end of file diff --git a/fixtures/null_array.yaml b/fixtures/null_array.yaml index befe9fb..ae9ff61 100644 --- a/fixtures/null_array.yaml +++ b/fixtures/null_array.yaml @@ -1,5 +1,5 @@ kind: Deployment -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 metadata: labels: k8s-app: kubernetes-dashboard diff --git a/fixtures/registry/sagemaker.aws.amazon.com_trainingjobs.yaml b/fixtures/registry/sagemaker.aws.amazon.com_trainingjobs.yaml new file mode 100644 index 0000000..7178b32 --- /dev/null +++ b/fixtures/registry/sagemaker.aws.amazon.com_trainingjobs.yaml @@ -0,0 +1,418 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.3.0 + creationTimestamp: null + name: trainingjobs.sagemaker.aws.amazon.com +spec: + additionalPrinterColumns: + - JSONPath: .status.trainingJobStatus + name: Status + type: string + - JSONPath: .status.secondaryStatus + name: Secondary-Status + type: string + - JSONPath: .metadata.creationTimestamp + format: date + name: Creation-Time + type: string + - JSONPath: .status.sageMakerTrainingJobName + name: Sagemaker-Job-Name + type: string + group: sagemaker.aws.amazon.com + names: + kind: TrainingJob + listKind: TrainingJobList + plural: trainingjobs + singular: trainingjob + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: TrainingJob is the Schema for the trainingjobs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TrainingJobSpec defines the desired state of TrainingJob + properties: + algorithmSpecification: + properties: + algorithmName: + minLength: 1 + type: string + metricDefinitions: + items: + properties: + name: + minLength: 1 + type: string + regex: + minLength: 1 + type: string + required: + - name + - regex + type: object + type: array + trainingImage: + minLength: 1 + type: string + trainingInputMode: + enum: + - File + - Pipe + type: string + required: + - trainingInputMode + type: object + checkpointConfig: + properties: + localPath: + type: string + s3Uri: + pattern: ^(https|s3)://([^/]+)/?(.*)$ + type: string + required: + - s3Uri + type: object + debugHookConfig: + description: DebugHookConfig https://docs.aws.amazon.com/sagemaker/latest/dg/API_DebugHookConfig.html + properties: + collectionConfigurations: + items: + description: CollectionConfiguration https://docs.aws.amazon.com/sagemaker/latest/dg/API_CollectionConfiguration.html + properties: + collectionName: + type: string + collectionParameters: + items: + description: Used in describing maps in Kubernetes. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + type: array + localPath: + type: string + ruleParameters: + items: + description: Used in describing maps in Kubernetes. + properties: + name: + type: string + value: + type: string + type: object + type: array + s3OutputPath: + pattern: ^(https|s3)://([^/]+)/?(.*)$ + type: string + required: + - s3OutputPath + type: object + debugRuleConfigurations: + items: + description: DebugRuleConfiguration https://docs.aws.amazon.com/sagemaker/latest/dg/API_DebugRuleConfiguration.html + properties: + instanceType: + type: string + localPath: + type: string + ruleConfigurationName: + type: string + ruleEvaluatorImage: + type: string + ruleParameters: + items: + description: Used in describing maps in Kubernetes. + properties: + name: + type: string + value: + type: string + type: object + type: array + s3OutputPath: + pattern: ^(https|s3)://([^/]+)/?(.*)$ + type: string + volumeSizeInGB: + format: int64 + minimum: 1 + type: integer + required: + - ruleConfigurationName + - ruleEvaluatorImage + type: object + type: array + enableInterContainerTrafficEncryption: + type: boolean + enableManagedSpotTraining: + type: boolean + enableNetworkIsolation: + type: boolean + hyperParameters: + items: + description: Used in describing maps in Kubernetes. + properties: + name: + type: string + value: + type: string + type: object + type: array + inputDataConfig: + items: + properties: + channelName: + minLength: 1 + pattern: '[A-Za-z0-9\.\-_]+' + type: string + compressionType: + enum: + - None + - Gzip + type: string + contentType: + type: string + dataSource: + properties: + fileSystemDataSource: + properties: + directoryPath: + type: string + fileSystemAccessMode: + type: string + fileSystemId: + type: string + fileSystemType: + type: string + required: + - directoryPath + - fileSystemAccessMode + - fileSystemId + - fileSystemType + type: object + s3DataSource: + properties: + attributeNames: + items: + type: string + type: array + s3DataDistributionType: + enum: + - FullyReplicated + - ShardedByS3Key + type: string + s3DataType: + enum: + - S3Prefix + - ManifestFile + - AugmentedManifestFile + type: string + s3Uri: + pattern: ^(https|s3)://([^/]+)/?(.*)$ + type: string + required: + - s3DataType + - s3Uri + type: object + type: object + inputMode: + enum: + - Pipe + - File + type: string + recordWrapperType: + type: string + shuffleConfig: + properties: + seed: + format: int64 + type: integer + required: + - seed + type: object + required: + - channelName + - dataSource + type: object + minItems: 1 + type: array + outputDataConfig: + properties: + kmsKeyId: + type: string + s3OutputPath: + pattern: ^(https|s3)://([^/]+)/?(.*)$ + type: string + required: + - s3OutputPath + type: object + region: + minLength: 1 + type: string + resourceConfig: + properties: + instanceCount: + format: int64 + minimum: 1 + type: integer + instanceType: + minLength: 1 + type: string + volumeKmsKeyId: + type: string + volumeSizeInGB: + format: int64 + minimum: 1 + type: integer + required: + - instanceCount + - instanceType + - volumeSizeInGB + type: object + roleArn: + minLength: 20 + type: string + sageMakerEndpoint: + description: A custom SageMaker endpoint to use when communicating with SageMaker. + pattern: ^(https|http)://.*$ + type: string + stoppingCondition: + properties: + maxRuntimeInSeconds: + format: int64 + minimum: 1 + type: integer + maxWaitTimeInSeconds: + format: int64 + minimum: 1 + type: integer + type: object + tags: + items: + properties: + key: + minLength: 1 + type: string + value: + type: string + required: + - key + - value + type: object + type: array + tensorBoardOutputConfig: + description: TensorBoardOutputConfig https://docs.aws.amazon.com/sagemaker/latest/dg/API_TensorBoardOutputConfig.html + properties: + localPath: + type: string + s3OutputPath: + pattern: ^(https|s3)://([^/]+)/?(.*)$ + type: string + required: + - s3OutputPath + type: object + trainingJobName: + description: The SageMaker training job name. This is optional for the SageMaker K8s operator. If it is empty, the operator will populate it with a generated name. + maxLength: 63 + type: string + vpcConfig: + properties: + securityGroupIds: + items: + type: string + maxItems: 5 + minItems: 1 + type: array + subnets: + items: + type: string + maxItems: 16 + minItems: 1 + type: array + required: + - securityGroupIds + - subnets + type: object + required: + - algorithmSpecification + - outputDataConfig + - region + - resourceConfig + - roleArn + - stoppingCondition + type: object + status: + description: TrainingJobStatus defines the observed state of TrainingJob + properties: + additional: + description: Field to store additional information, for example if we are unable to check the status we update this. + type: string + cloudWatchLogUrl: + description: Cloud Watch url for training log + type: string + debugRuleEvaluationStatuses: + description: Status of rule evaluation jobs, obtained from DebugRuleEvaluationStatuses. https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_DescribeTrainingJob.html#sagemaker-DescribeTrainingJob-response-DebugRuleEvaluationStatuses + items: + description: DebugRuleEvaluationStatus https://docs.aws.amazon.com/sagemaker/latest/dg/API_DebugRuleEvaluationStatus.html + properties: + lastModifiedTime: + format: date-time + type: string + ruleConfigurationName: + type: string + ruleEvaluationJobArn: + type: string + ruleEvaluationStatus: + type: string + statusDetail: + type: string + type: object + type: array + lastCheckTime: + description: The last time that we checked the status of the SageMaker job. + format: date-time + type: string + modelPath: + description: Full path to the training artifact (model) + type: string + sageMakerTrainingJobName: + description: SageMaker training job name + type: string + secondaryStatus: + description: The secondary, more granular status of the training job. https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeTrainingJob.html#SageMaker-DescribeTrainingJob-response-SecondaryStatus + type: string + trainingJobStatus: + description: The status of the training job. https://docs.aws.amazon.com/sagemaker/latest/dg/API_DescribeTrainingJob.html#SageMaker-DescribeTrainingJob-response-TrainingJobStatus + type: string + type: object + required: + - spec + type: object + version: v1 + versions: + - name: v1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/pkg/output/json.go b/pkg/output/json.go index d5abe86..84ea906 100644 --- a/pkg/output/json.go +++ b/pkg/output/json.go @@ -9,6 +9,7 @@ import ( type result struct { Filename string `json:"filename"` Kind string `json:"kind"` + Name string `json:"name"` Version string `json:"version"` Status string `json:"status"` Msg string `json:"msg"` @@ -37,10 +38,10 @@ func JSON(w io.Writer, withSummary bool, verbose bool) Output { } // JSON.Write will only write when JSON.Flush has been called -func (o *jsono) Write(filename, kind, version string, err error, skipped bool) error { +func (o *jsono) Write(filename, kind, name, version string, err error, skipped bool) error { msg, st := "", "" - s := status(err, skipped) + s := status(kind, name, err, skipped) switch s { case VALID: @@ -57,10 +58,11 @@ func (o *jsono) Write(filename, kind, version string, err error, skipped bool) e case SKIPPED: st = "SKIPPED" o.nSkipped++ + case EMPTY: } - if o.verbose || (s != VALID && s != SKIPPED) { - o.results = append(o.results, result{Filename: filename, Kind: kind, Version: version, Status: st, Msg: msg}) + if o.verbose || (s != VALID && s != SKIPPED && s != EMPTY ) { + o.results = append(o.results, result{Filename: filename, Kind: kind, Name: name, Version: version, Status: st, Msg: msg}) } return nil diff --git a/pkg/output/json_test.go b/pkg/output/json_test.go index aaf02bb..b0f0cf2 100644 --- a/pkg/output/json_test.go +++ b/pkg/output/json_test.go @@ -7,9 +7,9 @@ import ( func TestJSONWrite(t *testing.T) { type result struct { - fileName, kind, version string - err error - skipped bool + fileName, kind, name, version string + err error + skipped bool } for _, testCase := range []struct { @@ -28,6 +28,7 @@ func TestJSONWrite(t *testing.T) { { "deployment.yml", "Deployment", + "my-app", "apps/v1", nil, false, @@ -46,6 +47,7 @@ func TestJSONWrite(t *testing.T) { { "deployment.yml", "Deployment", + "my-app", "apps/v1", nil, false, @@ -70,6 +72,7 @@ func TestJSONWrite(t *testing.T) { { "deployment.yml", "Deployment", + "my-app", "apps/v1", nil, false, @@ -80,6 +83,7 @@ func TestJSONWrite(t *testing.T) { { "filename": "deployment.yml", "kind": "Deployment", + "name": "my-app", "version": "apps/v1", "status": "VALID", "msg": "" @@ -99,7 +103,7 @@ func TestJSONWrite(t *testing.T) { o := JSON(w, testCase.withSummary, testCase.verbose) for _, res := range testCase.res { - o.Write(res.fileName, res.kind, res.version, res.err, res.skipped) + o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped) } o.Flush() diff --git a/pkg/output/output.go b/pkg/output/output.go index d56403e..d5c0a4f 100644 --- a/pkg/output/output.go +++ b/pkg/output/output.go @@ -10,14 +10,19 @@ const ( INVALID ERROR SKIPPED + EMPTY ) type Output interface { - Write(filename, kind, version string, err error, skipped bool) error + Write(filename, kind, name, version string, err error, skipped bool) error Flush() error } -func status(err error, skipped bool) int { +func status(kind, name string, err error, skipped bool) int { + if name == "" && kind == "" && err == nil && skipped == false { + return EMPTY + } + if skipped { return SKIPPED } diff --git a/pkg/output/text.go b/pkg/output/text.go index a7cd2b5..a2706b8 100644 --- a/pkg/output/text.go +++ b/pkg/output/text.go @@ -29,30 +29,35 @@ func Text(w io.Writer, withSummary, verbose bool) Output { } } -func (o *text) Write(filename, kind, version string, reserr error, skipped bool) error { +func (o *text) Write(filename, kind, name, version string, reserr error, skipped bool) error { o.Lock() defer o.Unlock() var err error o.files[filename] = true - switch status(reserr, skipped) { + switch status(kind, name, reserr, skipped) { case VALID: if o.verbose { - _, err = fmt.Fprintf(o.w, "%s - %s is valid\n", filename, kind) + _, err = fmt.Fprintf(o.w, "%s - %s %s is valid\n", filename, kind, name) } o.nValid++ case INVALID: - _, err = fmt.Fprintf(o.w, "%s - %s is invalid: %s\n", filename, kind, reserr) + _, err = fmt.Fprintf(o.w, "%s - %s %s is invalid: %s\n", filename, kind, name, reserr) o.nInvalid++ case ERROR: - _, err = fmt.Fprintf(o.w, "%s - %s failed validation: %s\n", filename, kind, reserr) + if kind != "" && name != "" { + _, err = fmt.Fprintf(o.w, "%s - %s %s failed validation: %s\n", filename, kind, name, reserr) + } else { + _, err = fmt.Fprintf(o.w, "%s - failed validation: %s\n", filename, reserr) + } o.nErrors++ case SKIPPED: if o.verbose { - _, err = fmt.Fprintf(o.w, "%s - %s skipped\n", filename, kind) + _, err = fmt.Fprintf(o.w, "%s - %s %s skipped\n", filename, name, kind) } o.nSkipped++ + case EMPTY: // sent to ensure we count the filename as parsed } return err diff --git a/pkg/output/text_test.go b/pkg/output/text_test.go index 21fc1e7..82628b7 100644 --- a/pkg/output/text_test.go +++ b/pkg/output/text_test.go @@ -7,9 +7,9 @@ import ( func TestTextWrite(t *testing.T) { type result struct { - fileName, kind, version string - err error - skipped bool + fileName, kind, name, version string + err error + skipped bool } for _, testCase := range []struct { @@ -28,6 +28,7 @@ func TestTextWrite(t *testing.T) { { "deployment.yml", "Deployment", + "my-app", "apps/v1", nil, false, @@ -43,6 +44,7 @@ func TestTextWrite(t *testing.T) { { "deployment.yml", "Deployment", + "my-app", "apps/v1", nil, false, @@ -58,12 +60,13 @@ func TestTextWrite(t *testing.T) { { "deployment.yml", "Deployment", + "my-app", "apps/v1", nil, false, }, }, - `deployment.yml - Deployment is valid + `deployment.yml - Deployment my-app is valid Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0 `, }, @@ -72,7 +75,7 @@ Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0 o := Text(w, testCase.withSummary, testCase.verbose) for _, res := range testCase.res { - o.Write(res.fileName, res.kind, res.version, res.err, res.skipped) + o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped) } o.Flush() diff --git a/pkg/resource/signature.go b/pkg/resource/signature.go index d85b7d8..fab315b 100644 --- a/pkg/resource/signature.go +++ b/pkg/resource/signature.go @@ -5,7 +5,7 @@ import ( ) type Signature struct { - Kind, Version, Namespace string + Kind, Version, Namespace, Name string } // SignatureFromBytes returns key identifying elements from a []byte representing the resource @@ -14,10 +14,17 @@ func SignatureFromBytes(res []byte) (Signature, error) { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` Metadata struct { - Namespace string `yaml:"Namespace"` + Name string `yaml:"name"` + Namespace string `yaml:"namespace"` + GenerateName string `yaml:"generateName"` } `yaml:"Metadata"` }{} err := yaml.Unmarshal(res, &resource) - return Signature{Kind: resource.Kind, Version: resource.APIVersion, Namespace: resource.Metadata.Namespace}, err + name := resource.Metadata.Name + if resource.Metadata.GenerateName != "" { + name = resource.Metadata.GenerateName + "{{ generateName }}" + } + + return Signature{Kind: resource.Kind, Version: resource.APIVersion, Namespace: resource.Metadata.Namespace, Name: name}, err }