Assigns solver pod to exposed template with defaults, includes

validation

Signed-off-by: JoshVanL <vleeuwenjoshua@gmail.com>
This commit is contained in:
JoshVanL 2019-06-05 14:54:24 +01:00
parent 0a7a181808
commit f2ba4d9f20
7 changed files with 179 additions and 15 deletions

View File

@ -1394,6 +1394,10 @@ Appears In:
</tr>
</thead>
<tbody><tr>
<td><code>PodTemplate</code><br /> <em>PodTemplate</em></td>
<td>Optional template for configure the solver pods. Not all pod template options are valid (e.g. name)</td>
</tr>
<tr>
<td><code>serviceType</code><br /> <em>string</em></td>
<td>Optional service type for Kubernetes solver service</td>
</tr>

View File

@ -325,6 +325,11 @@ type ACMEIssuerHTTP01Config struct {
// Optional service type for Kubernetes solver service
// +optional
ServiceType corev1.ServiceType `json:"serviceType,omitempty"`
// Optional template for configure the solver pods. Not all pod template
// options are valid (e.g. name)
// +optional
PodTemplate corev1.PodTemplate `json"podTemplate,omitempty"`
}
// ACMEIssuerDNS01Config is a structure containing the ACME DNS configuration

View File

@ -197,7 +197,7 @@ func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) {
if in.HTTP01 != nil {
in, out := &in.HTTP01, &out.HTTP01
*out = new(ACMEIssuerHTTP01Config)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.DNS01 != nil {
in, out := &in.DNS01, &out.DNS01
@ -463,6 +463,7 @@ func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopy() *ACMEIssuerDNS01ProviderWeb
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ACMEIssuerHTTP01Config) DeepCopyInto(out *ACMEIssuerHTTP01Config) {
*out = *in
in.PodTemplate.DeepCopyInto(&out.PodTemplate)
return
}

View File

@ -169,6 +169,24 @@ func ValidateACMEIssuerHTTP01Config(iss *v1alpha1.ACMEIssuerHTTP01Config, fldPat
}
}
// Validate incoming solver pod template
if len(iss.PodTemplate.Name) > 0 {
el = append(el, field.Invalid(fldPath.Child("podTemplate"), iss.PodTemplate, fmt.Sprintf("name cannot be set for solver pod template, got %s",
iss.PodTemplate.Name)))
}
if len(iss.PodTemplate.GenerateName) > 0 {
el = append(el, field.Invalid(fldPath.Child("podTemplate"), iss.PodTemplate, fmt.Sprintf("generateName cannot be set for solver pod template, got %s",
iss.PodTemplate.GenerateName)))
}
if len(iss.PodTemplate.OwnerReferences) > 0 {
var names []string
for _, o := range iss.PodTemplate.OwnerReferences {
names = append(names, o.Name)
}
el = append(el, field.Invalid(fldPath.Child("podTemplate"), iss.PodTemplate, fmt.Sprintf("owner references cannot be set for solver pod template, got %s",
names)))
}
return el
}

View File

