Skip to content

Add variable tailoring based on variable selections #775

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,20 @@ spec:
rationale:
description: Rationale of why this value is being tailored
type: string
selection:
description: Selection to choose from predefined variable selections
in the datastream
type: string
value:
description: Value of the variable being set
type: string
required:
- name
- rationale
- value
type: object
x-kubernetes-validations:
- message: exactly one of value or selection must be specified
rule: has(self.value) != has(self.selection)
nullable: true
type: array
title:
Expand Down
9 changes: 7 additions & 2 deletions pkg/apis/compliance/v1alpha1/tailoredprofile_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ type RuleReferenceSpec struct {
}

// ValueReferenceSpec specifies a value to be set for a variable with a reason why
// +kubebuilder:validation:XValidation:rule="has(self.value) != has(self.selection)",message="exactly one of value or selection must be specified"
type VariableValueSpec struct {
// Name of the variable that's being referenced
Name string `json:"name"`
// Rationale of why this value is being tailored
Rationale string `json:"rationale"`
// Value of the variable being set
Value string `json:"value"`
// Value to set the variable to
// +optional
Value string `json:"value,omitempty"`
// Value to choose from predefined variable selections
// +optional
Selection string `json:"selection,omitempty"`
}

// TailoredProfileSpec defines the desired state of TailoredProfile
Expand Down
25 changes: 24 additions & 1 deletion pkg/controller/tailoredprofile/tailoredprofile_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,31 @@ func (r *ReconcileTailoredProfile) getVariablesFromSelections(tp *cmpv1alpha1.Ta
variable.GetName(), pb.GetName())
}

// Determine the value to set based on selection or direct value
var valueToSet string
if setValues.Selection != "" && setValues.Value != "" {
return nil, common.NewNonRetriableCtrlError("variable %s: cannot specify both 'value' and 'selection' fields", setValues.Name)
} else if setValues.Selection != "" {
// Find the value from the variable's selections based on description
found := false
for _, selection := range variable.Selections {
if selection.Description == setValues.Selection {
valueToSet = selection.Value
found = true
break
}
}
if !found {
return nil, common.NewNonRetriableCtrlError("variable %s: selection '%s' not found in available selections", setValues.Name, setValues.Selection)
}
} else if setValues.Value != "" {
valueToSet = setValues.Value
} else {
return nil, common.NewNonRetriableCtrlError("variable %s: must specify either 'value' or 'selection' field", setValues.Name)
}

// try setting the variable, this also validates the value
err = variable.SetValue(setValues.Value)
err = variable.SetValue(valueToSet)
if err != nil {
return nil, common.NewNonRetriableCtrlError("setting variable: %s", err)
}
Expand Down
157 changes: 156 additions & 1 deletion pkg/controller/tailoredprofile/tailoredprofile_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,25 @@ var _ = Describe("TailoredprofileController", func() {
Namespace: namespace,
},
VariablePayload: compv1alpha1.VariablePayload{
ID: fmt.Sprintf("var_%d", i),
ID: fmt.Sprintf("var_%d", i),
Type: compv1alpha1.VarTypeString,
},
}

// Add selections for var-1 to test selection functionality
if i == 1 {
v.Selections = []compv1alpha1.ValueSelection{
{
Description: "default",
Value: "default_value",
},
{
Description: "alternative",
Value: "alt_value",
},
}
}

// Rules and Variables 1, 2, 3, 4 are owned by pb1
if i < 5 {
crefErr := controllerutil.SetControllerReference(pb1, r, cscheme)
Expand Down Expand Up @@ -1249,5 +1264,145 @@ var _ = Describe("TailoredprofileController", func() {
Expect(tp.Status.ErrorMessage).NotTo(BeEmpty())
})
})

