Skip to content

Commit

Permalink
Fix issue sigstore#38. Do not block status updates.
Browse files Browse the repository at this point in the history
Signed-off-by: Ville Aikas <vaikas@chainguard.dev>
  • Loading branch information
vaikas committed Jul 14, 2022
1 parent bd6e9c9 commit 171f97e
Show file tree
Hide file tree
Showing 19 changed files with 816 additions and 60 deletions.
1 change: 1 addition & 0 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func NewMutatingAdmissionController(ctx context.Context, cmw configmap.Watcher)

// A function that infuses the context passed to Validate/SetDefaults with custom metadata.
func(ctx context.Context) context.Context {
ctx = policyduckv1beta1.WithPodScalableDefaulter(ctx, validator.ResolvePodScalable)
ctx = duckv1.WithPodDefaulter(ctx, validator.ResolvePod)
ctx = duckv1.WithPodSpecDefaulter(ctx, validator.ResolvePodSpecable)
ctx = duckv1.WithCronJobDefaulter(ctx, validator.ResolveCronJob)
Expand Down
8 changes: 7 additions & 1 deletion hack/update-codegen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ group "Kubernetes Codegen"
# instead of the $GOPATH directly. For normal projects this can be dropped.
${CODEGEN_PKG}/generate-groups.sh "deepcopy,client,informer,lister" \
github.com/sigstore/policy-controller/pkg/client github.com/sigstore/policy-controller/pkg/apis \
"policy:v1alpha1 policy:v1beta1 duck:v1beta1" \
"policy:v1alpha1 policy:v1beta1" \
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt

group "ducks"
${CODEGEN_PKG}/generate-groups.sh "deepcopy" \
github.com/sigstore/policy-controller/pkg/client github.com/sigstore/policy-controller/pkg/apis \
"duck:v1beta1" \
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt

group "Knative Codegen"
Expand Down
20 changes: 20 additions & 0 deletions pkg/apis/duck/v1beta1/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package v1beta1 contains the Autoscaling v1alpha1 API types.

// +k8s:deepcopy-gen=package
// +groupName=duck.sigstore.policy.dev

package v1beta1
46 changes: 46 additions & 0 deletions pkg/apis/duck/v1beta1/podscalable_defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1beta1

import (
"context"
)

// PodScalableDefaulter is a callback to validate a PodScalable.
type PodScalableDefaulter func(context.Context, *PodScalable)

// SetDefaults implements apis.Defaultable
func (wp *PodScalable) SetDefaults(ctx context.Context) {
if psd := GetPodScalableDefaulter(ctx); psd != nil {
psd(ctx, wp)
}
}

// psdKey is used for associating a PodScalableDefaulter with a context.Context
type psdKey struct{}

func WithPodScalableDefaulter(ctx context.Context, psd PodScalableDefaulter) context.Context {
return context.WithValue(ctx, psdKey{}, psd)
}

// GetPodScalableDefaulter extracts the PodScalableDefaulter from the context.
func GetPodScalableDefaulter(ctx context.Context) PodScalableDefaulter {
untyped := ctx.Value(psdKey{})
if untyped == nil {
return nil
}
return untyped.(PodScalableDefaulter)
}
101 changes: 101 additions & 0 deletions pkg/apis/duck/v1beta1/podscalable_defaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1beta1

import (
"context"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
"knative.dev/pkg/ptr"
)

func TestPodScalableDefaulting(t *testing.T) {
p := PodScalable{
Spec: PodScalableSpec{
Replicas: ptr.Int32(10),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "blah",
Image: "busybox",
}},
},
},
},
}

tests := []struct {
name string
with func(context.Context) context.Context
want *PodScalable
}{{
name: "no check",
with: func(ctx context.Context) context.Context {
return ctx
},
want: p.DeepCopy(),
}, {
name: "no change",
with: func(ctx context.Context) context.Context {
return WithPodScalableDefaulter(ctx, func(ctx context.Context, wp *PodScalable) {
})
},
want: p.DeepCopy(),
}, {
name: "no busybox",
with: func(ctx context.Context) context.Context {
return WithPodScalableDefaulter(ctx, func(ctx context.Context, wp *PodScalable) {
for i, c := range wp.Spec.Template.Spec.InitContainers {
if !strings.Contains(c.Image, "@") {
wp.Spec.Template.Spec.InitContainers[i].Image = c.Image + "@sha256:deadbeef"
}
}
for i, c := range wp.Spec.Template.Spec.Containers {
if !strings.Contains(c.Image, "@") {
wp.Spec.Template.Spec.Containers[i].Image = c.Image + "@sha256:deadbeef"
}
}
})
},
want: &PodScalable{
Spec: PodScalableSpec{
Replicas: ptr.Int32(10),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "blah",
Image: "busybox@sha256:deadbeef",
}},
},
},
},
},
}}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := test.with(context.Background())
got := p.DeepCopy()
got.SetDefaults(ctx)
if !cmp.Equal(test.want, got) {
t.Errorf("SetDefaults (-want, +got) = %s", cmp.Diff(test.want, got))
}
})
}
}
38 changes: 38 additions & 0 deletions pkg/apis/duck/v1beta1/podscalable_implements_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1beta1