@ -112,7 +112,7 @@ func httpDomainCfgForChallenge(issuer v1alpha1.GenericIssuer, ch *v1alpha1.Chall
func (s *Solver) Present(ctx context.Context, issuer v1alpha1.GenericIssuer, ch *v1alpha1.Challenge) error {
ctx = http01LogCtx(ctx)
_, podErr := s.ensurePod(ctx, ch)
_, podErr := s.ensurePod(ctx, issuer, ch)
svc, svcErr := s.ensureService(ctx, issuer, ch)
if svcErr != nil {
return utilerrors.NewAggregate([]error{podErr, svcErr})

View File

@ -46,7 +46,7 @@ func podLabels(ch *v1alpha1.Challenge) map[string]string {
}
}
func (s *Solver) ensurePod(ctx context.Context, ch *v1alpha1.Challenge) (*corev1.Pod, error) {
func (s *Solver) ensurePod(ctx context.Context, issuer v1alpha1.GenericIssuer, ch *v1alpha1.Challenge) (*corev1.Pod, error) {
log := logf.FromContext(ctx).WithName("ensurePod")
log.V(logf.DebugLevel).Info("checking for existing HTTP01 solver pods")
@ -68,7 +68,7 @@ func (s *Solver) ensurePod(ctx context.Context, ch *v1alpha1.Challenge) (*corev1
}
log.Info("creating HTTP01 challenge solver pod")
return s.createPod(ch)
return s.createPod(issuer, ch)
}
// getPodsForChallenge returns a list of pods that were created to solve
@ -130,15 +130,21 @@ func (s *Solver) cleanupPods(ctx context.Context, ch *v1alpha1.Challenge) error
// createPod will create a challenge solving pod for the given certificate,
// domain, token and key.
func (s *Solver) createPod(ch *v1alpha1.Challenge) (*corev1.Pod, error) {
return s.Client.CoreV1().Pods(ch.Namespace).Create(s.buildPod(ch))
func (s *Solver) createPod(issuer v1alpha1.GenericIssuer, ch *v1alpha1.Challenge) (*corev1.Pod, error) {
pod, err := s.buildPod(issuer, ch)
if err != nil {
return nil, err
}
return s.Client.CoreV1().Pods(ch.Namespace).Create(pod)
}
// buildPod will build a challenge solving pod for the given certificate,
// domain, token and key. It will not create it in the API server
func (s *Solver) buildPod(ch *v1alpha1.Challenge) *corev1.Pod {
func (s *Solver) buildPod(issuer v1alpha1.GenericIssuer, ch *v1alpha1.Challenge) (*corev1.Pod, error) {
podLabels := podLabels(ch)
return &corev1.Pod{
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "cm-acme-http-solver-",
Namespace: ch.Namespace,
@ -183,4 +189,40 @@ func (s *Solver) buildPod(ch *v1alpha1.Challenge) *corev1.Pod {
},
},
}
// Override defaults if they have changed in the pod template.
pod = s.mergePodWithPodTemplate(pod,
issuer.GetSpec().ACME.HTTP01.PodTemplate.DeepCopy())
return pod, nil
}
func (s *Solver) mergePodWithPodTemplate(podDefault *corev1.Pod, podTempl *corev1.PodTemplate) *corev1.Pod {
mergedPod := podDefault.DeepCopy()
if len(podTempl.Namespace) > 0 {
mergedPod.Namespace = podTempl.Namespace
}
if len(podTempl.Annotations) > 0 {
mergedPod.Annotations = podTempl.Annotations
}
if len(podTempl.Labels) > 0 {
mergedPod.Labels = podTempl.Labels
}
// Set merged spec to be equal to the new template
mergedPod.Spec = podTempl.Template.Spec
// These are the only two specs set by default. If they exist in the
// template, take the template.
if len(podTempl.Template.Spec.RestartPolicy) > 0 {
mergedPod.Spec.RestartPolicy = podDefault.Spec.RestartPolicy
}
if len(podTempl.Template.Spec.Containers) == 0 {
mergedPod.Spec.Containers = podDefault.Spec.Containers
}
return mergedPod
}

View File

@ -18,15 +18,18 @@ package http
import (
"context"
"fmt"
"reflect"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
coretesting "k8s.io/client-go/testing"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/test/util/generate"
)
func TestEnsurePod(t *testing.T) {
@ -44,7 +47,7 @@ func TestEnsurePod(t *testing.T) {
},
},
PreFn: func(t *testing.T, s *solverFixture) {
ing, err := s.Solver.createPod(s.Challenge)
ing, err := s.Solver.createPod(s.Issuer, s.Challenge)
if err != nil {
t.Errorf("error preparing test: %v", err)
}
@ -85,7 +88,12 @@ func TestEnsurePod(t *testing.T) {
},
},
PreFn: func(t *testing.T, s *solverFixture) {
expectedPod := s.Solver.buildPod(s.Challenge)
expectedPod, err := s.Solver.buildPod(s.Issuer, s.Challenge)
if err != nil {
t.Errorf("unexpected error building pod: %s", err)
t.Fail()
return
}
// create a reactor that fails the test if a pod is created
s.Builder.FakeKubeClient().PrependReactor("create", "pods", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
pod := action.(coretesting.CreateAction).GetObject().(*v1.Pod)
@ -136,11 +144,11 @@ func TestEnsurePod(t *testing.T) {
},
Err: true,
PreFn: func(t *testing.T, s *solverFixture) {
_, err := s.Solver.createPod(s.Challenge)
_, err := s.Solver.createPod(s.Issuer, s.Challenge)
if err != nil {
t.Errorf("error preparing test: %v", err)
}
_, err = s.Solver.createPod(s.Challenge)
_, err = s.Solver.createPod(s.Issuer, s.Challenge)
if err != nil {
t.Errorf("error preparing test: %v", err)
}
@ -163,7 +171,93 @@ func TestEnsurePod(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
test.Setup(t)
resp, err := test.Solver.ensurePod(context.TODO(), test.Challenge)
resp, err := test.Solver.ensurePod(context.TODO(), test.Issuer, test.Challenge)
if err != nil && !test.Err {
t.Errorf("Expected function to not error, but got: %v", err)
}
if err == nil && test.Err {
t.Errorf("Expected function to get an error, but got: %v", err)
}
test.Finish(t, resp, err)
})
}
}
func TestMergePodWithPodTemplate(t *testing.T) {
const createdPodKey = "createdPod"
tests := map[string]solverFixture{
"should return one pod that matches": {
Challenge: &v1alpha1.Challenge{
Spec: v1alpha1.ChallengeSpec{
DNSName: "example.com",
Config: &v1alpha1.SolverConfig{
HTTP01: &v1alpha1.HTTP01SolverConfig{},
},
},
},
//Issuer: &v1alpha1.Issuer{},
PreFn: func(t *testing.T, s *solverFixture) {
s.testResources[createdPodKey] = v1.PodTemplate{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"this is": "a label",
},
},
}
s.Builder.Sync()
},
Issuer: generate.Issuer(generate.IssuerConfig{
Name: defaultTestIssuerName,
Namespace: defaultTestNamespace,
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{
PodTemplate: v1.PodTemplate{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"this is a": "label",
},
},
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Command: []string{"this is a command foo"},
},
},
},
},
},
},
}),
CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) {
//createdPod := s.testResources[createdPodKey].(*v1.PodTemplate)
pod, err := s.Solver.buildPod(s.Issuer, s.Challenge)
if err != nil {
t.Error(err)
return
}
fmt.Printf("\n\n\n\n\n%s\n\n\n\n\n", pod)
//resp := args[0].([]*v1.Pod)
//if len(resp) != 1 {
// t.Errorf("expected one pod to be returned, but got %d", len(resp))
// t.Fail()
// return
//}
//if !reflect.DeepEqual(resp[0], createdPod) {
// t.Errorf("Expected %v to equal %v", resp[0], createdPod)
//}
return
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
test.Setup(t)
resp, err := test.Solver.buildPod(test.Issuer, test.Challenge)
if err != nil && !test.Err {
t.Errorf("Expected function to not error, but got: %v", err)
}
@ -188,7 +282,7 @@ func TestGetPodsForCertificate(t *testing.T) {
},
},
PreFn: func(t *testing.T, s *solverFixture) {
ing, err := s.Solver.createPod(s.Challenge)
ing, err := s.Solver.createPod(s.Issuer, s.Challenge)
if err != nil {
t.Errorf("error preparing test: %v", err)
}
@ -221,7 +315,7 @@ func TestGetPodsForCertificate(t *testing.T) {
PreFn: func(t *testing.T, s *solverFixture) {
differentChallenge := s.Challenge.DeepCopy()
differentChallenge.Spec.DNSName = "notexample.com"
_, err := s.Solver.createPod(differentChallenge)
_, err := s.Solver.createPod(s.Issuer, differentChallenge)
if err != nil {
t.Errorf("error preparing test: %v", err)
}