Context("with variable selection", func() {
BeforeEach(func() {
tp := &compv1alpha1.TailoredProfile{
ObjectMeta: metav1.ObjectMeta{
Name: tpName,
Namespace: namespace,
},
Spec: compv1alpha1.TailoredProfileSpec{
EnableRules: []compv1alpha1.RuleReferenceSpec{
{
Name: "rule-1",
Rationale: "Enable rule for test",
},
},
SetValues: []compv1alpha1.VariableValueSpec{
{
Name: "var-1",
Rationale: "Using selection for variable value",
Selection: "default",
},
},
},
}

createErr := r.Client.Create(ctx, tp)
Expect(createErr).To(BeNil())
})
It("sets the variable value using selection", func() {
tpKey := types.NamespacedName{
Name: tpName,
Namespace: namespace,
}
tpReq := reconcile.Request{}
tpReq.Name = tpName
tpReq.Namespace = namespace

By("Reconciling the first time (setting ownership)")
_, err := r.Reconcile(context.TODO(), tpReq)
Expect(err).To(BeNil())

tp := &compv1alpha1.TailoredProfile{}
geterr := r.Client.Get(ctx, tpKey, tp)
Expect(geterr).To(BeNil())

By("Sets the profile bundle as the owner")
ownerRefs := tp.GetOwnerReferences()
Expect(ownerRefs).To(HaveLen(1))
Expect(ownerRefs[0].Kind).To(Equal("ProfileBundle"))

By("Reconciling a second time (setting status)")
_, err = r.Reconcile(context.TODO(), tpReq)

tp = &compv1alpha1.TailoredProfile{}
geterr = r.Client.Get(ctx, tpKey, tp)
Expect(geterr).To(BeNil())

By("Has the appropriate status")
Expect(tp.Status.State).To(Equal(compv1alpha1.TailoredProfileStateReady))
Expect(tp.Status.OutputRef.Name).To(Equal(tp.Name + "-tp"))
Expect(tp.Status.OutputRef.Namespace).To(Equal(tp.Namespace))

By("Generated an appropriate ConfigMap with selection-based value")
cm := &corev1.ConfigMap{}
cmKey := types.NamespacedName{
Name: tp.Status.OutputRef.Name,
Namespace: tp.Status.OutputRef.Namespace,
}

geterr = r.Client.Get(ctx, cmKey, cm)
Expect(geterr).To(BeNil())
data := cm.Data["tailoring.xml"]
// Should set var-1 to "default_value" based on the "default" selection
Expect(data).To(ContainSubstring(`set-value idref="var_1">default_value<`))
})
})

Context("with both value and selection specified", func() {
BeforeEach(func() {
tp := &compv1alpha1.TailoredProfile{
ObjectMeta: metav1.ObjectMeta{
Name: tpName,
Namespace: namespace,
},
Spec: compv1alpha1.TailoredProfileSpec{
EnableRules: []compv1alpha1.RuleReferenceSpec{
{
Name: "rule-1",
Rationale: "Enable rule for test",
},
},
SetValues: []compv1alpha1.VariableValueSpec{
{
Name: "var-1",
Rationale: "Testing invalid configuration",
Value: "direct_value",
Selection: "default",
},
},
},
}

createErr := r.Client.Create(ctx, tp)
Expect(createErr).To(BeNil())
})
It("reports an error for conflicting value and selection", func() {
tpKey := types.NamespacedName{
Name: tpName,
Namespace: namespace,
}
tpReq := reconcile.Request{}
tpReq.Name = tpName
tpReq.Namespace = namespace

By("Reconciling the first time (setting ownership)")
_, err := r.Reconcile(context.TODO(), tpReq)
Expect(err).To(BeNil())

tp := &compv1alpha1.TailoredProfile{}
geterr := r.Client.Get(ctx, tpKey, tp)
Expect(geterr).To(BeNil())

By("Sets the profile bundle as the owner")
ownerRefs := tp.GetOwnerReferences()
Expect(ownerRefs).To(HaveLen(1))
Expect(ownerRefs[0].Kind).To(Equal("ProfileBundle"))

By("Reconciling a second time (should hit error)")
_, err = r.Reconcile(context.TODO(), tpReq)
Expect(err).To(BeNil())

tp = &compv1alpha1.TailoredProfile{}
geterr = r.Client.Get(ctx, tpKey, tp)
Expect(geterr).To(BeNil())

By("Has an error status")
Expect(tp.Status.State).To(Equal(compv1alpha1.TailoredProfileStateError))
Expect(tp.Status.ErrorMessage).To(ContainSubstring("cannot specify both 'value' and 'selection' fields"))
})
})
})
})
Loading