import (
"testing"

appsv1 "k8s.io/api/apps/v1"

"knative.dev/pkg/apis/duck"
)

func TestImplementsPodScalable(t *testing.T) {
instances := []interface{}{
&PodScalable{},
&appsv1.ReplicaSet{},
&appsv1.Deployment{},
&appsv1.StatefulSet{},
}
for _, instance := range instances {
if err := duck.VerifyType(instance, &PodScalable{}); err != nil {
t.Error(err)
}
}
}
113 changes: 113 additions & 0 deletions pkg/apis/duck/v1beta1/podscalable_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1beta1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck"
"knative.dev/pkg/ptr"
)

// +genduck
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// PodScalable is a duck type that the are PodSpecable but also can scale.
// These are used to validate resources that can be modified to scale down
// even if they contain invalid images.
type PodScalable struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec PodScalableSpec `json:"spec"`
Status PodScalableStatus `json:"status"`
}

// PodScalableSpec is the specification for the desired state of a
// PodScalable (or at least our shared portion).
type PodScalableSpec struct {
Replicas *int32 `json:"replicas,omitempty"`
Selector *metav1.LabelSelector `json:"selector"`
Template corev1.PodTemplateSpec `json:"template"`
}

// PodScalableStatus is the observed state of a PodScalable (or at
// least our shared portion).
type PodScalableStatus struct {
Replicas int32 `json:"replicas,omitempty"`
}

var (
_ apis.Validatable = (*PodScalable)(nil)
_ duck.Populatable = (*PodScalable)(nil)
_ duck.Implementable = (*PodScalable)(nil)
_ apis.Listable = (*PodScalable)(nil)
)

// GetFullType implements duck.Implementable
func (*PodScalable) GetFullType() duck.Populatable {
return &PodScalable{}
}

// Populate implements duck.Populatable
func (t *PodScalable) Populate() {
t.Spec = PodScalableSpec{
Replicas: ptr.Int32(12),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
MatchExpressions: []metav1.LabelSelectorRequirement{{
Key: "foo",
Operator: "In",
Values: []string{"baz", "blah"},
}},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "container-name",
Image: "container-image:latest",
}},
},
},
}
t.Status = PodScalableStatus{
Replicas: 42,
}
}

// GetListType implements apis.Listable
func (*PodScalable) GetListType() runtime.Object {
return &PodScalableList{}
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// PodScalableList is a list of PodScalable resources
type PodScalableList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`

Items []PodScalable `json:"items"`
}
49 changes: 49 additions & 0 deletions pkg/apis/duck/v1beta1/podscalable_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1beta1

import (
"context"

"knative.dev/pkg/apis"
)

// PodScalableValidator is a callback to validate a PodScalable.
type PodScalableValidator func(context.Context, *PodScalable) *apis.FieldError

// Validate implements apis.Validatable
func (wp *PodScalable) Validate(ctx context.Context) *apis.FieldError {
if psv := GetPodScalableValidator(ctx); psv != nil {
return psv(ctx, wp)
}
return nil
}

// psvKey is used for associating a PodScalableValidator with a context.Context
type psvKey struct{}

func WithPodScalableValidator(ctx context.Context, psv PodScalableValidator) context.Context {
return context.WithValue(ctx, psvKey{}, psv)
}

// GetPodScalableValidator extracts the PodSpecValidator from the context.
func GetPodScalableValidator(ctx context.Context) PodScalableValidator {
untyped := ctx.Value(psvKey{})
if untyped == nil {
return nil
}
return untyped.(PodScalableValidator)
}
Loading

0 comments on commit 171f97e

Please sign in to comment.