mirror of https://github.com/knative/client.git
Export test refactor (#1069)
* vendor changes for importing serving test * refactor export test * vendor changes * lint changes * add comments and address review comments * review comments for #1069 * rebase * remove vendor/license * remove vendor/license
This commit is contained in:
parent
8ca97c7920
commit
c60b85150c
|
|
@ -15,18 +15,44 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1"
|
||||||
|
"knative.dev/pkg/kmeta"
|
||||||
|
"knative.dev/pkg/ptr"
|
||||||
pkgtest "knative.dev/pkg/test"
|
pkgtest "knative.dev/pkg/test"
|
||||||
|
"knative.dev/serving/pkg/apis/config"
|
||||||
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
servingtest "knative.dev/serving/pkg/testing/v1"
|
||||||
|
|
||||||
"knative.dev/client/pkg/util"
|
"knative.dev/client/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExpectedServiceListOption enables further configuration of a ServiceList.
|
||||||
|
type ExpectedServiceListOption func(*servingv1.ServiceList)
|
||||||
|
|
||||||
|
// ExpectedRevisionListOption enables further configuration of a RevisionList.
|
||||||
|
type ExpectedRevisionListOption func(*servingv1.RevisionList)
|
||||||
|
|
||||||
|
// ExpectedKNExportOption enables further configuration of a Export.
|
||||||
|
type ExpectedKNExportOption func(*clientv1alpha1.Export)
|
||||||
|
|
||||||
|
var revisionSpec = servingv1.RevisionSpec{
|
||||||
|
PodSpec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Image: pkgtest.ImagePath("helloworld"),
|
||||||
|
}},
|
||||||
|
EnableServiceLinks: ptr.Bool(false),
|
||||||
|
},
|
||||||
|
TimeoutSeconds: ptr.Int64(config.DefaultRevisionTimeoutSeconds),
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceCreate verifies given service creation in sync mode and also verifies output
|
// ServiceCreate verifies given service creation in sync mode and also verifies output
|
||||||
func ServiceCreate(r *KnRunResultCollector, serviceName string) {
|
func ServiceCreate(r *KnRunResultCollector, serviceName string) {
|
||||||
out := r.KnTest().Kn().Run("service", "create", serviceName, "--image", pkgtest.ImagePath("helloworld"))
|
out := r.KnTest().Kn().Run("service", "create", serviceName, "--image", pkgtest.ImagePath("helloworld"))
|
||||||
|
|
@ -96,6 +122,7 @@ func ServiceDescribeWithJSONPath(r *KnRunResultCollector, serviceName, jsonpath
|
||||||
return out.Stdout
|
return out.Stdout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateServiceResources validates cpu and mem resources
|
||||||
func ValidateServiceResources(r *KnRunResultCollector, serviceName string, requestsMemory, requestsCPU, limitsMemory, limitsCPU string) {
|
func ValidateServiceResources(r *KnRunResultCollector, serviceName string, requestsMemory, requestsCPU, limitsMemory, limitsCPU string) {
|
||||||
var err error
|
var err error
|
||||||
rlist := corev1.ResourceList{}
|
rlist := corev1.ResourceList{}
|
||||||
|
|
@ -123,8 +150,8 @@ func ValidateServiceResources(r *KnRunResultCollector, serviceName string, reque
|
||||||
assert.DeepEqual(r.T(), serviceLimitsResourceList, llist)
|
assert.DeepEqual(r.T(), serviceLimitsResourceList, llist)
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetServiceFromKNServiceDescribe runs the kn service describe command
|
// GetServiceFromKNServiceDescribe runs the kn service describe command
|
||||||
//decodes it into a ksvc and returns it.
|
// decodes it into a ksvc and returns it.
|
||||||
func GetServiceFromKNServiceDescribe(r *KnRunResultCollector, serviceName string) servingv1.Service {
|
func GetServiceFromKNServiceDescribe(r *KnRunResultCollector, serviceName string) servingv1.Service {
|
||||||
out := r.KnTest().Kn().Run("service", "describe", serviceName, "-ojson")
|
out := r.KnTest().Kn().Run("service", "describe", serviceName, "-ojson")
|
||||||
data := json.NewDecoder(strings.NewReader(out.Stdout))
|
data := json.NewDecoder(strings.NewReader(out.Stdout))
|
||||||
|
|
@ -134,3 +161,162 @@ func GetServiceFromKNServiceDescribe(r *KnRunResultCollector, serviceName string
|
||||||
assert.NilError(r.T(), err)
|
assert.NilError(r.T(), err)
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildServiceListWithOptions returns ServiceList with options provided
|
||||||
|
func BuildServiceListWithOptions(options ...ExpectedServiceListOption) *servingv1.ServiceList {
|
||||||
|
list := &servingv1.ServiceList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithService appends the given service to ServiceList
|
||||||
|
func WithService(svc *servingv1.Service) ExpectedServiceListOption {
|
||||||
|
return func(list *servingv1.ServiceList) {
|
||||||
|
list.Items = append(list.Items, *svc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildRevisionListWithOptions returns RevisionList with options provided
|
||||||
|
func BuildRevisionListWithOptions(options ...ExpectedRevisionListOption) *servingv1.RevisionList {
|
||||||
|
list := &servingv1.RevisionList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildKNExportWithOptions returns Export object with the options provided
|
||||||
|
func BuildKNExportWithOptions(options ...ExpectedKNExportOption) *clientv1alpha1.Export {
|
||||||
|
knExport := &clientv1alpha1.Export{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "client.knative.dev/v1alpha1",
|
||||||
|
Kind: "Export",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(knExport)
|
||||||
|
}
|
||||||
|
|
||||||
|
return knExport
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildConfigurationSpec builds servingv1.ConfigurationSpec with the options provided
|
||||||
|
func BuildConfigurationSpec(co ...servingtest.ConfigOption) *servingv1.ConfigurationSpec {
|
||||||
|
c := &servingv1.Configuration{
|
||||||
|
Spec: servingv1.ConfigurationSpec{
|
||||||
|
Template: servingv1.RevisionTemplateSpec{
|
||||||
|
Spec: *revisionSpec.DeepCopy(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, opt := range co {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
c.SetDefaults(context.Background())
|
||||||
|
return &c.Spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildServiceWithOptions returns ksvc with options provided
|
||||||
|
func BuildServiceWithOptions(name string, so ...servingtest.ServiceOption) *servingv1.Service {
|
||||||
|
svc := servingtest.ServiceWithoutNamespace(name, so...)
|
||||||
|
svc.TypeMeta = metav1.TypeMeta{
|
||||||
|
Kind: "Service",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
}
|
||||||
|
svc.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{}
|
||||||
|
return svc
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTrafficSpec adds route to ksvc
|
||||||
|
func WithTrafficSpec(revisions []string, percentages []int, tags []string) servingtest.ServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
var trafficTargets []servingv1.TrafficTarget
|
||||||
|
for i, rev := range revisions {
|
||||||
|
trafficTargets = append(trafficTargets, servingv1.TrafficTarget{
|
||||||
|
Percent: ptr.Int64(int64(percentages[i])),
|
||||||
|
})
|
||||||
|
if tags[i] != "" {
|
||||||
|
trafficTargets[i].Tag = tags[i]
|
||||||
|
}
|
||||||
|
if rev == "latest" {
|
||||||
|
trafficTargets[i].LatestRevision = ptr.Bool(true)
|
||||||
|
} else {
|
||||||
|
trafficTargets[i].RevisionName = rev
|
||||||
|
trafficTargets[i].LatestRevision = ptr.Bool(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svc.Spec.RouteSpec = servingv1.RouteSpec{
|
||||||
|
Traffic: trafficTargets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildRevision returns Revision object with the options provided
|
||||||
|
func BuildRevision(name string, options ...servingtest.RevisionOption) *servingv1.Revision {
|
||||||
|
rev := servingtest.Revision("", name, options...)
|
||||||
|
rev.TypeMeta = metav1.TypeMeta{
|
||||||
|
Kind: "Revision",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
}
|
||||||
|
rev.Spec.PodSpec.Containers[0].Name = config.DefaultUserContainerName
|
||||||
|
rev.Spec.PodSpec.EnableServiceLinks = ptr.Bool(false)
|
||||||
|
rev.ObjectMeta.SelfLink = ""
|
||||||
|
rev.ObjectMeta.Namespace = ""
|
||||||
|
rev.ObjectMeta.UID = ""
|
||||||
|
rev.ObjectMeta.Generation = int64(0)
|
||||||
|
rev.Spec.PodSpec.Containers[0].Resources = corev1.ResourceRequirements{}
|
||||||
|
return rev
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevision appends Revision object to RevisionList
|
||||||
|
func WithRevision(rev servingv1.Revision) ExpectedRevisionListOption {
|
||||||
|
return func(list *servingv1.RevisionList) {
|
||||||
|
list.Items = append(list.Items, rev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKNRevision appends Revision object RevisionList to Kn Export
|
||||||
|
func WithKNRevision(rev servingv1.Revision) ExpectedKNExportOption {
|
||||||
|
return func(export *clientv1alpha1.Export) {
|
||||||
|
export.Spec.Revisions = append(export.Spec.Revisions, rev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionEnv adds env variable to Revision object
|
||||||
|
func WithRevisionEnv(evs ...corev1.EnvVar) servingtest.RevisionOption {
|
||||||
|
return func(s *servingv1.Revision) {
|
||||||
|
s.Spec.PodSpec.Containers[0].Env = evs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionImage adds revision image to Revision object
|
||||||
|
func WithRevisionImage(image string) servingtest.RevisionOption {
|
||||||
|
return func(s *servingv1.Revision) {
|
||||||
|
s.Spec.PodSpec.Containers[0].Image = image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionAnnotations adds annotation to revision spec in ksvc
|
||||||
|
func WithRevisionAnnotations(annotations map[string]string) servingtest.ServiceOption {
|
||||||
|
return func(service *servingv1.Service) {
|
||||||
|
service.Spec.Template.ObjectMeta.Annotations = kmeta.UnionMaps(
|
||||||
|
service.Spec.Template.ObjectMeta.Annotations, annotations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,28 +15,33 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
libtest "knative.dev/client/lib/test"
|
||||||
clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1"
|
clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1"
|
||||||
knclient "knative.dev/client/pkg/serving/v1"
|
knclient "knative.dev/client/pkg/serving/v1"
|
||||||
"knative.dev/client/pkg/util/mock"
|
"knative.dev/client/pkg/util/mock"
|
||||||
"knative.dev/pkg/ptr"
|
"knative.dev/pkg/ptr"
|
||||||
apiserving "knative.dev/serving/pkg/apis/serving"
|
apiserving "knative.dev/serving/pkg/apis/serving"
|
||||||
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
servingtest "knative.dev/serving/pkg/testing/v1"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type expectedServiceOption func(*servingv1.Service)
|
var revisionSpec = servingv1.RevisionSpec{
|
||||||
type expectedRevisionOption func(*servingv1.Revision)
|
PodSpec: v1.PodSpec{
|
||||||
type expectedServiceListOption func(*servingv1.ServiceList)
|
Containers: []v1.Container{{
|
||||||
type expectedRevisionListOption func(*servingv1.RevisionList)
|
Image: "busybox",
|
||||||
type expectedKNExportOption func(*clientv1alpha1.Export)
|
}},
|
||||||
type podSpecOption func(*v1.PodSpec)
|
EnableServiceLinks: ptr.Bool(false),
|
||||||
|
},
|
||||||
|
TimeoutSeconds: ptr.Int64(300),
|
||||||
|
}
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -62,11 +67,11 @@ func TestServiceExportError(t *testing.T) {
|
||||||
func TestServiceExport(t *testing.T) {
|
func TestServiceExport(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range []testCase{
|
for _, tc := range []testCase{
|
||||||
{latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer()))},
|
{latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()))},
|
||||||
{latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer(), withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}})))},
|
{latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "mouse"}))},
|
||||||
{latestSvc: getServiceWithOptions(getService("foo"), withConfigurationLabels(map[string]string{"a": "mouse"}), withConfigurationAnnotations(map[string]string{"a": "mouse"}), withServicePodSpecOption(withContainer()))},
|
{latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v2"}))},
|
||||||
{latestSvc: getServiceWithOptions(getService("foo"), withLabels(map[string]string{"a": "mouse"}), withAnnotations(map[string]string{"a": "mouse"}), withServicePodSpecOption(withContainer()))},
|
{latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithServiceLabel("a", "mouse"), servingtest.WithServiceAnnotation("a", "mouse"))},
|
||||||
{latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer(), withVolumeandSecrets("secretName")))},
|
{latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithVolume("secretName", "/mountpath", volumeSource("secretName")))},
|
||||||
} {
|
} {
|
||||||
exportServiceTest(t, &tc)
|
exportServiceTest(t, &tc)
|
||||||
}
|
}
|
||||||
|
|
@ -80,181 +85,130 @@ func exportServiceTest(t *testing.T, tc *testCase) {
|
||||||
err = yaml.Unmarshal([]byte(output), &actSvc)
|
err = yaml.Unmarshal([]byte(output), &actSvc)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
stripUnwantedFields(tc.latestSvc)
|
|
||||||
assert.DeepEqual(t, tc.latestSvc, &actSvc)
|
assert.DeepEqual(t, tc.latestSvc, &actSvc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceExportwithMultipleRevisions(t *testing.T) {
|
func TestServiceExportwithMultipleRevisions(t *testing.T) {
|
||||||
for _, tc := range []testCase{{
|
for _, tc := range []testCase{{
|
||||||
name: "test 2 revisions with traffic split",
|
name: "test 2 revisions with traffic split",
|
||||||
latestSvc: getServiceWithOptions(
|
latestSvc: libtest.BuildServiceWithOptions(
|
||||||
getService("foo"),
|
"foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withAnnotations(map[string]string{"serving.knative.dev/creator": "ut", "serving.knative.dev/lastModifier": "ut"}),
|
libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v2"}),
|
||||||
withConfigurationAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz2"}),
|
libtest.WithTrafficSpec([]string{"foo-rev-1", "latest"}, []int{50, 50}, []string{"", ""}),
|
||||||
withTrafficSplit([]string{"foo-rev-1", ""}, []int{50, 50}, []bool{false, true}),
|
|
||||||
withServicePodSpecOption(withContainer()),
|
|
||||||
),
|
),
|
||||||
expectedSvcList: getServiceListWithOptions(
|
expectedSvcList: libtest.BuildServiceListWithOptions(
|
||||||
withServices(
|
libtest.WithService(libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
getService("foo"),
|
libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v1"}),
|
||||||
withUnwantedFieldsStripped(),
|
servingtest.WithBYORevisionName("foo-rev-1"),
|
||||||
withConfigurationAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz1"}),
|
)),
|
||||||
withServicePodSpecOption(withContainer()),
|
libtest.WithService(libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withServiceRevisionName("foo-rev-1"),
|
libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v2"}),
|
||||||
),
|
libtest.WithTrafficSpec([]string{"foo-rev-1", "latest"}, []int{50, 50}, []string{"", ""}),
|
||||||
withServices(
|
)),
|
||||||
getService("foo"),
|
|
||||||
withUnwantedFieldsStripped(),
|
|
||||||
withConfigurationAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz2"}),
|
|
||||||
withServicePodSpecOption(withContainer()),
|
|
||||||
withTrafficSplit([]string{"foo-rev-1", ""}, []int{50, 50}, []bool{false, true}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
revisionList: getRevisionListWithOptions(
|
revisionList: libtest.BuildRevisionListWithOptions(
|
||||||
withRevisions(
|
libtest.WithRevision(*(libtest.BuildRevision("foo-rev-1",
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
withRevisionGeneration("1"),
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"),
|
||||||
withRevisionAnnotations(map[string]string{"serving.knative.dev/lastPinned": "1111132"}),
|
servingtest.WithRevisionAnn("client.knative.dev/user-image", "busybox:v1"),
|
||||||
withRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz1"}),
|
servingtest.WithRevisionAnn("serving.knative.dev/lastPinned", "1111132"),
|
||||||
withRevisionName("foo-rev-1"),
|
))),
|
||||||
withRevisionPodSpecOption(withContainer()),
|
libtest.WithRevision(*(libtest.BuildRevision("foo-rev-2",
|
||||||
),
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
withRevisions(
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"),
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
servingtest.WithRevisionAnn("client.knative.dev/user-image", "busybox:v2"),
|
||||||
withRevisionGeneration("2"),
|
))),
|
||||||
withRevisionName("foo-rev-2"),
|
|
||||||
withRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz2"}),
|
|
||||||
withRevisionPodSpecOption(withContainer()),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
expectedKNExport: getKNExportWithOptions(
|
expectedKNExport: libtest.BuildKNExportWithOptions(
|
||||||
withKNRevisions(
|
libtest.WithKNRevision(*(libtest.BuildRevision("foo-rev-1",
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
withRevisionName("foo-rev-1"),
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"),
|
||||||
withRevisionGeneration("1"),
|
servingtest.WithRevisionAnn("client.knative.dev/user-image", "busybox:v1"),
|
||||||
withRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz1"}),
|
))),
|
||||||
withRevisionPodSpecOption(withContainer()),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
}, {
|
}, {
|
||||||
name: "test 2 revisions no traffic split",
|
name: "test 2 revisions no traffic split",
|
||||||
latestSvc: getServiceWithOptions(
|
latestSvc: libtest.BuildServiceWithOptions(
|
||||||
getService("foo"),
|
"foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withTrafficSplit([]string{""}, []int{100}, []bool{true}),
|
libtest.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}),
|
||||||
withServicePodSpecOption(withContainer()),
|
|
||||||
),
|
),
|
||||||
expectedSvcList: getServiceListWithOptions(
|
expectedSvcList: libtest.BuildServiceListWithOptions(
|
||||||
withServices(
|
libtest.WithService(libtest.BuildServiceWithOptions(
|
||||||
getService("foo"),
|
"foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withUnwantedFieldsStripped(),
|
libtest.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}),
|
||||||
withServicePodSpecOption(withContainer()),
|
)),
|
||||||
withTrafficSplit([]string{""}, []int{100}, []bool{true}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
revisionList: getRevisionListWithOptions(
|
revisionList: libtest.BuildRevisionListWithOptions(
|
||||||
withRevisions(
|
libtest.WithRevision(*(libtest.BuildRevision("foo-rev-1",
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"),
|
||||||
withRevisionGeneration("1"),
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
withRevisionName("foo-rev-1"),
|
))),
|
||||||
withRevisionPodSpecOption(withContainer()),
|
libtest.WithRevision(*(libtest.BuildRevision("foo-rev-2",
|
||||||
),
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "2"),
|
||||||
withRevisions(
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
))),
|
||||||
withRevisionGeneration("2"),
|
|
||||||
withRevisionName("foo-rev-2"),
|
|
||||||
withRevisionPodSpecOption(withContainer()),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
expectedKNExport: getKNExportWithOptions(),
|
expectedKNExport: libtest.BuildKNExportWithOptions(),
|
||||||
}, {
|
}, {
|
||||||
name: "test 3 active revisions with traffic split with no latest revision",
|
name: "test 3 active revisions with traffic split with no latest revision",
|
||||||
latestSvc: getServiceWithOptions(
|
latestSvc: libtest.BuildServiceWithOptions(
|
||||||
getService("foo"),
|
"foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withTrafficSplit([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []bool{false, false, false}),
|
servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withServiceRevisionName("foo-rev-3"),
|
servingtest.WithBYORevisionName("foo-rev-3"),
|
||||||
withServicePodSpecOption(
|
libtest.WithTrafficSpec([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []string{"", "", ""}),
|
||||||
withContainer(),
|
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
expectedSvcList: getServiceListWithOptions(
|
expectedSvcList: libtest.BuildServiceListWithOptions(
|
||||||
withServices(
|
libtest.WithService(
|
||||||
getService("foo"),
|
libtest.BuildServiceWithOptions(
|
||||||
withUnwantedFieldsStripped(),
|
"foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "cat"}),
|
||||||
withContainer(),
|
servingtest.WithBYORevisionName("foo-rev-1"),
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}),
|
|
||||||
),
|
|
||||||
withServiceRevisionName("foo-rev-1"),
|
|
||||||
),
|
|
||||||
withServices(
|
|
||||||
getService("foo"),
|
|
||||||
withUnwantedFieldsStripped(),
|
|
||||||
withServicePodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}),
|
|
||||||
),
|
|
||||||
withServiceRevisionName("foo-rev-2"),
|
|
||||||
),
|
|
||||||
withServices(
|
|
||||||
getService("foo"),
|
|
||||||
withUnwantedFieldsStripped(),
|
|
||||||
withServicePodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
withServiceRevisionName("foo-rev-3"),
|
|
||||||
withTrafficSplit([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []bool{false, false, false}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
revisionList: getRevisionListWithOptions(
|
|
||||||
withRevisions(
|
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
|
||||||
withRevisionGeneration("1"),
|
|
||||||
withRevisionName("foo-rev-1"),
|
|
||||||
withRevisionPodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
withRevisions(
|
libtest.WithService(
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
libtest.BuildServiceWithOptions(
|
||||||
withRevisionGeneration("2"),
|
"foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withRevisionName("foo-rev-2"),
|
servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "dog"}),
|
||||||
withRevisionPodSpecOption(
|
servingtest.WithBYORevisionName("foo-rev-2"),
|
||||||
withContainer(),
|
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
withRevisions(
|
libtest.WithService(
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
libtest.BuildServiceWithOptions(
|
||||||
withRevisionGeneration("3"),
|
"foo", servingtest.WithConfigSpec(buildConfiguration()),
|
||||||
withRevisionName("foo-rev-3"),
|
servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withRevisionPodSpecOption(
|
servingtest.WithBYORevisionName("foo-rev-3"),
|
||||||
withContainer(),
|
libtest.WithTrafficSpec([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []string{"", "", ""}),
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectedKNExport: getKNExportWithOptions(
|
revisionList: libtest.BuildRevisionListWithOptions(
|
||||||
withKNRevisions(
|
libtest.WithRevision(*(libtest.BuildRevision("foo-rev-1",
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"),
|
||||||
withRevisionName("foo-rev-1"),
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
withRevisionGeneration("1"),
|
libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "cat"}),
|
||||||
withRevisionPodSpecOption(
|
))),
|
||||||
withContainer(),
|
libtest.WithRevision(*(libtest.BuildRevision("foo-rev-2",
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}),
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "2"),
|
||||||
),
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
),
|
libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "dog"}),
|
||||||
withKNRevisions(
|
))),
|
||||||
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
libtest.WithRevision(*(libtest.BuildRevision("foo-rev-3",
|
||||||
withRevisionName("foo-rev-2"),
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "3"),
|
||||||
withRevisionGeneration("2"),
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
withRevisionPodSpecOption(
|
libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withContainer(),
|
))),
|
||||||
withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}),
|
),
|
||||||
),
|
expectedKNExport: libtest.BuildKNExportWithOptions(
|
||||||
),
|
libtest.WithKNRevision(*(libtest.BuildRevision("foo-rev-1",
|
||||||
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"),
|
||||||
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
|
libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "cat"}),
|
||||||
|
))),
|
||||||
|
libtest.WithKNRevision(*(libtest.BuildRevision("foo-rev-2",
|
||||||
|
servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "2"),
|
||||||
|
servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"),
|
||||||
|
libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "dog"}),
|
||||||
|
))),
|
||||||
),
|
),
|
||||||
}} {
|
}} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
@ -278,7 +232,6 @@ func exportWithRevisionsTest(t *testing.T, tc *testCase) {
|
||||||
output, err := executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "--with-revisions", "--mode", "export", "-o", "json")
|
output, err := executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "--with-revisions", "--mode", "export", "-o", "json")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
stripUnwantedFields(tc.latestSvc)
|
|
||||||
tc.expectedKNExport.Spec.Service = *tc.latestSvc
|
tc.expectedKNExport.Spec.Service = *tc.latestSvc
|
||||||
|
|
||||||
actKNExport := &clientv1alpha1.Export{}
|
actKNExport := &clientv1alpha1.Export{}
|
||||||
|
|
@ -298,226 +251,22 @@ func executeServiceExportCommand(t *testing.T, tc *testCase, options ...string)
|
||||||
return executeServiceCommand(client, options...)
|
return executeServiceCommand(client, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripUnwantedFields(svc *servingv1.Service) {
|
func buildConfiguration() *servingv1.ConfigurationSpec {
|
||||||
svc.ObjectMeta.Namespace = ""
|
c := &servingv1.Configuration{
|
||||||
svc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{}
|
Spec: servingv1.ConfigurationSpec{
|
||||||
svc.Status = servingv1.ServiceStatus{}
|
Template: servingv1.RevisionTemplateSpec{
|
||||||
svc.ObjectMeta.CreationTimestamp = metav1.Time{}
|
Spec: *revisionSpec.DeepCopy(),
|
||||||
for _, annotation := range IGNORED_SERVICE_ANNOTATIONS {
|
|
||||||
delete(svc.ObjectMeta.Annotations, annotation)
|
|
||||||
}
|
|
||||||
if len(svc.ObjectMeta.Annotations) == 0 {
|
|
||||||
svc.ObjectMeta.Annotations = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServiceListWithOptions(options ...expectedServiceListOption) *servingv1.ServiceList {
|
|
||||||
list := &servingv1.ServiceList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func withServices(svc *servingv1.Service, options ...expectedServiceOption) expectedServiceListOption {
|
|
||||||
return func(list *servingv1.ServiceList) {
|
|
||||||
list.Items = append(list.Items, *(getServiceWithOptions(svc, options...)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRevisionListWithOptions(options ...expectedRevisionListOption) *servingv1.RevisionList {
|
|
||||||
list := &servingv1.RevisionList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func withRevisions(options ...expectedRevisionOption) expectedRevisionListOption {
|
|
||||||
return func(list *servingv1.RevisionList) {
|
|
||||||
list.Items = append(list.Items, getRevisionWithOptions(options...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKNExportWithOptions(options ...expectedKNExportOption) *clientv1alpha1.Export {
|
|
||||||
knExport := &clientv1alpha1.Export{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "client.knative.dev/v1alpha1",
|
|
||||||
Kind: "Export",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(knExport)
|
|
||||||
}
|
|
||||||
|
|
||||||
return knExport
|
|
||||||
}
|
|
||||||
|
|
||||||
func withKNRevisions(options ...expectedRevisionOption) expectedKNExportOption {
|
|
||||||
return func(export *clientv1alpha1.Export) {
|
|
||||||
export.Spec.Revisions = append(export.Spec.Revisions, getRevisionWithOptions(options...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServiceWithOptions(svc *servingv1.Service, options ...expectedServiceOption) *servingv1.Service {
|
|
||||||
svc.TypeMeta = metav1.TypeMeta{
|
|
||||||
Kind: "service",
|
|
||||||
APIVersion: "serving.knative.dev/v1",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(svc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc
|
|
||||||
}
|
|
||||||
func withLabels(labels map[string]string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.ObjectMeta.Labels = labels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withConfigurationLabels(labels map[string]string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.Template.ObjectMeta.Labels = labels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withAnnotations(Annotations map[string]string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.ObjectMeta.Annotations = Annotations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withConfigurationAnnotations(Annotations map[string]string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.Template.ObjectMeta.Annotations = Annotations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withServiceRevisionName(name string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.Template.ObjectMeta.Name = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withUnwantedFieldsStripped() expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.ObjectMeta.Namespace = ""
|
|
||||||
svc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{}
|
|
||||||
svc.Status = servingv1.ServiceStatus{}
|
|
||||||
svc.ObjectMeta.CreationTimestamp = metav1.Time{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withTrafficSplit(revisions []string, percentages []int, latest []bool) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
var trafficTargets []servingv1.TrafficTarget
|
|
||||||
for i, rev := range revisions {
|
|
||||||
trafficTargets = append(trafficTargets, servingv1.TrafficTarget{
|
|
||||||
Percent: ptr.Int64(int64(percentages[i])),
|
|
||||||
})
|
|
||||||
if latest[i] {
|
|
||||||
trafficTargets[i].LatestRevision = ptr.Bool(true)
|
|
||||||
} else {
|
|
||||||
trafficTargets[i].RevisionName = rev
|
|
||||||
trafficTargets[i].LatestRevision = ptr.Bool(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
svc.Spec.RouteSpec = servingv1.RouteSpec{
|
|
||||||
Traffic: trafficTargets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withServicePodSpecOption(options ...podSpecOption) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.Template.Spec.PodSpec = getPodSpecWithOptions(options...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRevisionWithOptions(options ...expectedRevisionOption) servingv1.Revision {
|
|
||||||
rev := servingv1.Revision{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Revision",
|
|
||||||
APIVersion: "serving.knative.dev/v1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(&rev)
|
|
||||||
}
|
|
||||||
return rev
|
|
||||||
}
|
|
||||||
func withRevisionGeneration(gen string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Labels[apiserving.ConfigurationGenerationLabelKey] = gen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withRevisionName(name string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Name = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withRevisionLabels(labels map[string]string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Labels = labels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withRevisionAnnotations(Annotations map[string]string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Annotations = Annotations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withRevisionPodSpecOption(options ...podSpecOption) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.Spec.PodSpec = getPodSpecWithOptions(options...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPodSpecWithOptions(options ...podSpecOption) v1.PodSpec {
|
|
||||||
spec := v1.PodSpec{}
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(&spec)
|
|
||||||
}
|
|
||||||
return spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func withEnv(env []v1.EnvVar) podSpecOption {
|
|
||||||
return func(spec *v1.PodSpec) {
|
|
||||||
spec.Containers[0].Env = env
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withContainer() podSpecOption {
|
|
||||||
return func(spec *v1.PodSpec) {
|
|
||||||
spec.Containers = append(spec.Containers, v1.Container{Image: "gcr.io/foo/bar:baz"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withVolumeandSecrets(secretName string) podSpecOption {
|
|
||||||
return func(spec *v1.PodSpec) {
|
|
||||||
spec.Volumes = []v1.Volume{
|
|
||||||
{
|
|
||||||
Name: secretName,
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
Secret: &v1.SecretVolumeSource{
|
|
||||||
SecretName: secretName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
spec.Containers[0].VolumeMounts = []v1.VolumeMount{
|
}
|
||||||
{
|
c.SetDefaults(context.Background())
|
||||||
Name: secretName,
|
return &c.Spec
|
||||||
MountPath: "/mount/path",
|
}
|
||||||
ReadOnly: true,
|
|
||||||
},
|
func volumeSource(secretName string) v1.VolumeSource {
|
||||||
}
|
return v1.VolumeSource{
|
||||||
|
Secret: &v1.SecretVolumeSource{
|
||||||
|
SecretName: secretName,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,25 +25,17 @@ import (
|
||||||
|
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
|
||||||
"knative.dev/pkg/ptr"
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"knative.dev/client/lib/test"
|
"knative.dev/client/lib/test"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1"
|
clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1"
|
||||||
pkgtest "knative.dev/pkg/test"
|
pkgtest "knative.dev/pkg/test"
|
||||||
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
servingtest "knative.dev/serving/pkg/testing/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type expectedServiceOption func(*servingv1.Service)
|
|
||||||
type expectedRevisionOption func(*servingv1.Revision)
|
|
||||||
type expectedServiceListOption func(*servingv1.ServiceList)
|
|
||||||
type expectedKNExportOption func(*clientv1alpha1.Export)
|
|
||||||
type podSpecOption func(*corev1.PodSpec)
|
|
||||||
|
|
||||||
func TestServiceExport(t *testing.T) {
|
func TestServiceExport(t *testing.T) {
|
||||||
//FIXME: enable once 0.19 is available
|
//FIXME: enable once 0.19 is available
|
||||||
// see: https://github.com/knative/serving/pull/9685
|
// see: https://github.com/knative/serving/pull/9685
|
||||||
|
|
@ -64,105 +56,75 @@ func TestServiceExport(t *testing.T) {
|
||||||
serviceCreateWithOptions(r, "hello", "--revision-name", "rev1")
|
serviceCreateWithOptions(r, "hello", "--revision-name", "rev1")
|
||||||
|
|
||||||
t.Log("export service-revision1 and compare")
|
t.Log("export service-revision1 and compare")
|
||||||
serviceExport(r, "hello", getServiceWithOptions(
|
serviceExport(r, "hello", test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev1"),
|
servingtest.WithBYORevisionName("hello-rev1"),
|
||||||
withConfigurationAnnotations(map[string]string{
|
test.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": pkgtest.ImagePath("helloworld")}),
|
||||||
"client.knative.dev/user-image": pkgtest.ImagePath("helloworld"),
|
|
||||||
}),
|
|
||||||
withServicePodSpecOption(withContainer()),
|
|
||||||
), "-o", "json")
|
), "-o", "json")
|
||||||
|
|
||||||
t.Log("update service - add env variable")
|
t.Log("update service - add env variable")
|
||||||
test.ServiceUpdate(r, "hello", "--env", "a=mouse", "--revision-name", "rev2", "--no-lock-to-digest")
|
test.ServiceUpdate(r, "hello", "--env", "a=mouse", "--revision-name", "rev2", "--no-lock-to-digest")
|
||||||
serviceExport(r, "hello", getServiceWithOptions(
|
serviceExport(r, "hello", test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev2"),
|
servingtest.WithBYORevisionName("hello-rev2"),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withContainer(),
|
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
), "-o", "json")
|
), "-o", "json")
|
||||||
|
|
||||||
t.Log("export service-revision2 with kubernetes-resources")
|
t.Log("export service-revision2 with kubernetes-resources")
|
||||||
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions(
|
||||||
withServices(
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev2"),
|
servingtest.WithBYORevisionName("hello-rev2"),
|
||||||
withTrafficSplit([]string{"latest"}, []int{100}, []string{""}),
|
test.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withContainer(),
|
)),
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
||||||
|
|
||||||
t.Log("export service-revision2 with revisions-only")
|
t.Log("export service-revision2 with revisions-only")
|
||||||
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev2"),
|
servingtest.WithBYORevisionName("hello-rev2"),
|
||||||
withTrafficSplit([]string{"latest"}, []int{100}, []string{""}),
|
test.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withContainer(),
|
), test.BuildKNExportWithOptions(), "--with-revisions", "--mode", "export", "-o", "yaml")
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
), getKNExportWithOptions(), "--with-revisions", "--mode", "export", "-o", "yaml")
|
|
||||||
|
|
||||||
t.Log("update service with tag and split traffic")
|
t.Log("update service with tag and split traffic")
|
||||||
test.ServiceUpdate(r, "hello", "--tag", "hello-rev1=candidate", "--traffic", "candidate=2%,@latest=98%")
|
test.ServiceUpdate(r, "hello", "--tag", "hello-rev1=candidate", "--traffic", "candidate=2%,@latest=98%")
|
||||||
|
|
||||||
t.Log("export service-revision2 after tagging kubernetes-resources")
|
t.Log("export service-revision2 after tagging kubernetes-resources")
|
||||||
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions(
|
||||||
withServices(
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev1"),
|
servingtest.WithBYORevisionName("hello-rev1"),
|
||||||
withConfigurationAnnotations(map[string]string{
|
test.WithRevisionAnnotations(map[string]string{
|
||||||
"client.knative.dev/user-image": pkgtest.ImagePath("helloworld"),
|
"client.knative.dev/user-image": pkgtest.ImagePath("helloworld"),
|
||||||
"serving.knative.dev/routes": "hello",
|
"serving.knative.dev/routes": "hello",
|
||||||
}),
|
}),
|
||||||
withServicePodSpecOption(
|
)),
|
||||||
withContainer(),
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
),
|
servingtest.WithBYORevisionName("hello-rev2"),
|
||||||
withServices(
|
test.WithTrafficSpec([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
||||||
withServiceName("hello"),
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withServiceRevisionName("hello-rev2"),
|
)),
|
||||||
withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
|
||||||
withServicePodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
||||||
|
|
||||||
t.Log("export service-revision2 after tagging with revisions-only")
|
t.Log("export service-revision2 after tagging with revisions-only")
|
||||||
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev2"),
|
servingtest.WithBYORevisionName("hello-rev2"),
|
||||||
withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
test.WithTrafficSpec([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withContainer(),
|
), test.BuildKNExportWithOptions(
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
test.WithKNRevision(*(test.BuildRevision("hello-rev1",
|
||||||
),
|
servingtest.WithRevisionAnn("client.knative.dev/user-image", pkgtest.ImagePath("helloworld")),
|
||||||
), getKNExportWithOptions(
|
servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"),
|
||||||
withRevisions(
|
servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"),
|
||||||
withRevisionName("hello-rev1"),
|
servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "1"),
|
||||||
withRevisionAnnotations(
|
servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"),
|
||||||
map[string]string{
|
servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"),
|
||||||
"client.knative.dev/user-image": pkgtest.ImagePath("helloworld"),
|
test.WithRevisionImage(pkgtest.ImagePath("helloworld")),
|
||||||
"serving.knative.dev/routes": "hello",
|
))),
|
||||||
}),
|
|
||||||
withRevisionLabels(
|
|
||||||
map[string]string{
|
|
||||||
"serving.knative.dev/configuration": "hello",
|
|
||||||
"serving.knative.dev/configurationGeneration": "1",
|
|
||||||
"serving.knative.dev/routingState": "active",
|
|
||||||
"serving.knative.dev/service": "hello",
|
|
||||||
}),
|
|
||||||
withRevisionPodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), "--with-revisions", "--mode", "export", "-o", "yaml")
|
), "--with-revisions", "--mode", "export", "-o", "yaml")
|
||||||
|
|
||||||
t.Log("update service - untag, add env variable, traffic split and system revision name")
|
t.Log("update service - untag, add env variable, traffic split and system revision name")
|
||||||
|
|
@ -170,148 +132,105 @@ func TestServiceExport(t *testing.T) {
|
||||||
test.ServiceUpdate(r, "hello", "--env", "b=cat", "--revision-name", "hello-rev3", "--traffic", "hello-rev1=30,hello-rev2=30,hello-rev3=40")
|
test.ServiceUpdate(r, "hello", "--env", "b=cat", "--revision-name", "hello-rev3", "--traffic", "hello-rev1=30,hello-rev2=30,hello-rev3=40")
|
||||||
|
|
||||||
t.Log("export service-revision3 with kubernetes-resources")
|
t.Log("export service-revision3 with kubernetes-resources")
|
||||||
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions(
|
||||||
withServices(
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withConfigurationAnnotations(map[string]string{
|
test.WithRevisionAnnotations(map[string]string{
|
||||||
"client.knative.dev/user-image": pkgtest.ImagePath("helloworld"),
|
"client.knative.dev/user-image": pkgtest.ImagePath("helloworld"),
|
||||||
"serving.knative.dev/routes": "hello",
|
"serving.knative.dev/routes": "hello",
|
||||||
}),
|
}),
|
||||||
withServiceRevisionName("hello-rev1"),
|
servingtest.WithBYORevisionName("hello-rev1"),
|
||||||
withServicePodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
withServices(
|
),
|
||||||
withServiceName("hello"),
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
withServiceRevisionName("hello-rev2"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withConfigurationAnnotations(map[string]string{
|
servingtest.WithBYORevisionName("hello-rev2"),
|
||||||
|
test.WithRevisionAnnotations(map[string]string{
|
||||||
"serving.knative.dev/routes": "hello",
|
"serving.knative.dev/routes": "hello",
|
||||||
}),
|
}),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withContainer(),
|
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
withServices(
|
|
||||||
withServiceName("hello"),
|
|
||||||
withServiceRevisionName("hello-rev3"),
|
|
||||||
withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
|
||||||
withServicePodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
|
servingtest.WithBYORevisionName("hello-rev3"),
|
||||||
|
test.WithTrafficSpec([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
||||||
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}),
|
||||||
|
),
|
||||||
|
)), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
||||||
|
|
||||||
t.Log("export service-revision3 with revisions-only")
|
t.Log("export service-revision3 with revisions-only")
|
||||||
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev3"),
|
test.WithTrafficSpec([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
||||||
withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
servingtest.WithBYORevisionName("hello-rev3"),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}),
|
||||||
withContainer(),
|
), test.BuildKNExportWithOptions(
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
test.WithKNRevision(*(test.BuildRevision("hello-rev1",
|
||||||
),
|
servingtest.WithRevisionAnn("client.knative.dev/user-image", pkgtest.ImagePath("helloworld")),
|
||||||
), getKNExportWithOptions(
|
servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"),
|
||||||
withRevisions(
|
servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"),
|
||||||
withRevisionName("hello-rev1"),
|
servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "1"),
|
||||||
withRevisionAnnotations(
|
servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"),
|
||||||
map[string]string{
|
servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"),
|
||||||
"client.knative.dev/user-image": pkgtest.ImagePath("helloworld"),
|
test.WithRevisionImage(pkgtest.ImagePath("helloworld")),
|
||||||
"serving.knative.dev/routes": "hello",
|
))),
|
||||||
}),
|
test.WithKNRevision(*(test.BuildRevision("hello-rev2",
|
||||||
withRevisionLabels(
|
servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"),
|
||||||
map[string]string{
|
servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"),
|
||||||
"serving.knative.dev/configuration": "hello",
|
servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "2"),
|
||||||
"serving.knative.dev/configurationGeneration": "1",
|
servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"),
|
||||||
"serving.knative.dev/routingState": "active",
|
servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"),
|
||||||
"serving.knative.dev/service": "hello",
|
test.WithRevisionImage(pkgtest.ImagePath("helloworld")),
|
||||||
}),
|
test.WithRevisionEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withRevisionPodSpecOption(
|
))),
|
||||||
withContainer(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
withRevisions(
|
|
||||||
withRevisionName("hello-rev2"),
|
|
||||||
withRevisionAnnotations(
|
|
||||||
map[string]string{
|
|
||||||
"serving.knative.dev/routes": "hello",
|
|
||||||
}),
|
|
||||||
withRevisionLabels(
|
|
||||||
map[string]string{
|
|
||||||
"serving.knative.dev/configuration": "hello",
|
|
||||||
"serving.knative.dev/configurationGeneration": "2",
|
|
||||||
"serving.knative.dev/routingState": "active",
|
|
||||||
"serving.knative.dev/service": "hello",
|
|
||||||
}),
|
|
||||||
withRevisionPodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), "--with-revisions", "--mode", "export", "-o", "yaml")
|
), "--with-revisions", "--mode", "export", "-o", "yaml")
|
||||||
|
|
||||||
t.Log("send all traffic to revision 2")
|
t.Log("send all traffic to revision 2")
|
||||||
test.ServiceUpdate(r, "hello", "--traffic", "hello-rev2=100")
|
test.ServiceUpdate(r, "hello", "--traffic", "hello-rev2=100")
|
||||||
|
|
||||||
t.Log("export kubernetes-resources - all traffic to revision 2")
|
t.Log("export kubernetes-resources - all traffic to revision 2")
|
||||||
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions(
|
||||||
withServices(
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev2"),
|
servingtest.WithBYORevisionName("hello-rev2"),
|
||||||
withConfigurationAnnotations(map[string]string{
|
test.WithRevisionAnnotations(map[string]string{
|
||||||
"serving.knative.dev/routes": "hello",
|
"serving.knative.dev/routes": "hello",
|
||||||
}),
|
}),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withContainer(),
|
),
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
),
|
||||||
),
|
test.WithService(test.BuildServiceWithOptions("hello",
|
||||||
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
|
test.WithTrafficSpec([]string{"hello-rev2"}, []int{100}, []string{""}),
|
||||||
|
servingtest.WithBYORevisionName("hello-rev3"),
|
||||||
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}),
|
||||||
),
|
),
|
||||||
withServices(
|
|
||||||
withServiceName("hello"),
|
|
||||||
withServiceRevisionName("hello-rev3"),
|
|
||||||
withTrafficSplit([]string{"hello-rev2"}, []int{100}, []string{""}),
|
|
||||||
withServicePodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
), "--with-revisions", "--mode", "replay", "-o", "yaml")
|
||||||
|
|
||||||
t.Log("export revisions-only - all traffic to revision 2")
|
t.Log("export revisions-only - all traffic to revision 2")
|
||||||
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello",
|
||||||
withServiceName("hello"),
|
servingtest.WithConfigSpec(test.BuildConfigurationSpec()),
|
||||||
withServiceRevisionName("hello-rev3"),
|
servingtest.WithBYORevisionName("hello-rev3"),
|
||||||
withTrafficSplit([]string{"hello-rev2"}, []int{100}, []string{""}),
|
test.WithTrafficSpec([]string{"hello-rev2"}, []int{100}, []string{""}),
|
||||||
withServicePodSpecOption(
|
servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}),
|
||||||
withContainer(),
|
), test.BuildKNExportWithOptions(
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
test.WithKNRevision(*(test.BuildRevision("hello-rev2",
|
||||||
),
|
servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"),
|
||||||
), getKNExportWithOptions(
|
servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"),
|
||||||
withRevisions(
|
servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "2"),
|
||||||
withRevisionName("hello-rev2"),
|
servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"),
|
||||||
withRevisionAnnotations(map[string]string{
|
servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"),
|
||||||
"serving.knative.dev/routes": "hello",
|
test.WithRevisionImage(pkgtest.ImagePath("helloworld")),
|
||||||
}),
|
test.WithRevisionEnv(corev1.EnvVar{Name: "a", Value: "mouse"}),
|
||||||
withRevisionLabels(
|
))),
|
||||||
map[string]string{
|
|
||||||
"serving.knative.dev/configuration": "hello",
|
|
||||||
"serving.knative.dev/configurationGeneration": "2",
|
|
||||||
"serving.knative.dev/routingState": "active",
|
|
||||||
"serving.knative.dev/service": "hello",
|
|
||||||
}),
|
|
||||||
withRevisionPodSpecOption(
|
|
||||||
withContainer(),
|
|
||||||
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), "--with-revisions", "--mode", "export", "-o", "yaml")
|
), "--with-revisions", "--mode", "export", "-o", "yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
|
|
||||||
func serviceExport(r *test.KnRunResultCollector, serviceName string, expService servingv1.Service, options ...string) {
|
func serviceExport(r *test.KnRunResultCollector, serviceName string, expService *servingv1.Service, options ...string) {
|
||||||
command := []string{"service", "export", serviceName}
|
command := []string{"service", "export", serviceName}
|
||||||
command = append(command, options...)
|
command = append(command, options...)
|
||||||
out := r.KnTest().Kn().Run(command...)
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
|
@ -319,7 +238,7 @@ func serviceExport(r *test.KnRunResultCollector, serviceName string, expService
|
||||||
r.AssertNoError(out)
|
r.AssertNoError(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceExportWithServiceList(r *test.KnRunResultCollector, serviceName string, expServiceList servingv1.ServiceList, options ...string) {
|
func serviceExportWithServiceList(r *test.KnRunResultCollector, serviceName string, expServiceList *servingv1.ServiceList, options ...string) {
|
||||||
command := []string{"service", "export", serviceName}
|
command := []string{"service", "export", serviceName}
|
||||||
command = append(command, options...)
|
command = append(command, options...)
|
||||||
out := r.KnTest().Kn().Run(command...)
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
|
@ -327,7 +246,7 @@ func serviceExportWithServiceList(r *test.KnRunResultCollector, serviceName stri
|
||||||
r.AssertNoError(out)
|
r.AssertNoError(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceExportWithRevisionList(r *test.KnRunResultCollector, serviceName string, expService servingv1.Service, knExport clientv1alpha1.Export, options ...string) {
|
func serviceExportWithRevisionList(r *test.KnRunResultCollector, serviceName string, expService *servingv1.Service, knExport *clientv1alpha1.Export, options ...string) {
|
||||||
command := []string{"service", "export", serviceName}
|
command := []string{"service", "export", serviceName}
|
||||||
command = append(command, options...)
|
command = append(command, options...)
|
||||||
out := r.KnTest().Kn().Run(command...)
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
|
@ -337,195 +256,25 @@ func serviceExportWithRevisionList(r *test.KnRunResultCollector, serviceName str
|
||||||
|
|
||||||
// Private functions
|
// Private functions
|
||||||
|
|
||||||
func validateExportedService(t *testing.T, it *test.KnTest, out string, expService servingv1.Service) {
|
func validateExportedService(t *testing.T, it *test.KnTest, out string, expService *servingv1.Service) {
|
||||||
actSvc := servingv1.Service{}
|
actSvc := servingv1.Service{}
|
||||||
err := json.Unmarshal([]byte(out), &actSvc)
|
err := json.Unmarshal([]byte(out), &actSvc)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, &expService, &actSvc)
|
assert.DeepEqual(t, expService, &actSvc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateExportedServiceList(t *testing.T, it *test.KnTest, out string, expServiceList servingv1.ServiceList) {
|
func validateExportedServiceList(t *testing.T, it *test.KnTest, out string, expServiceList *servingv1.ServiceList) {
|
||||||
actSvcList := servingv1.ServiceList{}
|
actSvcList := servingv1.ServiceList{}
|
||||||
err := yaml.Unmarshal([]byte(out), &actSvcList)
|
err := yaml.Unmarshal([]byte(out), &actSvcList)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, &expServiceList, &actSvcList)
|
assert.DeepEqual(t, expServiceList, &actSvcList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateExportedServiceandRevisionList(t *testing.T, it *test.KnTest, out string, expService servingv1.Service, knExport clientv1alpha1.Export) {
|
func validateExportedServiceandRevisionList(t *testing.T, it *test.KnTest, out string, expService *servingv1.Service, knExport *clientv1alpha1.Export) {
|
||||||
actSvc := clientv1alpha1.Export{}
|
actSvc := clientv1alpha1.Export{}
|
||||||
err := yaml.Unmarshal([]byte(out), &actSvc)
|
err := yaml.Unmarshal([]byte(out), &actSvc)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
knExport.Spec.Service = expService
|
knExport.Spec.Service = *expService
|
||||||
assert.DeepEqual(t, &knExport, &actSvc)
|
assert.DeepEqual(t, knExport, &actSvc)
|
||||||
}
|
|
||||||
|
|
||||||
func getServiceListWithOptions(options ...expectedServiceListOption) servingv1.ServiceList {
|
|
||||||
list := servingv1.ServiceList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(&list)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func withServices(options ...expectedServiceOption) expectedServiceListOption {
|
|
||||||
return func(list *servingv1.ServiceList) {
|
|
||||||
list.Items = append(list.Items, getServiceWithOptions(options...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKNExportWithOptions(options ...expectedKNExportOption) clientv1alpha1.Export {
|
|
||||||
knExport := clientv1alpha1.Export{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "client.knative.dev/v1alpha1",
|
|
||||||
Kind: "Export",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(&knExport)
|
|
||||||
}
|
|
||||||
|
|
||||||
return knExport
|
|
||||||
}
|
|
||||||
|
|
||||||
func withRevisions(options ...expectedRevisionOption) expectedKNExportOption {
|
|
||||||
return func(export *clientv1alpha1.Export) {
|
|
||||||
export.Spec.Revisions = append(export.Spec.Revisions, getRevisionWithOptions(options...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServiceWithOptions(options ...expectedServiceOption) servingv1.Service {
|
|
||||||
svc := servingv1.Service{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Service",
|
|
||||||
APIVersion: "serving.knative.dev/v1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(&svc)
|
|
||||||
}
|
|
||||||
svc.Spec.Template.Spec.ContainerConcurrency = ptr.Int64(int64(0))
|
|
||||||
svc.Spec.Template.Spec.TimeoutSeconds = ptr.Int64(int64(300))
|
|
||||||
|
|
||||||
return svc
|
|
||||||
}
|
|
||||||
func withServiceName(name string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.ObjectMeta.Name = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withConfigurationAnnotations(annotations map[string]string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.Template.ObjectMeta.Annotations = annotations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withServiceRevisionName(name string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.Template.ObjectMeta.Name = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withTrafficSplit(revisions []string, percentages []int, tags []string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
var trafficTargets []servingv1.TrafficTarget
|
|
||||||
for i, rev := range revisions {
|
|
||||||
trafficTargets = append(trafficTargets, servingv1.TrafficTarget{
|
|
||||||
Percent: ptr.Int64(int64(percentages[i])),
|
|
||||||
})
|
|
||||||
if tags[i] != "" {
|
|
||||||
trafficTargets[i].Tag = tags[i]
|
|
||||||
}
|
|
||||||
if rev == "latest" {
|
|
||||||
trafficTargets[i].LatestRevision = ptr.Bool(true)
|
|
||||||
} else {
|
|
||||||
trafficTargets[i].RevisionName = rev
|
|
||||||
trafficTargets[i].LatestRevision = ptr.Bool(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
svc.Spec.RouteSpec = servingv1.RouteSpec{
|
|
||||||
Traffic: trafficTargets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withServicePodSpecOption(options ...podSpecOption) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.Template.Spec.PodSpec = getPodSpecWithOptions(options...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func getRevisionWithOptions(options ...expectedRevisionOption) servingv1.Revision {
|
|
||||||
rev := servingv1.Revision{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Revision",
|
|
||||||
APIVersion: "serving.knative.dev/v1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(&rev)
|
|
||||||
}
|
|
||||||
rev.Spec.ContainerConcurrency = ptr.Int64(int64(0))
|
|
||||||
rev.Spec.TimeoutSeconds = ptr.Int64(int64(300))
|
|
||||||
return rev
|
|
||||||
}
|
|
||||||
func withRevisionName(name string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Name = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withRevisionLabels(labels map[string]string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Labels = labels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withRevisionAnnotations(Annotations map[string]string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Annotations = Annotations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func withRevisionPodSpecOption(options ...podSpecOption) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.Spec.PodSpec = getPodSpecWithOptions(options...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPodSpecWithOptions(options ...podSpecOption) corev1.PodSpec {
|
|
||||||
spec := corev1.PodSpec{}
|
|
||||||
for _, fn := range options {
|
|
||||||
fn(&spec)
|
|
||||||
}
|
|
||||||
// Service links are disabled by default now see https://github.com/knative/serving/pull/9685
|
|
||||||
spec.EnableServiceLinks = ptr.Bool(false)
|
|
||||||
return spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func withEnv(env []corev1.EnvVar) podSpecOption {
|
|
||||||
return func(spec *corev1.PodSpec) {
|
|
||||||
spec.Containers[0].Env = env
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withContainer() podSpecOption {
|
|
||||||
return func(spec *corev1.PodSpec) {
|
|
||||||
spec.Containers = []corev1.Container{
|
|
||||||
{
|
|
||||||
Name: "user-container",
|
|
||||||
Image: pkgtest.ImagePath("helloworld"),
|
|
||||||
Resources: corev1.ResourceRequirements{},
|
|
||||||
ReadinessProbe: &corev1.Probe{
|
|
||||||
SuccessThreshold: int32(1),
|
|
||||||
Handler: corev1.Handler{
|
|
||||||
TCPSocket: &corev1.TCPSocketAction{
|
|
||||||
Port: intstr.FromInt(0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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 gc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
cm "knative.dev/pkg/configmap"
|
||||||
|
"knative.dev/pkg/controller"
|
||||||
|
"knative.dev/pkg/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ConfigName is the name of the config map for garbage collection.
|
||||||
|
ConfigName = "config-gc"
|
||||||
|
|
||||||
|
// Disabled is the value (-1) used by various config map values to indicate
|
||||||
|
// the setting is disabled.
|
||||||
|
Disabled = -1
|
||||||
|
|
||||||
|
disabled = "disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config defines the tunable parameters for Garbage Collection.
|
||||||
|
type Config struct {
|
||||||
|
// Delay duration after a revision create before considering it for GC
|
||||||
|
StaleRevisionCreateDelay time.Duration
|
||||||
|
// Timeout since a revision lastPinned before it should be GC'd
|
||||||
|
// This must be longer than the controller resync period
|
||||||
|
StaleRevisionTimeout time.Duration
|
||||||
|
// Minimum number of generations of revisions to keep before considering for GC.
|
||||||
|
StaleRevisionMinimumGenerations int64
|
||||||
|
// Minimum staleness duration before updating lastPinned
|
||||||
|
StaleRevisionLastpinnedDebounce time.Duration
|
||||||
|
|
||||||
|
// Duration from creation when a Revision should be considered active
|
||||||
|
// and exempt from GC. Note that GCMaxStaleRevision may override this if set.
|
||||||
|
// Set Disabled (-1) to disable/ignore duration and always consider active.
|
||||||
|
RetainSinceCreateTime time.Duration
|
||||||
|
// Duration from last active when a Revision should be considered active
|
||||||
|
// and exempt from GC.Note that GCMaxStaleRevision may override this if set.
|
||||||
|
// Set Disabled (-1) to disable/ignore duration and always consider active.
|
||||||
|
RetainSinceLastActiveTime time.Duration
|
||||||
|
// Minimum number of non-active revisions to keep before considering for GC.
|
||||||
|
MinNonActiveRevisions int64
|
||||||
|
// Maximum number of non-active revisions to keep before considering for GC.
|
||||||
|
// regardless of creation or staleness time-bounds.
|
||||||
|
// Set Disabled (-1) to disable/ignore max.
|
||||||
|
MaxNonActiveRevisions int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
// V1 GC Settings
|
||||||
|
StaleRevisionCreateDelay: 48 * time.Hour,
|
||||||
|
StaleRevisionTimeout: 15 * time.Hour,
|
||||||
|
StaleRevisionLastpinnedDebounce: 5 * time.Hour,
|
||||||
|
StaleRevisionMinimumGenerations: 20,
|
||||||
|
|
||||||
|
// V2 GC Settings
|
||||||
|
RetainSinceCreateTime: 48 * time.Hour,
|
||||||
|
RetainSinceLastActiveTime: 15 * time.Hour,
|
||||||
|
MinNonActiveRevisions: 20,
|
||||||
|
MaxNonActiveRevisions: 1000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigFromConfigMapFunc creates a Config from the supplied ConfigMap func.
|
||||||
|
func NewConfigFromConfigMapFunc(ctx context.Context) func(configMap *corev1.ConfigMap) (*Config, error) {
|
||||||
|
logger := logging.FromContext(ctx)
|
||||||
|
minRevisionTimeout := controller.GetResyncPeriod(ctx)
|
||||||
|
return func(configMap *corev1.ConfigMap) (*Config, error) {
|
||||||
|
c := defaultConfig()
|
||||||
|
|
||||||
|
var retainCreate, retainActive, max string
|
||||||
|
if err := cm.Parse(configMap.Data,
|
||||||
|
cm.AsDuration("stale-revision-create-delay", &c.StaleRevisionCreateDelay),
|
||||||
|
cm.AsDuration("stale-revision-timeout", &c.StaleRevisionTimeout),
|
||||||
|
cm.AsDuration("stale-revision-lastpinned-debounce", &c.StaleRevisionLastpinnedDebounce),
|
||||||
|
cm.AsInt64("stale-revision-minimum-generations", &c.StaleRevisionMinimumGenerations),
|
||||||
|
|
||||||
|
// v2 settings
|
||||||
|
cm.AsString("retain-since-create-time", &retainCreate),
|
||||||
|
cm.AsString("retain-since-last-active-time", &retainActive),
|
||||||
|
cm.AsInt64("min-non-active-revisions", &c.MinNonActiveRevisions),
|
||||||
|
cm.AsString("max-non-active-revisions", &max),
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.StaleRevisionMinimumGenerations < 0 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"stale-revision-minimum-generations must be non-negative, was: %d",
|
||||||
|
c.StaleRevisionMinimumGenerations)
|
||||||
|
}
|
||||||
|
if c.StaleRevisionTimeout-c.StaleRevisionLastpinnedDebounce < minRevisionTimeout {
|
||||||
|
logger.Warnf("Got revision timeout of %v, minimum supported value is %v",
|
||||||
|
c.StaleRevisionTimeout, minRevisionTimeout+c.StaleRevisionLastpinnedDebounce)
|
||||||
|
c.StaleRevisionTimeout = minRevisionTimeout + c.StaleRevisionLastpinnedDebounce
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate V2 settings
|
||||||
|
if err := parseDisabledOrDuration(retainCreate, &c.RetainSinceCreateTime); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse retain-since-create-time: %w", err)
|
||||||
|
}
|
||||||
|
if err := parseDisabledOrDuration(retainActive, &c.RetainSinceLastActiveTime); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse retain-since-last-active-time: %w", err)
|
||||||
|
}
|
||||||
|
if err := parseDisabledOrInt64(max, &c.MaxNonActiveRevisions); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse max-stale-revisions: %w", err)
|
||||||
|
}
|
||||||
|
if c.MinNonActiveRevisions < 0 {
|
||||||
|
return nil, fmt.Errorf("min-non-active-revisions must be non-negative, was: %d", c.MinNonActiveRevisions)
|
||||||
|
}
|
||||||
|
if c.MaxNonActiveRevisions >= 0 && c.MinNonActiveRevisions > c.MaxNonActiveRevisions {
|
||||||
|
return nil, fmt.Errorf("min-non-active-revisions(%d) must be <= max-stale-revisions(%d)", c.MinNonActiveRevisions, c.MaxNonActiveRevisions)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDisabledOrInt64(val string, toSet *int64) error {
|
||||||
|
switch {
|
||||||
|
case val == "":
|
||||||
|
// keep default value
|
||||||
|
case strings.EqualFold(val, disabled):
|
||||||
|
*toSet = Disabled
|
||||||
|
default:
|
||||||
|
parsed, err := strconv.ParseUint(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*toSet = int64(parsed)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDisabledOrDuration(val string, toSet *time.Duration) error {
|
||||||
|
switch {
|
||||||
|
case val == "":
|
||||||
|
// keep default value
|
||||||
|
case strings.EqualFold(val, disabled):
|
||||||
|
*toSet = time.Duration(Disabled)
|
||||||
|
default:
|
||||||
|
parsed, err := time.ParseDuration(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if parsed < 0 {
|
||||||
|
return fmt.Errorf("must be non-negative")
|
||||||
|
}
|
||||||
|
*toSet = parsed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
|
||||||
|
// Package gc manages garbage collection of old resources.
|
||||||
|
package gc
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Knative 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package gc
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Config) DeepCopyInto(out *Config) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||||
|
func (in *Config) DeepCopy() *Config {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Config)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
|
||||||
|
// Package config holds the typed objects that define the schemas for
|
||||||
|
// assorted ConfigMap objects on which the Route controller depends.
|
||||||
|
package config
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
netpkg "knative.dev/networking/pkg"
|
||||||
|
"knative.dev/pkg/configmap"
|
||||||
|
"knative.dev/pkg/network"
|
||||||
|
"knative.dev/serving/pkg/apis/serving"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DomainConfigName is the config map name for the domain configuration.
|
||||||
|
DomainConfigName = "config-domain"
|
||||||
|
// DefaultDomain holds the domain that Route's live under by default
|
||||||
|
// when no label selector-based options apply.
|
||||||
|
DefaultDomain = "example.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LabelSelector represents map of {key,value} pairs. A single {key,value} in the
|
||||||
|
// map is equivalent to a requirement key == value. The requirements are ANDed.
|
||||||
|
type LabelSelector struct {
|
||||||
|
Selector map[string]string `json:"selector,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LabelSelector) specificity() int {
|
||||||
|
return len(s.Selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches returns whether the given labels meet the requirement of the selector.
|
||||||
|
func (s *LabelSelector) Matches(labels map[string]string) bool {
|
||||||
|
for label, expectedValue := range s.Selector {
|
||||||
|
value, ok := labels[label]
|
||||||
|
if !ok || expectedValue != value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain maps domains to routes by matching the domain's
|
||||||
|
// label selectors to the route's labels.
|
||||||
|
type Domain struct {
|
||||||
|
// Domains map from domain to label selector. If a route has
|
||||||
|
// labels matching a particular selector, it will use the
|
||||||
|
// corresponding domain. If multiple selectors match, we choose
|
||||||
|
// the most specific selector.
|
||||||
|
Domains map[string]*LabelSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDomainFromConfigMap creates a Domain from the supplied ConfigMap
|
||||||
|
func NewDomainFromConfigMap(configMap *corev1.ConfigMap) (*Domain, error) {
|
||||||
|
c := Domain{Domains: map[string]*LabelSelector{}}
|
||||||
|
hasDefault := false
|
||||||
|
for k, v := range configMap.Data {
|
||||||
|
if k == configmap.ExampleKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
labelSelector := &LabelSelector{}
|
||||||
|
err := yaml.Unmarshal([]byte(v), labelSelector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Domains[k] = labelSelector
|
||||||
|
if len(labelSelector.Selector) == 0 {
|
||||||
|
hasDefault = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasDefault {
|
||||||
|
c.Domains[DefaultDomain] = &LabelSelector{}
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupDomainForLabels returns a domain given a set of labels.
|
||||||
|
// Since we reject configuration without a default domain, this should
|
||||||
|
// always return a value.
|
||||||
|
func (c *Domain) LookupDomainForLabels(labels map[string]string) string {
|
||||||
|
domain := ""
|
||||||
|
specificity := -1
|
||||||
|
// If we see VisibilityLabelKey sets with VisibilityClusterLocal, that
|
||||||
|
// will take precedence and the route will get a Cluster's Domain Name.
|
||||||
|
if labels[netpkg.VisibilityLabelKey] == serving.VisibilityClusterLocal ||
|
||||||
|
labels[serving.VisibilityLabelKeyObsolete] == serving.VisibilityClusterLocal {
|
||||||
|
return "svc." + network.GetClusterDomainName()
|
||||||
|
}
|
||||||
|
for k, selector := range c.Domains {
|
||||||
|
// Ignore if selector doesn't match, or decrease the specificity.
|
||||||
|
if !selector.Matches(labels) || selector.specificity() < specificity {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if selector.specificity() > specificity || strings.Compare(k, domain) < 0 {
|
||||||
|
domain = k
|
||||||
|
specificity = selector.specificity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
network "knative.dev/networking/pkg"
|
||||||
|
"knative.dev/pkg/configmap"
|
||||||
|
"knative.dev/pkg/logging"
|
||||||
|
cfgmap "knative.dev/serving/pkg/apis/config"
|
||||||
|
"knative.dev/serving/pkg/gc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cfgKey struct{}
|
||||||
|
|
||||||
|
// Config is the configuration for the route reconciler.
|
||||||
|
// +k8s:deepcopy-gen=false
|
||||||
|
type Config struct {
|
||||||
|
Domain *Domain
|
||||||
|
GC *gc.Config
|
||||||
|
Network *network.Config
|
||||||
|
Features *cfgmap.Features
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromContext obtains a Config injected into the passed context.
|
||||||
|
func FromContext(ctx context.Context) *Config {
|
||||||
|
return ctx.Value(cfgKey{}).(*Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromContextOrDefaults is like FromContext, but when no Config is attached it
|
||||||
|
// returns a Config populated with the defaults for each of the Config fields.
|
||||||
|
func FromContextOrDefaults(ctx context.Context) *Config {
|
||||||
|
cfg := FromContext(ctx)
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Features == nil {
|
||||||
|
cfg.Features, _ = cfgmap.NewFeaturesConfigFromMap(map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToContext stores the configuration Config in the passed context.
|
||||||
|
func ToContext(ctx context.Context, c *Config) context.Context {
|
||||||
|
return context.WithValue(ctx, cfgKey{}, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store is based on configmap.UntypedStore and is used to store and watch for
|
||||||
|
// updates to configuration related to routes (currently only config-domain).
|
||||||
|
//
|
||||||
|
// +k8s:deepcopy-gen=false
|
||||||
|
type Store struct {
|
||||||
|
*configmap.UntypedStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStore creates a configmap.UntypedStore based config store.
|
||||||
|
//
|
||||||
|
// logger must be non-nil implementation of configmap.Logger (commonly used
|
||||||
|
// loggers conform)
|
||||||
|
//
|
||||||
|
// onAfterStore is a variadic list of callbacks to run
|
||||||
|
// after the ConfigMap has been processed and stored.
|
||||||
|
//
|
||||||
|
// See also: configmap.NewUntypedStore().
|
||||||
|
func NewStore(ctx context.Context, onAfterStore ...func(name string, value interface{})) *Store {
|
||||||
|
logger := logging.FromContext(ctx)
|
||||||
|
|
||||||
|
store := &Store{
|
||||||
|
UntypedStore: configmap.NewUntypedStore(
|
||||||
|
"route",
|
||||||
|
logger,
|
||||||
|
configmap.Constructors{
|
||||||
|
DomainConfigName: NewDomainFromConfigMap,
|
||||||
|
gc.ConfigName: gc.NewConfigFromConfigMapFunc(ctx),
|
||||||
|
network.ConfigName: network.NewConfigFromConfigMap,
|
||||||
|
cfgmap.FeaturesConfigName: cfgmap.NewFeaturesConfigFromConfigMap,
|
||||||
|
},
|
||||||
|
onAfterStore...,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToContext stores the configuration Store in the passed context.
|
||||||
|
func (s *Store) ToContext(ctx context.Context) context.Context {
|
||||||
|
return ToContext(ctx, s.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load creates a Config for this store.
|
||||||
|
func (s *Store) Load() *Config {
|
||||||
|
config := &Config{
|
||||||
|
Domain: s.UntypedLoad(DomainConfigName).(*Domain).DeepCopy(),
|
||||||
|
GC: s.UntypedLoad(gc.ConfigName).(*gc.Config).DeepCopy(),
|
||||||
|
Network: s.UntypedLoad(network.ConfigName).(*network.Config).DeepCopy(),
|
||||||
|
Features: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if featureConfig := s.UntypedLoad(cfgmap.FeaturesConfigName); featureConfig != nil {
|
||||||
|
config.Features = featureConfig.(*cfgmap.Features).DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
75
vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go
vendored
Normal file
75
vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Knative 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Domain) DeepCopyInto(out *Domain) {
|
||||||
|
*out = *in
|
||||||
|
if in.Domains != nil {
|
||||||
|
in, out := &in.Domains, &out.Domains
|
||||||
|
*out = make(map[string]*LabelSelector, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
var outVal *LabelSelector
|
||||||
|
if val == nil {
|
||||||
|
(*out)[key] = nil
|
||||||
|
} else {
|
||||||
|
in, out := &val, &outVal
|
||||||
|
*out = new(LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
(*out)[key] = outVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Domain.
|
||||||
|
func (in *Domain) DeepCopy() *Domain {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Domain)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *LabelSelector) DeepCopyInto(out *LabelSelector) {
|
||||||
|
*out = *in
|
||||||
|
if in.Selector != nil {
|
||||||
|
in, out := &in.Selector, &out.Selector
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelSelector.
|
||||||
|
func (in *LabelSelector) DeepCopy() *LabelSelector {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(LabelSelector)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Knative 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 domains holds simple functions for generating domains.
|
||||||
|
package domains
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Knative 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 domains
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
network "knative.dev/networking/pkg"
|
||||||
|
netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
pkgnet "knative.dev/pkg/network"
|
||||||
|
"knative.dev/serving/pkg/apis/serving"
|
||||||
|
v1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
"knative.dev/serving/pkg/reconciler/route/config"
|
||||||
|
"knative.dev/serving/pkg/reconciler/route/resources/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPScheme is the string representation of http.
|
||||||
|
const HTTPScheme string = "http"
|
||||||
|
|
||||||
|
// GetAllDomainsAndTags returns all of the domains and tags(including subdomains) associated with a Route
|
||||||
|
func GetAllDomainsAndTags(ctx context.Context, r *v1.Route, names []string, visibility map[string]netv1alpha1.IngressVisibility) (map[string]string, error) {
|
||||||
|
domainTagMap := make(map[string]string)
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
meta := r.ObjectMeta.DeepCopy()
|
||||||
|
|
||||||
|
hostname, err := HostnameFromTemplate(ctx, meta.Name, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
labels.SetVisibility(meta, visibility[name] == netv1alpha1.IngressVisibilityClusterLocal)
|
||||||
|
|
||||||
|
subDomain, err := DomainNameFromTemplate(ctx, *meta, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
domainTagMap[subDomain] = name
|
||||||
|
}
|
||||||
|
return domainTagMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainNameFromTemplate generates domain name base on the template specified in the `config-network` ConfigMap.
|
||||||
|
// name is the "subdomain" which will be referred as the "name" in the template
|
||||||
|
func DomainNameFromTemplate(ctx context.Context, r metav1.ObjectMeta, name string) (string, error) {
|
||||||
|
domainConfig := config.FromContext(ctx).Domain
|
||||||
|
rLabels := r.Labels
|
||||||
|
domain := domainConfig.LookupDomainForLabels(rLabels)
|
||||||
|
annotations := r.Annotations
|
||||||
|
// These are the available properties they can choose from.
|
||||||
|
// We could add more over time - e.g. RevisionName if we thought that
|
||||||
|
// might be of interest to people.
|
||||||
|
data := network.DomainTemplateValues{
|
||||||
|
Name: name,
|
||||||
|
Namespace: r.Namespace,
|
||||||
|
Domain: domain,
|
||||||
|
Annotations: annotations,
|
||||||
|
Labels: rLabels,
|
||||||
|
}
|
||||||
|
|
||||||
|
networkConfig := config.FromContext(ctx).Network
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
var templ *template.Template
|
||||||
|
// If the route is "cluster local" then don't use the user-defined
|
||||||
|
// domain template, use the default one
|
||||||
|
if rLabels[network.VisibilityLabelKey] == serving.VisibilityClusterLocal ||
|
||||||
|
rLabels[serving.VisibilityLabelKeyObsolete] == serving.VisibilityClusterLocal {
|
||||||
|
templ = template.Must(template.New("domain-template").Parse(
|
||||||
|
network.DefaultDomainTemplate))
|
||||||
|
} else {
|
||||||
|
templ = networkConfig.GetDomainTemplate()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := templ.Execute(&buf, data); err != nil {
|
||||||
|
return "", fmt.Errorf("error executing the DomainTemplate: %w", err)
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostnameFromTemplate generates domain name base on the template specified in the `config-network` ConfigMap.
|
||||||
|
// name is the "subdomain" which will be referred as the "name" in the template
|
||||||
|
func HostnameFromTemplate(ctx context.Context, name, tag string) (string, error) {
|
||||||
|
if tag == "" {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
// These are the available properties they can choose from.
|
||||||
|
// We could add more over time - e.g. RevisionName if we thought that
|
||||||
|
// might be of interest to people.
|
||||||
|
data := network.TagTemplateValues{
|
||||||
|
Name: name,
|
||||||
|
Tag: tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
networkConfig := config.FromContext(ctx).Network
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
if err := networkConfig.GetTagTemplate().Execute(&buf, data); err != nil {
|
||||||
|
return "", fmt.Errorf("error executing the TagTemplate: %w", err)
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL generates the a string representation of a URL.
|
||||||
|
func URL(scheme, fqdn string) *apis.URL {
|
||||||
|
return &apis.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: fqdn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClusterLocal checks if a domain is only visible with cluster.
|
||||||
|
func IsClusterLocal(domain string) bool {
|
||||||
|
return strings.HasSuffix(domain, pkgnet.GetClusterDomainName())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Knative 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 labels holds simple functions for working with ObjectMeta labels.
|
||||||
|
package labels
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Knative 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 labels
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
network "knative.dev/networking/pkg"
|
||||||
|
"knative.dev/serving/pkg/apis/serving"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsObjectLocalVisibility returns whether an ObjectMeta is of cluster-local visibility
|
||||||
|
func IsObjectLocalVisibility(meta *v1.ObjectMeta) bool {
|
||||||
|
return meta.Labels[network.VisibilityLabelKey] != "" || meta.Labels[serving.VisibilityLabelKeyObsolete] != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVisibility sets the visibility on an ObjectMeta
|
||||||
|
func SetVisibility(meta *v1.ObjectMeta, isClusterLocal bool) {
|
||||||
|
if isClusterLocal {
|
||||||
|
SetLabel(meta, network.VisibilityLabelKey, serving.VisibilityClusterLocal)
|
||||||
|
} else {
|
||||||
|
DeleteLabel(meta, network.VisibilityLabelKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLabel sets/update the label of the an ObjectMeta
|
||||||
|
func SetLabel(meta *v1.ObjectMeta, key, value string) {
|
||||||
|
if meta.Labels == nil {
|
||||||
|
meta.Labels = make(map[string]string, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.Labels[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLabel removes a label from the ObjectMeta
|
||||||
|
func DeleteLabel(meta *v1.ObjectMeta, key string) {
|
||||||
|
delete(meta.Labels, key)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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 names holds simple functions for synthesizing resource names.
|
||||||
|
package names
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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 names
|
||||||
|
|
||||||
|
import (
|
||||||
|
"knative.dev/pkg/kmeta"
|
||||||
|
"knative.dev/pkg/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
// K8sService returns the name of the K8sService for a given route.
|
||||||
|
func K8sService(route kmeta.Accessor) string {
|
||||||
|
return route.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// K8sServiceFullname returns the full name of the K8sService for a given route.
|
||||||
|
func K8sServiceFullname(route kmeta.Accessor) string {
|
||||||
|
return network.GetServiceHostname(K8sService(route), route.GetNamespace())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ingress returns the name for the Ingress
|
||||||
|
// child resource for the given Route.
|
||||||
|
func Ingress(route kmeta.Accessor) string {
|
||||||
|
return kmeta.ChildName(route.GetName(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate returns the name for the Certificate
|
||||||
|
// child resource for the given Route.
|
||||||
|
func Certificate(route kmeta.Accessor) string {
|
||||||
|
return "route-" + string(route.GetUID())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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 names holds simple functions for synthesizing resource names.
|
||||||
|
package names
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Knative 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 names
|
||||||
|
|
||||||
|
import "knative.dev/pkg/kmeta"
|
||||||
|
|
||||||
|
// Configuration returns a configuration name based on a given service name.
|
||||||
|
func Configuration(service kmeta.Accessor) string {
|
||||||
|
return service.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route returns a route name based on a given service name.
|
||||||
|
func Route(service kmeta.Accessor) string {
|
||||||
|
return service.GetName()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Knative 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"knative.dev/pkg/kmeta"
|
||||||
|
"knative.dev/pkg/ptr"
|
||||||
|
v1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigOption enables further configuration of a Configuration.
|
||||||
|
type ConfigOption func(*v1.Configuration)
|
||||||
|
|
||||||
|
// WithConfigReadinessProbe sets the provided probe to be the readiness
|
||||||
|
// probe on the configuration.
|
||||||
|
func WithConfigReadinessProbe(p *corev1.Probe) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Spec.Template.Spec.Containers[0].ReadinessProbe = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigImage sets the container image to be the provided string.
|
||||||
|
func WithConfigImage(img string) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Spec.Template.Spec.Containers[0].Image = img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigDeletionTimestamp sets the DeletionTimestamp on the Config.
|
||||||
|
func WithConfigDeletionTimestamp(r *v1.Configuration) {
|
||||||
|
t := metav1.NewTime(time.Unix(1e9, 0))
|
||||||
|
r.ObjectMeta.SetDeletionTimestamp(&t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigContainerConcurrency sets the given Configuration's concurrency.
|
||||||
|
func WithConfigContainerConcurrency(cc int64) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Spec.GetTemplate().Spec.ContainerConcurrency = &cc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigGeneration sets the generation of the Configuration.
|
||||||
|
func WithConfigGeneration(gen int64) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Generation = gen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigObservedGen sets the observed generation of the Configuration.
|
||||||
|
func WithConfigObservedGen(cfg *v1.Configuration) {
|
||||||
|
cfg.Status.ObservedGeneration = cfg.Generation
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLatestCreated initializes the .status.latestCreatedRevisionName to be the name
|
||||||
|
// of the latest revision that the Configuration would have created.
|
||||||
|
func WithLatestCreated(name string) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Status.SetLatestCreatedRevisionName(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLatestReady initializes the .status.latestReadyRevisionName to be the name
|
||||||
|
// of the latest revision that the Configuration would have created.
|
||||||
|
func WithLatestReady(name string) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Status.SetLatestReadyRevisionName(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkRevisionCreationFailed calls .Status.MarkRevisionCreationFailed.
|
||||||
|
func MarkRevisionCreationFailed(msg string) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Status.MarkRevisionCreationFailed(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkLatestCreatedFailed calls .Status.MarkLatestCreatedFailed.
|
||||||
|
func MarkLatestCreatedFailed(msg string) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Status.MarkLatestCreatedFailed(cfg.Status.LatestCreatedRevisionName, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigLabel attaches a particular label to the configuration.
|
||||||
|
func WithConfigLabel(key, value string) ConfigOption {
|
||||||
|
return func(config *v1.Configuration) {
|
||||||
|
config.Labels = kmeta.UnionMaps(config.Labels, map[string]string{key: value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigAnn attaches a particular label to the configuration.
|
||||||
|
func WithConfigAnn(key, value string) ConfigOption {
|
||||||
|
return func(config *v1.Configuration) {
|
||||||
|
config.Annotations = kmeta.UnionMaps(config.Annotations, map[string]string{key: value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigOwnersRemoved clears the owner references of this Configuration.
|
||||||
|
func WithConfigOwnersRemoved(cfg *v1.Configuration) {
|
||||||
|
cfg.OwnerReferences = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigEnv configures the Service to use the provided environment variables.
|
||||||
|
func WithConfigEnv(evs ...corev1.EnvVar) ConfigOption {
|
||||||
|
return func(c *v1.Configuration) {
|
||||||
|
c.Spec.Template.Spec.Containers[0].Env = evs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigRevisionTimeoutSeconds sets revision timeout.
|
||||||
|
func WithConfigRevisionTimeoutSeconds(revisionTimeoutSeconds int64) ConfigOption {
|
||||||
|
return func(cfg *v1.Configuration) {
|
||||||
|
cfg.Spec.Template.Spec.TimeoutSeconds = ptr.Int64(revisionTimeoutSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Knative 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/clock"
|
||||||
|
"knative.dev/pkg/kmeta"
|
||||||
|
"knative.dev/serving/pkg/apis/serving"
|
||||||
|
v1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RevisionOption enables further configuration of a Revision.
|
||||||
|
type RevisionOption func(*v1.Revision)
|
||||||
|
|
||||||
|
// WithRevisionDeletionTimestamp will set the DeletionTimestamp on the Revision.
|
||||||
|
func WithRevisionDeletionTimestamp(r *v1.Revision) {
|
||||||
|
t := metav1.NewTime(time.Unix(1e9, 0))
|
||||||
|
r.ObjectMeta.SetDeletionTimestamp(&t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInitRevConditions calls .Status.InitializeConditions() on a Revision.
|
||||||
|
func WithInitRevConditions(r *v1.Revision) {
|
||||||
|
r.Status.InitializeConditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevName sets the name of the revision
|
||||||
|
func WithRevName(name string) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceName propagates the given service name to the revision status.
|
||||||
|
func WithServiceName(sn string) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Status.ServiceName = sn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkResourceNotOwned calls the function of the same name on the Revision's status.
|
||||||
|
func MarkResourceNotOwned(kind, name string) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Status.MarkResourcesAvailableFalse(
|
||||||
|
v1.ReasonNotOwned,
|
||||||
|
v1.ResourceNotOwnedMessage(kind, name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevContainerConcurrency sets the given Revision's concurrency.
|
||||||
|
func WithRevContainerConcurrency(cc int64) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Spec.ContainerConcurrency = &cc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLogURL sets the .Status.LogURL to the expected value.
|
||||||
|
func WithLogURL(r *v1.Revision) {
|
||||||
|
r.Status.LogURL = "http://logger.io/test-uid"
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCreationTimestamp sets the Revision's timestamp to the provided time.
|
||||||
|
// TODO(mattmoor): Ideally this could be a more generic Option and use meta.Accessor,
|
||||||
|
// but unfortunately Go's type system cannot support that.
|
||||||
|
func WithCreationTimestamp(t time.Time) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.ObjectMeta.CreationTimestamp = metav1.Time{Time: t}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLastPinned updates the "last pinned" annotation to the provided timestamp.
|
||||||
|
func WithLastPinned(t time.Time) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.SetLastPinned(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionPreserveAnnotation updates the annotation with preserve key.
|
||||||
|
func WithRevisionPreserveAnnotation() RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Annotations = kmeta.UnionMaps(rev.Annotations,
|
||||||
|
map[string]string{
|
||||||
|
serving.RevisionPreservedAnnotationKey: "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRoutingStateModified updates the annotation to the provided timestamp.
|
||||||
|
func WithRoutingStateModified(t time.Time) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Annotations = kmeta.UnionMaps(rev.Annotations,
|
||||||
|
map[string]string{
|
||||||
|
serving.RoutingStateModifiedAnnotationKey: t.UTC().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRoutingState updates the annotation to the provided timestamp.
|
||||||
|
func WithRoutingState(s v1.RoutingState) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.SetRoutingState(s, clock.RealClock{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevStatus is a generic escape hatch for creating hard-to-craft
|
||||||
|
// status orientations.
|
||||||
|
func WithRevStatus(st v1.RevisionStatus) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Status = st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithImagePullSecrets updates the revision spec ImagePullSecrets to
|
||||||
|
// the provided secrets
|
||||||
|
func WithImagePullSecrets(secretName string) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{
|
||||||
|
Name: secretName,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkActive calls .Status.MarkActive on the Revision.
|
||||||
|
func MarkActive(r *v1.Revision) {
|
||||||
|
r.Status.MarkActiveTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithK8sServiceName applies sn to the revision status field.
|
||||||
|
func WithK8sServiceName(sn string) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.ServiceName = sn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkInactive calls .Status.MarkInactive on the Revision.
|
||||||
|
func MarkInactive(reason, message string) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.MarkActiveFalse(reason, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkActivating calls .Status.MarkActivating on the Revision.
|
||||||
|
func MarkActivating(reason, message string) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.MarkActiveUnknown(reason, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkDeploying calls .Status.MarkDeploying on the Revision.
|
||||||
|
func MarkDeploying(reason string) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.MarkResourcesAvailableUnknown(reason, "")
|
||||||
|
r.Status.MarkContainerHealthyUnknown(reason, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkProgressDeadlineExceeded calls the method of the same name on the Revision
|
||||||
|
// with the message we expect the Revision Reconciler to pass.
|
||||||
|
func MarkProgressDeadlineExceeded(message string) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.MarkResourcesAvailableFalse(
|
||||||
|
v1.ReasonProgressDeadlineExceeded,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkContainerMissing calls .Status.MarkContainerMissing on the Revision.
|
||||||
|
func MarkContainerMissing(rev *v1.Revision) {
|
||||||
|
rev.Status.MarkContainerHealthyFalse(v1.ReasonContainerMissing, "It's the end of the world as we know it")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkContainerExiting calls .Status.MarkContainerExiting on the Revision.
|
||||||
|
func MarkContainerExiting(exitCode int32, message string) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.MarkContainerHealthyFalse(v1.ExitCodeReason(exitCode), message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkResourcesUnavailable calls .Status.MarkResourcesUnavailable on the Revision.
|
||||||
|
func MarkResourcesUnavailable(reason, message string) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.MarkResourcesAvailableFalse(reason, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkRevisionReady calls the necessary helpers to make the Revision Ready=True.
|
||||||
|
func MarkRevisionReady(r *v1.Revision) {
|
||||||
|
WithInitRevConditions(r)
|
||||||
|
MarkActive(r)
|
||||||
|
r.Status.MarkResourcesAvailableTrue()
|
||||||
|
r.Status.MarkContainerHealthyTrue()
|
||||||
|
r.Status.ObservedGeneration = r.Generation
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionLabel attaches a particular label to the revision.
|
||||||
|
func WithRevisionLabel(key, value string) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Labels = kmeta.UnionMaps(rev.Labels, map[string]string{key: value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionAnn attaches a particular label to the revision.
|
||||||
|
func WithRevisionAnn(key, value string) RevisionOption {
|
||||||
|
return func(rev *v1.Revision) {
|
||||||
|
rev.Annotations = kmeta.UnionMaps(rev.Annotations, map[string]string{key: value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContainerStatuses sets the .Status.ContainerStatuses to the Revision.
|
||||||
|
func WithContainerStatuses(containerStatus []v1.ContainerStatus) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.ContainerStatuses = containerStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionObservedGeneration sets the observed generation on the
|
||||||
|
// revision status.
|
||||||
|
func WithRevisionObservedGeneration(gen int64) RevisionOption {
|
||||||
|
return func(r *v1.Revision) {
|
||||||
|
r.Status.ObservedGeneration = gen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revision creates a revision object with given ns/name and options.
|
||||||
|
func Revision(namespace, name string, ro ...RevisionOption) *v1.Revision {
|
||||||
|
r := &v1.Revision{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
SelfLink: "/apis/serving/v1/namespaces/test/revisions/" + name,
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
UID: "test-uid",
|
||||||
|
Generation: 1,
|
||||||
|
},
|
||||||
|
Spec: v1.RevisionSpec{
|
||||||
|
PodSpec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Name: name,
|
||||||
|
Image: "busybox",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.SetDefaults(context.Background())
|
||||||
|
|
||||||
|
for _, opt := range ro {
|
||||||
|
opt(r)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,294 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Knative 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"knative.dev/networking/pkg/apis/networking"
|
||||||
|
netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||||
|
"knative.dev/pkg/network"
|
||||||
|
"knative.dev/pkg/ptr"
|
||||||
|
v1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
routenames "knative.dev/serving/pkg/reconciler/route/resources/names"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RouteOption enables further configuration of a Route.
|
||||||
|
type RouteOption func(*v1.Route)
|
||||||
|
|
||||||
|
// WithSpecTraffic sets the Route's traffic block to the specified traffic targets.
|
||||||
|
func WithSpecTraffic(traffic ...v1.TrafficTarget) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Spec.Traffic = traffic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteUID sets the Route's UID
|
||||||
|
func WithRouteUID(uid types.UID) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.UID = uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteGeneration sets the route's generation
|
||||||
|
func WithRouteGeneration(generation int64) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Generation = generation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteObservedGeneration sets the route's observed generation to it's generation
|
||||||
|
func WithRouteObservedGeneration(r *v1.Route) {
|
||||||
|
r.Status.ObservedGeneration = r.Generation
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteFinalizer adds the Route finalizer to the Route.
|
||||||
|
func WithRouteFinalizer(r *v1.Route) {
|
||||||
|
r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, "routes.serving.knative.dev")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteDeletionTimestamp adds the Route finalizer to the Route.
|
||||||
|
func WithRouteDeletionTimestamp(t *metav1.Time) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.ObjectMeta.DeletionTimestamp = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigTarget sets the Route's traffic block to point at a particular Configuration.
|
||||||
|
func WithConfigTarget(config string) RouteOption {
|
||||||
|
return WithSpecTraffic(v1.TrafficTarget{
|
||||||
|
ConfigurationName: config,
|
||||||
|
Percent: ptr.Int64(100),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevTarget sets the Route's traffic block to point at a particular Revision.
|
||||||
|
func WithRevTarget(revision string) RouteOption {
|
||||||
|
return WithSpecTraffic(v1.TrafficTarget{
|
||||||
|
RevisionName: revision,
|
||||||
|
Percent: ptr.Int64(100),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStatusTraffic sets the Route's status traffic block to the specified traffic targets.
|
||||||
|
func WithStatusTraffic(traffic ...v1.TrafficTarget) RouteOption {
|
||||||
|
ctx := apis.WithinStatus(context.Background())
|
||||||
|
|
||||||
|
for _, t := range traffic {
|
||||||
|
if err := t.Validate(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Status.Traffic = traffic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteOwnersRemoved clears the owner references of this Route.
|
||||||
|
func WithRouteOwnersRemoved(r *v1.Route) {
|
||||||
|
r.OwnerReferences = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkServiceNotOwned calls the function of the same name on the Service's status.
|
||||||
|
func MarkServiceNotOwned(r *v1.Route) {
|
||||||
|
r.Status.MarkServiceNotOwned(routenames.K8sService(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithURL sets the .Status.Domain field to the prototypical domain.
|
||||||
|
func WithURL(r *v1.Route) {
|
||||||
|
r.Status.URL = &apis.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: fmt.Sprintf("%s.%s.example.com", r.Name, r.Namespace),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHTTPSDomain sets the .Status.URL field to a https-domain based on the name and namespace.
|
||||||
|
func WithHTTPSDomain(r *v1.Route) {
|
||||||
|
r.Status.URL = &apis.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: fmt.Sprintf("%s.%s.example.com", r.Name, r.Namespace),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAddress sets the .Status.Address field to the prototypical internal hostname.
|
||||||
|
func WithAddress(r *v1.Route) {
|
||||||
|
r.Status.Address = &duckv1.Addressable{
|
||||||
|
URL: &apis.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: network.GetServiceHostname(r.Name, r.Namespace),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAnotherDomain sets the .Status.Domain field to an atypical domain.
|
||||||
|
func WithAnotherDomain(r *v1.Route) {
|
||||||
|
r.Status.URL = &apis.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: fmt.Sprintf("%s.%s.another-example.com", r.Name, r.Namespace),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLocalDomain sets the .Status.Domain field to use ClusterDomain suffix.
|
||||||
|
func WithLocalDomain(r *v1.Route) {
|
||||||
|
r.Status.URL = &apis.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: network.GetServiceHostname(r.Name, r.Namespace),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInitRouteConditions initializes the Service's conditions.
|
||||||
|
func WithInitRouteConditions(rt *v1.Route) {
|
||||||
|
rt.Status.InitializeConditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteConditionsAutoTLSDisabled calls MarkTLSNotEnabled with AutoTLSNotEnabledMessage
|
||||||
|
// after initialized the Service's conditions.
|
||||||
|
func WithRouteConditionsAutoTLSDisabled(rt *v1.Route) {
|
||||||
|
rt.Status.InitializeConditions()
|
||||||
|
rt.Status.MarkTLSNotEnabled(v1.AutoTLSNotEnabledMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteConditionsTLSNotEnabledForClusterLocalMessage calls
|
||||||
|
// MarkTLSNotEnabled with TLSNotEnabledForClusterLocalMessage after initialized
|
||||||
|
// the Service's conditions.
|
||||||
|
func WithRouteConditionsTLSNotEnabledForClusterLocalMessage(rt *v1.Route) {
|
||||||
|
rt.Status.InitializeConditions()
|
||||||
|
rt.Status.MarkTLSNotEnabled(v1.TLSNotEnabledForClusterLocalMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteConditionsHTTPDowngrade calls MarkHTTPDowngrade after initialized the Service's conditions.
|
||||||
|
func WithRouteConditionsHTTPDowngrade(rt *v1.Route) {
|
||||||
|
rt.Status.InitializeConditions()
|
||||||
|
rt.Status.MarkHTTPDowngrade(routenames.Certificate(rt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkTrafficAssigned calls the method of the same name on .Status
|
||||||
|
func MarkTrafficAssigned(r *v1.Route) {
|
||||||
|
r.Status.MarkTrafficAssigned()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkCertificateNotReady calls the method of the same name on .Status
|
||||||
|
func MarkCertificateNotReady(r *v1.Route) {
|
||||||
|
r.Status.MarkCertificateNotReady(routenames.Certificate(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkCertificateNotOwned calls the method of the same name on .Status
|
||||||
|
func MarkCertificateNotOwned(r *v1.Route) {
|
||||||
|
r.Status.MarkCertificateNotOwned(routenames.Certificate(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkCertificateReady calls the method of the same name on .Status
|
||||||
|
func MarkCertificateReady(r *v1.Route) {
|
||||||
|
r.Status.MarkCertificateReady(routenames.Certificate(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReadyCertificateName marks the certificate specified by name as ready.
|
||||||
|
func WithReadyCertificateName(name string) func(*v1.Route) {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Status.MarkCertificateReady(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkIngressReady propagates a Ready=True Ingress status to the Route.
|
||||||
|
func MarkIngressReady(r *v1.Route) {
|
||||||
|
r.Status.PropagateIngressStatus(netv1alpha1.IngressStatus{
|
||||||
|
Status: duckv1.Status{
|
||||||
|
Conditions: duckv1.Conditions{{
|
||||||
|
Type: "Ready",
|
||||||
|
Status: "True",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkIngressNotConfigured calls the method of the same name on .Status
|
||||||
|
func MarkIngressNotConfigured(r *v1.Route) {
|
||||||
|
r.Status.MarkIngressNotConfigured()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPropagatedStatus propagates the given IngressStatus into the routes status.
|
||||||
|
func WithPropagatedStatus(status netv1alpha1.IngressStatus) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Status.PropagateIngressStatus(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkMissingTrafficTarget calls the method of the same name on .Status
|
||||||
|
func MarkMissingTrafficTarget(kind, revision string) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Status.MarkMissingTrafficTarget(kind, revision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkConfigurationNotReady calls the method of the same name on .Status
|
||||||
|
func MarkConfigurationNotReady(name string) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Status.MarkConfigurationNotReady(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkConfigurationFailed calls the method of the same name on .Status
|
||||||
|
func MarkConfigurationFailed(name string) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Status.MarkConfigurationFailed(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteLabel sets the specified label on the Route.
|
||||||
|
func WithRouteLabel(labels map[string]string) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Labels = labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithIngressClass sets the ingress class annotation on the Route.
|
||||||
|
func WithIngressClass(ingressClass string) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
if r.Annotations == nil {
|
||||||
|
r.Annotations = make(map[string]string, 1)
|
||||||
|
}
|
||||||
|
r.Annotations[networking.IngressClassAnnotationKey] = ingressClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteAnnotation sets the specified annotation on the Route.
|
||||||
|
func WithRouteAnnotation(annotations map[string]string) RouteOption {
|
||||||
|
return func(r *v1.Route) {
|
||||||
|
r.Annotations = annotations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route creates a route with RouteOptions
|
||||||
|
func Route(namespace, name string, ro ...RouteOption) *v1.Route {
|
||||||
|
r := &v1.Route{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, opt := range ro {
|
||||||
|
opt(r)
|
||||||
|
}
|
||||||
|
r.SetDefaults(context.Background())
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,481 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Knative 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||||
|
"knative.dev/pkg/kmeta"
|
||||||
|
"knative.dev/pkg/network"
|
||||||
|
"knative.dev/pkg/ptr"
|
||||||
|
v1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
"knative.dev/serving/pkg/reconciler/route/domains"
|
||||||
|
servicenames "knative.dev/serving/pkg/reconciler/service/resources/names"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceOption enables further configuration of a Service.
|
||||||
|
type ServiceOption func(*v1.Service)
|
||||||
|
|
||||||
|
// Service creates a service with ServiceOptions
|
||||||
|
func Service(name, namespace string, so ...ServiceOption) *v1.Service {
|
||||||
|
s := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, opt := range so {
|
||||||
|
opt(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultService creates a service with ServiceOptions and with default values set
|
||||||
|
func DefaultService(name, namespace string, so ...ServiceOption) *v1.Service {
|
||||||
|
return Service(name, namespace, append(so, WithServiceDefaults)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceWithoutNamespace creates a service with ServiceOptions but without a specific namespace
|
||||||
|
func ServiceWithoutNamespace(name string, so ...ServiceOption) *v1.Service {
|
||||||
|
s := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, opt := range so {
|
||||||
|
opt(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInitSvcConditions initializes the Service's conditions.
|
||||||
|
func WithInitSvcConditions(s *v1.Service) {
|
||||||
|
s.Status.InitializeConditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceObservedGenFailure marks the top level condition as unknown when the reconciler
|
||||||
|
// does not set any condition during reconciliation of a new generation.
|
||||||
|
func WithServiceObservedGenFailure(s *v1.Service) {
|
||||||
|
condSet := s.GetConditionSet()
|
||||||
|
condSet.Manage(&s.Status).MarkUnknown(condSet.GetTopLevelConditionType(),
|
||||||
|
"NewObservedGenFailure", "unsuccessfully observed a new generation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigSpec configures the Service to use the given config spec
|
||||||
|
func WithConfigSpec(config *v1.ConfigurationSpec) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Spec.ConfigurationSpec = *config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteSpec configures the Service to use the given route spec
|
||||||
|
func WithRouteSpec(route v1.RouteSpec) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Spec.RouteSpec = route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNamedPort sets the name on the Service's port to the provided name
|
||||||
|
func WithNamedPort(name string) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
c := &svc.Spec.Template.Spec.Containers[0]
|
||||||
|
if len(c.Ports) == 1 {
|
||||||
|
c.Ports[0].Name = name
|
||||||
|
} else {
|
||||||
|
c.Ports = []corev1.ContainerPort{{
|
||||||
|
Name: name,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNumberedPort sets the Service's port number to what's provided.
|
||||||
|
func WithNumberedPort(number int32) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
c := &svc.Spec.Template.Spec.Containers[0]
|
||||||
|
if len(c.Ports) == 1 {
|
||||||
|
c.Ports[0].ContainerPort = number
|
||||||
|
} else {
|
||||||
|
c.Ports = []corev1.ContainerPort{{
|
||||||
|
ContainerPort: number,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceDefaults will set the default values on the service.
|
||||||
|
func WithServiceDefaults(svc *v1.Service) {
|
||||||
|
svc.SetDefaults(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithResourceRequirements attaches resource requirements to the service
|
||||||
|
func WithResourceRequirements(resourceRequirements corev1.ResourceRequirements) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Spec.Template.Spec.Containers[0].Resources = resourceRequirements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceAnnotation adds the given annotation to the service.
|
||||||
|
func WithServiceAnnotation(k, v string) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Annotations = kmeta.UnionMaps(svc.Annotations, map[string]string{
|
||||||
|
k: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceAnnotationRemoved adds the given annotation to the service.
|
||||||
|
func WithServiceAnnotationRemoved(k string) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Annotations = kmeta.FilterMap(svc.Annotations, func(s string) bool {
|
||||||
|
return k == s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceImage sets the container image to be the provided string.
|
||||||
|
func WithServiceImage(img string) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Spec.Template.Spec.Containers[0].Image = img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceTemplateMeta sets the container image to be the provided string.
|
||||||
|
func WithServiceTemplateMeta(m metav1.ObjectMeta) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Spec.Template.ObjectMeta = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRevisionTimeoutSeconds sets revision timeout
|
||||||
|
func WithRevisionTimeoutSeconds(revisionTimeoutSeconds int64) ServiceOption {
|
||||||
|
return func(service *v1.Service) {
|
||||||
|
service.Spec.Template.Spec.TimeoutSeconds = ptr.Int64(revisionTimeoutSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceAccountName sets revision service account name
|
||||||
|
func WithServiceAccountName(serviceAccountName string) ServiceOption {
|
||||||
|
return func(service *v1.Service) {
|
||||||
|
service.Spec.Template.Spec.ServiceAccountName = serviceAccountName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContainerConcurrency sets the given Service's concurrency.
|
||||||
|
func WithContainerConcurrency(cc int64) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Spec.Template.Spec.ContainerConcurrency = &cc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithVolume adds a volume to the service
|
||||||
|
func WithVolume(name, mountPath string, volumeSource corev1.VolumeSource) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
rt := &svc.Spec.ConfigurationSpec.Template.Spec
|
||||||
|
|
||||||
|
rt.Containers[0].VolumeMounts = append(rt.Containers[0].VolumeMounts,
|
||||||
|
corev1.VolumeMount{
|
||||||
|
Name: name,
|
||||||
|
MountPath: mountPath,
|
||||||
|
})
|
||||||
|
|
||||||
|
rt.Volumes = append(rt.Volumes, corev1.Volume{
|
||||||
|
Name: name,
|
||||||
|
VolumeSource: volumeSource,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnv configures the Service to use the provided environment variables.
|
||||||
|
func WithEnv(evs ...corev1.EnvVar) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Spec.Template.Spec.Containers[0].Env = evs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnvFrom configures the Service to use the provided environment variables.
|
||||||
|
func WithEnvFrom(evs ...corev1.EnvFromSource) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Spec.Template.Spec.Containers[0].EnvFrom = evs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSecurityContext configures the Service to use the provided security context.
|
||||||
|
func WithSecurityContext(sc *corev1.SecurityContext) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Spec.Template.Spec.Containers[0].SecurityContext = sc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithWorkingDir configures the Service to use the provided working directory.
|
||||||
|
func WithWorkingDir(wd string) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Spec.Template.Spec.Containers[0].WorkingDir = wd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceAnnotations adds the supplied annotations to the Service
|
||||||
|
func WithServiceAnnotations(annotations map[string]string) ServiceOption {
|
||||||
|
return func(service *v1.Service) {
|
||||||
|
service.Annotations = kmeta.UnionMaps(service.Annotations, annotations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigAnnotations assigns config annotations to a service
|
||||||
|
func WithConfigAnnotations(annotations map[string]string) ServiceOption {
|
||||||
|
return func(service *v1.Service) {
|
||||||
|
service.Spec.Template.Annotations = kmeta.UnionMaps(
|
||||||
|
service.Spec.Template.Annotations, annotations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBYORevisionName sets the given name to the config spec
|
||||||
|
func WithBYORevisionName(name string) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Spec.Template.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceDeletionTimestamp will set the DeletionTimestamp on the Service.
|
||||||
|
func WithServiceDeletionTimestamp(r *v1.Service) {
|
||||||
|
t := metav1.NewTime(time.Unix(1e9, 0))
|
||||||
|
r.ObjectMeta.SetDeletionTimestamp(&t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNamedRevision configures the Service to use BYO Revision in the
|
||||||
|
// template spec and reference that same revision name in the route spec.
|
||||||
|
func WithNamedRevision(s *v1.Service) {
|
||||||
|
s.Spec = v1.ServiceSpec{
|
||||||
|
ConfigurationSpec: v1.ConfigurationSpec{
|
||||||
|
Template: v1.RevisionTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: s.Name + "-byo",
|
||||||
|
},
|
||||||
|
Spec: v1.RevisionSpec{
|
||||||
|
PodSpec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Image: "busybox",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
TimeoutSeconds: ptr.Int64(60),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RouteSpec: v1.RouteSpec{
|
||||||
|
Traffic: []v1.TrafficTarget{{
|
||||||
|
RevisionName: s.Name + "-byo",
|
||||||
|
Percent: ptr.Int64(100),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOutOfDateConfig reflects the Configuration's readiness in the Service
|
||||||
|
// resource.
|
||||||
|
func WithOutOfDateConfig(s *v1.Service) {
|
||||||
|
s.Status.MarkConfigurationNotReconciled()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceGeneration sets the service's generation
|
||||||
|
func WithServiceGeneration(generation int64) ServiceOption {
|
||||||
|
return func(svc *v1.Service) {
|
||||||
|
svc.Status.ObservedGeneration = generation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceLabel attaches a particular label to the service.
|
||||||
|
func WithServiceLabel(key, value string) ServiceOption {
|
||||||
|
return func(service *v1.Service) {
|
||||||
|
if service.Labels == nil {
|
||||||
|
service.Labels = make(map[string]string, 1)
|
||||||
|
}
|
||||||
|
service.Labels[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceObservedGeneration sets the service's observed generation to it's generation
|
||||||
|
func WithServiceObservedGeneration(svc *v1.Service) {
|
||||||
|
svc.Status.ObservedGeneration = svc.Generation
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkRevisionNameTaken calls the function of the same name on the Service's status
|
||||||
|
func MarkRevisionNameTaken(service *v1.Service) {
|
||||||
|
service.Status.MarkRevisionNameTaken(service.Spec.GetTemplate().GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRunLatestRollout configures the Service to use a "runLatest" rollout.
|
||||||
|
func WithRunLatestRollout(s *v1.Service) {
|
||||||
|
s.Spec = v1.ServiceSpec{
|
||||||
|
ConfigurationSpec: *configSpec.DeepCopy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReadyRoute reflects the Route's readiness in the Service resource.
|
||||||
|
func WithReadyRoute(s *v1.Service) {
|
||||||
|
s.Status.PropagateRouteStatus(&v1.RouteStatus{
|
||||||
|
Status: duckv1.Status{
|
||||||
|
Conditions: duckv1.Conditions{{
|
||||||
|
Type: "Ready",
|
||||||
|
Status: "True",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReadyConfig reflects the Configuration's readiness in the Service
|
||||||
|
// resource. This must coincide with the setting of Latest{Created,Ready}
|
||||||
|
// to the provided revision name.
|
||||||
|
func WithReadyConfig(name string) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Status.PropagateConfigurationStatus(&v1.ConfigurationStatus{
|
||||||
|
Status: duckv1.Status{
|
||||||
|
Conditions: duckv1.Conditions{{
|
||||||
|
Type: "Ready",
|
||||||
|
Status: "True",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
ConfigurationStatusFields: v1.ConfigurationStatusFields{
|
||||||
|
LatestCreatedRevisionName: name,
|
||||||
|
LatestReadyRevisionName: name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSvcStatusDomain propagates the domain name to the status of the Service.
|
||||||
|
func WithSvcStatusDomain(s *v1.Service) {
|
||||||
|
n, ns := s.GetName(), s.GetNamespace()
|
||||||
|
s.Status.URL = &apis.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: fmt.Sprintf("%s.%s.example.com", n, ns),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSvcStatusAddress updates the service's status with the address.
|
||||||
|
func WithSvcStatusAddress(s *v1.Service) {
|
||||||
|
s.Status.Address = &duckv1.Addressable{
|
||||||
|
URL: &apis.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: network.GetServiceHostname(s.Name, s.Namespace),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSvcStatusTraffic sets the Service's status traffic block to the specified traffic targets.
|
||||||
|
func WithSvcStatusTraffic(targets ...v1.TrafficTarget) ServiceOption {
|
||||||
|
return func(r *v1.Service) {
|
||||||
|
// Automatically inject URL into TrafficTarget status
|
||||||
|
for _, tt := range targets {
|
||||||
|
tt.URL = domains.URL(domains.HTTPScheme, tt.Tag+".example.com")
|
||||||
|
}
|
||||||
|
r.Status.Traffic = targets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceLatestReadyRevision sets the latest ready revision on the Service's status.
|
||||||
|
func WithServiceLatestReadyRevision(lrr string) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Status.LatestReadyRevisionName = lrr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReadinessProbe sets the provided probe to be the readiness
|
||||||
|
// probe on the service.
|
||||||
|
func WithReadinessProbe(p *corev1.Probe) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Spec.Template.Spec.Containers[0].ReadinessProbe = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkConfigurationNotReconciled calls the function of the same name on the Service's status.
|
||||||
|
func MarkConfigurationNotReconciled(service *v1.Service) {
|
||||||
|
service.Status.MarkConfigurationNotReconciled()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServiceStatusRouteNotReady sets the `RoutesReady` condition on the service to `Unknown`.
|
||||||
|
func WithServiceStatusRouteNotReady(s *v1.Service) {
|
||||||
|
s.Status.MarkRouteNotYetReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkConfigurationNotOwned calls the function of the same name on the Service's status.
|
||||||
|
func MarkConfigurationNotOwned(service *v1.Service) {
|
||||||
|
service.Status.MarkConfigurationNotOwned(servicenames.Configuration(service))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkRouteNotOwned calls the function of the same name on the Service's status.
|
||||||
|
func MarkRouteNotOwned(service *v1.Service) {
|
||||||
|
service.Status.MarkRouteNotOwned(servicenames.Route(service))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFailedRoute reflects a Route's failure in the Service resource.
|
||||||
|
func WithFailedRoute(reason, message string) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Status.PropagateRouteStatus(&v1.RouteStatus{
|
||||||
|
Status: duckv1.Status{
|
||||||
|
Conditions: duckv1.Conditions{{
|
||||||
|
Type: "Ready",
|
||||||
|
Status: "False",
|
||||||
|
Reason: reason,
|
||||||
|
Message: message,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFailedConfig reflects the Configuration's failure in the Service
|
||||||
|
// resource. The failing revision's name is reflected in LatestCreated.
|
||||||
|
func WithFailedConfig(name, reason, message string) ServiceOption {
|
||||||
|
return func(s *v1.Service) {
|
||||||
|
s.Status.PropagateConfigurationStatus(&v1.ConfigurationStatus{
|
||||||
|
Status: duckv1.Status{
|
||||||
|
Conditions: duckv1.Conditions{{
|
||||||
|
Type: "Ready",
|
||||||
|
Status: "False",
|
||||||
|
Reason: reason,
|
||||||
|
Message: fmt.Sprintf("Revision %q failed with message: %s.",
|
||||||
|
name, message),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
ConfigurationStatusFields: v1.ConfigurationStatusFields{
|
||||||
|
LatestCreatedRevisionName: name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// configSpec is the spec used for the different styles of Service rollout.
|
||||||
|
configSpec = v1.ConfigurationSpec{
|
||||||
|
Template: v1.RevisionTemplateSpec{
|
||||||
|
Spec: v1.RevisionSpec{
|
||||||
|
TimeoutSeconds: ptr.Int64(60),
|
||||||
|
PodSpec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Image: "busybox",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -906,7 +906,14 @@ knative.dev/serving/pkg/autoscaler/config/autoscalerconfig
|
||||||
knative.dev/serving/pkg/client/clientset/versioned/scheme
|
knative.dev/serving/pkg/client/clientset/versioned/scheme
|
||||||
knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1
|
knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1
|
||||||
knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1/fake
|
knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1/fake
|
||||||
|
knative.dev/serving/pkg/gc
|
||||||
knative.dev/serving/pkg/networking
|
knative.dev/serving/pkg/networking
|
||||||
|
knative.dev/serving/pkg/reconciler/route/config
|
||||||
|
knative.dev/serving/pkg/reconciler/route/domains
|
||||||
|
knative.dev/serving/pkg/reconciler/route/resources/labels
|
||||||
|
knative.dev/serving/pkg/reconciler/route/resources/names
|
||||||
|
knative.dev/serving/pkg/reconciler/service/resources/names
|
||||||
|
knative.dev/serving/pkg/testing/v1
|
||||||
knative.dev/serving/test/test_images/grpc-ping
|
knative.dev/serving/test/test_images/grpc-ping
|
||||||
knative.dev/serving/test/test_images/grpc-ping/proto
|
knative.dev/serving/test/test_images/grpc-ping/proto
|
||||||
# sigs.k8s.io/kustomize v2.0.3+incompatible
|
# sigs.k8s.io/kustomize v2.0.3+incompatible
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue