Skip to content

Commit

Permalink
CMP-2614: Implement update timestamps on ComplianceCheckResults
Browse files Browse the repository at this point in the history
Adding a `Disabled` filed in `ScanSetting.Spec.RawResultStorage.Disabled`, defaulting to false, if setting to true we will not create a result server to store the arf report.
  • Loading branch information
Vincent056 committed Aug 29, 2024
1 parent cd44a50 commit f17fd90
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 6 deletions.
36 changes: 33 additions & 3 deletions cmd/manager/aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,19 +308,24 @@ func createOrUpdateOneResult(crClient aggregatorCrClient, owner metav1.Object, l
}

res.SetLabels(labels)
if annotations != nil {
res.SetAnnotations(annotations)
}

name := res.GetName()

err := backoff.Retry(func() error {
var err error
if !exists {
cmdLog.Info("Creating object", "kind", kind, "name", name)
annotations = setTimestampAnnotations(crClient, owner, annotations)
if annotations != nil {
res.SetAnnotations(annotations)
}
err = crClient.getClient().Create(context.TODO(), res)
} else {
cmdLog.Info("Updating object", "kind", kind, "name", name)
annotations = setTimestampAnnotations(crClient, owner, annotations)
if annotations != nil {
res.SetAnnotations(annotations)
}
err = crClient.getClient().Update(context.TODO(), res)
}
if err != nil && !errors.IsAlreadyExists(err) {
Expand All @@ -336,6 +341,31 @@ func createOrUpdateOneResult(crClient aggregatorCrClient, owner metav1.Object, l
return nil
}

func setTimestampAnnotations(crClient aggregatorCrClient, owner metav1.Object, annotations map[string]string) map[string]string {
// Get the scan timestamp
scan := &compv1alpha1.ComplianceScan{}
err := backoff.Retry(func() error {
err := crClient.getClient().Get(context.TODO(), types.NamespacedName{
Namespace: owner.GetNamespace(),
Name: owner.GetName(),
}, scan)
if err != nil {
return err
}
return nil
}, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), maxRetriesForTimestamp))
if err != nil {
cmdLog.Error(err, "Failed to get scan", "ComplianceScan.Name", owner.GetName())
return annotations
}
if annotations == nil {
annotations = make(map[string]string)
}

annotations[compv1alpha1.LastScannedTimestampAnnotation] = scan.Status.StartTimestamp.Format(time.RFC3339)
return annotations
}

func shouldSkipRemediation(
scan *compv1alpha1.ComplianceScan,
rem *compv1alpha1.ComplianceRemediation,
Expand Down
8 changes: 5 additions & 3 deletions cmd/manager/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package manager

import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
"os"
"path/filepath"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"

ocpcfgv1 "github.com/openshift/api/config/v1"
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
"github.com/spf13/cobra"
Expand All @@ -25,7 +26,8 @@ import (
)

const (
maxRetries = 15
maxRetries = 15
maxRetriesForTimestamp = 3
)

var cmdLog = logf.Log.WithName("cmd")
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/compliance/v1alpha1/compliancecheckresult_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const ComplianceCheckResultHasRemediation = "compliance.openshift.io/automated-r
// across the target nodes
const ComplianceCheckInconsistentLabel = "compliance.openshift.io/inconsistent-check"

// LastScannedTimestampAnnotation
const LastScannedTimestampAnnotation = "compliance.openshift.io/last-scanned-timestamp"

// ComplianceCheckResultRuleAnnotation exposes the DNS-friendly name of a rule as a label.
// This provides a way to link a result to a Rule object.
const ComplianceCheckResultRuleAnnotation = "compliance.openshift.io/rule"
Expand Down
37 changes: 37 additions & 0 deletions tests/e2e/framework/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,43 @@ func (f *Framework) AssertScanIsCompliant(name, namespace string) error {
return nil
}

// AssertComplianceCheckResultTimestamps checks if the timestamps in the compliance check results are within the expected range
func (f *Framework) AssertComplianceCheckResultTimestamps(scanName, namespace string) error {
cs := &compv1alpha1.ComplianceScan{}
defer f.logContainerOutput(namespace, scanName)
err := f.Client.Get(context.TODO(), types.NamespacedName{Name: scanName, Namespace: namespace}, cs)
if err != nil {
return err
}
ccr := &compv1alpha1.ComplianceCheckResultList{}
lo := &client.ListOptions{
LabelSelector: labels.SelectorFromSet(map[string]string{
"compliance.openshift.io/scan-name": scanName,
}),
}
err = f.Client.List(context.TODO(), ccr, lo)
if err != nil {
return err
}
for _, check := range ccr.Items {
annotations := check.GetAnnotations()
if annotations == nil {
return fmt.Errorf("Check %s has no annotations", check.Name)
}
if annotations[compv1alpha1.LastScannedTimestampAnnotation] == "" {
return fmt.Errorf("Check %s has no timestamp annotation", check.Name)
}
timestamp, err := time.Parse(time.RFC3339, annotations[compv1alpha1.LastScannedTimestampAnnotation])
if err != nil {
return fmt.Errorf("Error parsing timestamp for check %s: %v", check.Name, err)
}
if timestamp.Before(cs.Status.StartTimestamp.Time) || timestamp.After(cs.Status.EndTimestamp.Time) {
return fmt.Errorf("Timestamp for check %s is not within the expected range: %v", check.Name, timestamp)
}
}
return nil
}

// AssertScanGUIDMatches checks if the scan has the expected GUID
func (f *Framework) AssertScanGUIDMatches(name, namespace, expectedGUID string) error {
cs := &compv1alpha1.ComplianceScan{}
Expand Down
57 changes: 57 additions & 0 deletions tests/e2e/parallel/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,63 @@ func TestSingleScanSucceeds(t *testing.T) {
}
}

func TestSingleScanTimestamps(t *testing.T) {
t.Parallel()
f := framework.Global

scanName := framework.GetObjNameFromTest(t)
testScan := &compv1alpha1.ComplianceScan{
ObjectMeta: metav1.ObjectMeta{
Name: scanName,
Namespace: f.OperatorNamespace,
},
Spec: compv1alpha1.ComplianceScanSpec{
Profile: "xccdf_org.ssgproject.content_profile_moderate",
Content: framework.RhcosContentFile,
Rule: "xccdf_org.ssgproject.content_rule_no_netrc_files",
ComplianceScanSettings: compv1alpha1.ComplianceScanSettings{
Debug: true,
},
},
}
// use Context's create helper to create the object and add a cleanup function for the new object
err := f.Client.Create(context.TODO(), testScan, nil)
if err != nil {
t.Fatalf("failed to create scan %s: %s", scanName, err)
}
defer f.Client.Delete(context.TODO(), testScan)

err = f.WaitForScanStatus(f.OperatorNamespace, scanName, compv1alpha1.PhaseDone)
if err != nil {
t.Fatal(err)
}

// assertComplianceCheckResultTimestamps checks that the timestamps are set
// and that they are set to the same value of startTimestamp of the scan
err = f.AssertComplianceCheckResultTimestamps(scanName, f.OperatorNamespace)
if err != nil {
t.Fatal(err)
}

// rerun the scan
err = f.ReRunScan(scanName, f.OperatorNamespace)
if err != nil {
t.Fatal(err)
}
err = f.WaitForScanStatus(f.OperatorNamespace, scanName, compv1alpha1.PhaseDone)
if err != nil {
t.Fatal(err)
}

// assertComplianceCheckResultTimestamps checks that the timestamps are set
// and that they are set to the same value of startTimestamp of the scan
err = f.AssertComplianceCheckResultTimestamps(scanName, f.OperatorNamespace)
if err != nil {
t.Fatal(err)
}

}

func TestScanProducesRemediations(t *testing.T) {
t.Parallel()
f := framework.Global
Expand Down

0 comments on commit f17fd90

Please sign in to comment.