Update to serving 0.6.0 (#129)

* chore: Update to 0.6.0

* chore: Fix test

* chore: Update modules.txt
This commit is contained in:
Roland Huß 2019-05-20 19:41:27 +02:00 committed by Knative Prow Robot
parent a264f060c1
commit 52b6b236fe
177 changed files with 11683 additions and 4174 deletions

26
go.mod
View File

@ -1,13 +1,15 @@
module github.com/knative/client
require (
contrib.go.opencensus.io/exporter/stackdriver v0.11.0 // indirect
github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
github.com/evanphx/json-patch v4.2.0+incompatible // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gogo/protobuf v1.2.1 // indirect
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-containerregistry v0.0.0-20190424210018-7d6d1d3cd63b // indirect
github.com/google/go-cmp v0.3.0 // indirect
github.com/google/go-containerregistry v0.0.0-20190503220729-1c6c7f61e8a5 // indirect
github.com/google/gofuzz v1.0.0 // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
@ -15,29 +17,25 @@ require (
github.com/imdario/mergo v0.3.7 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/knative/build v0.5.0 // indirect
github.com/knative/pkg v0.0.0-20190329155329-916205998db9
github.com/knative/serving v0.5.2
github.com/knative/build v0.6.0 // indirect
github.com/knative/pkg v0.0.0-20190518173526-34792a92cec2
github.com/knative/serving v0.6.0
github.com/knative/test-infra v0.0.0-20190517181617-d1bb39bbca6e
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.1
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect
golang.org/x/oauth2 v0.0.0-20190517181255-950ef44c6e07 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.0.0-20190226173710-145d52631d00
k8s.io/apimachinery v0.0.0-20190221084156-01f179d85dbc
k8s.io/cli-runtime v0.0.0-20190226180714-082c0831af2b
k8s.io/client-go v0.0.0-20190226174127-78295b709ec6
k8s.io/kube-openapi v0.0.0-20190418160015-6b3d3b2d5666 // indirect
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22 // indirect
sigs.k8s.io/yaml v1.1.0
)

19
go.sum
View File

@ -44,6 +44,8 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
@ -84,8 +86,12 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-containerregistry v0.0.0-20190424210018-7d6d1d3cd63b h1:3KvxrcCoYX4wIZoeCMFfBPD840fopFkyjQeZDv5B+T8=
github.com/google/go-containerregistry v0.0.0-20190424210018-7d6d1d3cd63b/go.mod h1:yZAFP63pRshzrEYLXLGPmUt0Ay+2zdjmMN1loCnRLUk=
github.com/google/go-containerregistry v0.0.0-20190503220729-1c6c7f61e8a5 h1:wXZCVr9/0naJbZhAzKDzj/sgnduf8qn9ldjkI/CND9k=
github.com/google/go-containerregistry v0.0.0-20190503220729-1c6c7f61e8a5/go.mod h1:yZAFP63pRshzrEYLXLGPmUt0Ay+2zdjmMN1loCnRLUk=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -125,10 +131,16 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/knative/build v0.5.0 h1:q2W4+BmT3jEOSOXZ44UXndJUAHLWUd20QtMQAFGWzsU=
github.com/knative/build v0.5.0/go.mod h1:/sU74ZQkwlYA5FwYDJhYTy61i/Kn+5eWfln2jDbw3Qo=
github.com/knative/build v0.6.0 h1:tAhqGbRL3/wFOfEc4srO8XDNV8DxUE+z6TzKW0JPWsE=
github.com/knative/build v0.6.0/go.mod h1:/sU74ZQkwlYA5FwYDJhYTy61i/Kn+5eWfln2jDbw3Qo=
github.com/knative/pkg v0.0.0-20190329155329-916205998db9 h1:DnGe2nwEq+ibifGZt4HoD4akmX1K9Tcx3CjNwFpSZow=
github.com/knative/pkg v0.0.0-20190329155329-916205998db9/go.mod h1:7Ijfhw7rfB+H9VtosIsDYvZQ+qYTz7auK3fHW/5z4ww=
github.com/knative/pkg v0.0.0-20190518173526-34792a92cec2 h1:OA4f02os85BMZbC6DxNL5gbGHRxPgjQW+IDENES7QFc=
github.com/knative/pkg v0.0.0-20190518173526-34792a92cec2/go.mod h1:7Ijfhw7rfB+H9VtosIsDYvZQ+qYTz7auK3fHW/5z4ww=
github.com/knative/serving v0.5.2 h1:jsmeIN7B6oDHrK0jmtFRf7hWWr+KrjXVHuArK8jo5Nw=
github.com/knative/serving v0.5.2/go.mod h1:ljvMfwQy2qanaM/8xnBSK4Mz3Vv2NawC2fo5kFRJS1A=
github.com/knative/serving v0.6.0 h1:2SOr1jAvrUPO1y0mJvpiTe3bJTSMd2tKXflmHCM0MAA=
github.com/knative/serving v0.6.0/go.mod h1:ljvMfwQy2qanaM/8xnBSK4Mz3Vv2NawC2fo5kFRJS1A=
github.com/knative/test-infra v0.0.0-20190404172656-4ce16d390c55 h1:2tpTEN6OydMWVmkKJC3iVNrYbA+iHNL9qjLXe7hocfE=
github.com/knative/test-infra v0.0.0-20190404172656-4ce16d390c55/go.mod h1:l77IWBscEV5T4sYb64/9iwRCVY4UXEIqMcAppsblHW4=
github.com/knative/test-infra v0.0.0-20190509163238-a721698dbe49 h1:TEv7xkUjVofC2lqiSNeYjOYhC3XosGAfr0HG8x/lpC0=
@ -254,11 +266,15 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190514140710-3ec191127204 h1:4yG6GqBtw9C+UrLp6s2wtSniayy/Vd/3F7ffLE427XI=
golang.org/x/net v0.0.0-20190514140710-3ec191127204/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190517181255-950ef44c6e07 h1:XC1K3wNjuz44KaI+cj85C9TW85w/46RH7J+DTXNH5Wk=
golang.org/x/oauth2 v0.0.0-20190517181255-950ef44c6e07/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
@ -336,6 +352,9 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190418160015-6b3d3b2d5666 h1:hlzz2EvLPcefAcG/j0tOZpds4LWSElZzxpZuhxbblbc=
k8s.io/kube-openapi v0.0.0-20190418160015-6b3d3b2d5666/go.mod h1:jqYp7BKXW0Jl+F1dWXBieUmcHKMPpGHGWA0uqfpOZZ4=
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22 h1:f0BTap/vrgs21vVbJ1ySdsNtcivpA1x4ut6Wla9HKKw=
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22/go.mod h1:iU+ZGYsNlvU9XKUSso6SQfKTCCw7lFduMZy26Mgr2Fw=
sigs.k8s.io/structured-merge-diff v0.0.0-20181214233322-d43a45b8663b/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190426204423-ea680f03cc65/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@ -66,7 +66,7 @@ func TestDescribeRevisionYaml(t *testing.T) {
Namespace: "default",
},
Spec: v1alpha1.RevisionSpec{
Container: corev1.Container{
DeprecatedContainer: &corev1.Container{
Name: "some-container",
Image: "knative/test:latest",
},

View File

@ -21,6 +21,7 @@ import (
serving_lib "github.com/knative/client/pkg/serving"
servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -69,7 +70,16 @@ func NewServiceCreateCommand(p *KnParams) *cobra.Command {
Namespace: namespace,
},
}
service.Spec.RunLatest = &servingv1alpha1.RunLatestType{}
service.Spec.DeprecatedRunLatest = &servingv1alpha1.RunLatestType{
Configuration: servingv1alpha1.ConfigurationSpec{
DeprecatedRevisionTemplate: &servingv1alpha1.RevisionTemplateSpec{
Spec: servingv1alpha1.RevisionSpec{
DeprecatedContainer: &corev1.Container{},
},
},
},
}
config, err := serving_lib.GetConfiguration(&service)
if err != nil {

View File

@ -77,8 +77,8 @@ func TestServiceCreateImage(t *testing.T) {
conf, err := servinglib.GetConfiguration(created)
if err != nil {
t.Fatal(err)
} else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/bar:baz" {
t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image)
} else if conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image != "gcr.io/foo/bar:baz" {
t.Fatalf("wrong image set: %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image)
} else if !strings.Contains(output, "foo") || !strings.Contains(output, "created") ||
!strings.Contains(output, "default") {
t.Fatalf("wrong stdout message: %v", output)
@ -100,19 +100,19 @@ func TestServiceCreateEnv(t *testing.T) {
"B": "WOLVES"}
conf, err := servinglib.GetConfiguration(created)
actualEnvVars, err := servinglib.EnvToMap(conf.RevisionTemplate.Spec.Container.Env)
actualEnvVars, err := servinglib.EnvToMap(conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
if err != nil {
t.Fatal(err)
}
if err != nil {
t.Fatal(err)
} else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/bar:baz" {
t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image)
} else if conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image != "gcr.io/foo/bar:baz" {
t.Fatalf("wrong image set: %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image)
} else if !reflect.DeepEqual(
actualEnvVars,
expectedEnvVars) {
t.Fatalf("wrong env vars %v", conf.RevisionTemplate.Spec.Container.Env)
t.Fatalf("wrong env vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
}
}
@ -136,9 +136,9 @@ func TestServiceCreateWithRequests(t *testing.T) {
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Requests,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests,
expectedRequestsVars) {
t.Fatalf("wrong requests vars %v", conf.RevisionTemplate.Spec.Container.Resources.Requests)
t.Fatalf("wrong requests vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests)
}
}
@ -162,9 +162,9 @@ func TestServiceCreateWithLimits(t *testing.T) {
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Limits,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits,
expectedLimitsVars) {
t.Fatalf("wrong limits vars %v", conf.RevisionTemplate.Spec.Container.Resources.Limits)
t.Fatalf("wrong limits vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits)
}
}
@ -192,15 +192,15 @@ func TestServiceCreateRequestsLimitsCPU(t *testing.T) {
t.Fatal(err)
} else {
if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Requests,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests,
expectedRequestsVars) {
t.Fatalf("wrong requests vars %v", conf.RevisionTemplate.Spec.Container.Resources.Requests)
t.Fatalf("wrong requests vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests)
}
if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Limits,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits,
expectedLimitsVars) {
t.Fatalf("wrong limits vars %v", conf.RevisionTemplate.Spec.Container.Resources.Limits)
t.Fatalf("wrong limits vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits)
}
}
}
@ -229,15 +229,15 @@ func TestServiceCreateRequestsLimitsMemory(t *testing.T) {
t.Fatal(err)
} else {
if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Requests,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests,
expectedRequestsVars) {
t.Fatalf("wrong requests vars %v", conf.RevisionTemplate.Spec.Container.Resources.Requests)
t.Fatalf("wrong requests vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests)
}
if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Limits,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits,
expectedLimitsVars) {
t.Fatalf("wrong limits vars %v", conf.RevisionTemplate.Spec.Container.Resources.Limits)
t.Fatalf("wrong limits vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits)
}
}
}
@ -270,15 +270,15 @@ func TestServiceCreateRequestsLimitsCPUMemory(t *testing.T) {
t.Fatal(err)
} else {
if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Requests,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests,
expectedRequestsVars) {
t.Fatalf("wrong requests vars %v", conf.RevisionTemplate.Spec.Container.Resources.Requests)
t.Fatalf("wrong requests vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests)
}
if !reflect.DeepEqual(
conf.RevisionTemplate.Spec.Container.Resources.Limits,
conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits,
expectedLimitsVars) {
t.Fatalf("wrong limits vars %v", conf.RevisionTemplate.Spec.Container.Resources.Limits)
t.Fatalf("wrong limits vars %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits)
}
}
}
@ -307,8 +307,8 @@ func TestServiceCreateImageForce(t *testing.T) {
conf, err := servinglib.GetConfiguration(created)
if err != nil {
t.Fatal(err)
} else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/bar:v2" {
t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image)
} else if conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image != "gcr.io/foo/bar:v2" {
t.Fatalf("wrong image set: %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image)
} else if !strings.Contains(output, "foo") || !strings.Contains(output, "default") {
t.Fatalf("wrong output: %s", output)
}
@ -334,18 +334,18 @@ func TestServiceCreateEnvForce(t *testing.T) {
"B": "LIONS"}
conf, err := servinglib.GetConfiguration(created)
actualEnvVars, err := servinglib.EnvToMap(conf.RevisionTemplate.Spec.Container.Env)
actualEnvVars, err := servinglib.EnvToMap(conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
if err != nil {
t.Fatal(err)
}
if err != nil {
t.Fatal(err)
} else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/bar:v2" {
t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image)
} else if conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image != "gcr.io/foo/bar:v2" {
t.Fatalf("wrong image set: %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image)
} else if !reflect.DeepEqual(
actualEnvVars,
expectedEnvVars) {
t.Fatalf("wrong env vars:%v", conf.RevisionTemplate.Spec.Container.Env)
t.Fatalf("wrong env vars:%v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
} else if !strings.Contains(output, "foo") || !strings.Contains(output, "default") {
t.Fatalf("wrong output: %s", output)
}

View File

@ -17,7 +17,8 @@ package commands
import (
"fmt"
hprinters "github.com/knative/client/pkg/printers"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"github.com/knative/pkg/apis"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -95,7 +96,7 @@ func ServiceGetHandlers(h hprinters.PrintHandler) {
}
// conditionsValue returns the True conditions count among total conditions
func conditionsValue(conditions duckv1alpha1.Conditions) string {
func conditionsValue(conditions duckv1beta1.Conditions) string {
var ok int
for _, condition := range conditions {
if condition.Status == "True" {
@ -106,18 +107,18 @@ func conditionsValue(conditions duckv1alpha1.Conditions) string {
}
// readyCondition returns status of resource's Ready type condition
func readyCondition(conditions duckv1alpha1.Conditions) string {
func readyCondition(conditions duckv1beta1.Conditions) string {
for _, condition := range conditions {
if condition.Type == duckv1alpha1.ConditionReady {
if condition.Type == apis.ConditionReady {
return string(condition.Status)
}
}
return "<unknown>"
}
func nonReadyConditionReason(conditions duckv1alpha1.Conditions) string {
func nonReadyConditionReason(conditions duckv1beta1.Conditions) string {
for _, condition := range conditions {
if condition.Type == duckv1alpha1.ConditionReady {
if condition.Type == apis.ConditionReady {
if string(condition.Status) == "True" {
return ""
}
@ -155,7 +156,7 @@ func printKServiceList(kServiceList *servingv1alpha1.ServiceList, options hprint
// printKService populates the knative service table rows
func printKService(kService *servingv1alpha1.Service, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
name := kService.Name
domain := kService.Status.Domain
domain := kService.Status.RouteStatusFields.DeprecatedDomain
//lastCreatedRevision := kService.Status.LatestCreatedRevisionName
//lastReadyRevision := kService.Status.LatestReadyRevisionName
generation := kService.Status.ObservedGeneration

View File

@ -19,7 +19,7 @@ import (
"strings"
"testing"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
v1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
servingclient "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1"
"github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake"
@ -101,13 +101,13 @@ func createMockServiceWithParams(name, domain string, generation int64) *v1alpha
Namespace: "default",
},
Spec: v1alpha1.ServiceSpec{
RunLatest: &v1alpha1.RunLatestType{},
DeprecatedRunLatest: &v1alpha1.RunLatestType{},
},
Status: v1alpha1.ServiceStatus{
Status: duckv1alpha1.Status{
Status: duckv1beta1.Status{
ObservedGeneration: generation},
RouteStatusFields: v1alpha1.RouteStatusFields{
Domain: domain,
DeprecatedDomain: domain,
},
},
}

View File

@ -81,7 +81,15 @@ func TestServiceUpdateImage(t *testing.T) {
Namespace: "default",
},
Spec: v1alpha1.ServiceSpec{
RunLatest: &v1alpha1.RunLatestType{},
DeprecatedRunLatest: &v1alpha1.RunLatestType{
Configuration: v1alpha1.ConfigurationSpec{
DeprecatedRevisionTemplate: &v1alpha1.RevisionTemplateSpec{
Spec: v1alpha1.RevisionSpec{
DeprecatedContainer: &corev1.Container{},
},
},
},
},
},
}
@ -103,8 +111,8 @@ func TestServiceUpdateImage(t *testing.T) {
conf, err := servinglib.GetConfiguration(updated)
if err != nil {
t.Fatal(err)
} else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/quux:xyzzy" {
t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image)
} else if conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image != "gcr.io/foo/quux:xyzzy" {
t.Fatalf("wrong image set: %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image)
}
}
@ -119,7 +127,15 @@ func TestServiceUpdateEnv(t *testing.T) {
Namespace: "default",
},
Spec: v1alpha1.ServiceSpec{
RunLatest: &v1alpha1.RunLatestType{},
DeprecatedRunLatest: &v1alpha1.RunLatestType{
Configuration: v1alpha1.ConfigurationSpec{
DeprecatedRevisionTemplate: &v1alpha1.RevisionTemplateSpec{
Spec: v1alpha1.RevisionSpec{
DeprecatedContainer: &corev1.Container{},
},
},
},
},
},
}
@ -146,10 +162,10 @@ func TestServiceUpdateEnv(t *testing.T) {
conf, err := servinglib.GetConfiguration(updated)
if err != nil {
t.Fatal(err)
} else if conf.RevisionTemplate.Spec.Container.Image != "gcr.io/foo/bar:baz" {
t.Fatalf("wrong image set: %v", conf.RevisionTemplate.Spec.Container.Image)
} else if conf.RevisionTemplate.Spec.Container.Env[0] != expectedEnvVar {
t.Fatalf("wrong env set: %v", conf.RevisionTemplate.Spec.Container.Env)
} else if conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image != "gcr.io/foo/bar:baz" {
t.Fatalf("wrong image set: %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image)
} else if conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env[0] != expectedEnvVar {
t.Fatalf("wrong env set: %v", conf.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
}
}
@ -178,15 +194,15 @@ func TestServiceUpdateRequestsLimitsCPU(t *testing.T) {
t.Fatal(err)
} else {
if !reflect.DeepEqual(
newConfig.RevisionTemplate.Spec.Container.Resources.Requests,
newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests,
expectedRequestsVars) {
t.Fatalf("wrong requests vars %v", newConfig.RevisionTemplate.Spec.Container.Resources.Requests)
t.Fatalf("wrong requests vars %v", newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests)
}
if !reflect.DeepEqual(
newConfig.RevisionTemplate.Spec.Container.Resources.Limits,
newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits,
expectedLimitsVars) {
t.Fatalf("wrong limits vars %v", newConfig.RevisionTemplate.Spec.Container.Resources.Limits)
t.Fatalf("wrong limits vars %v", newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits)
}
}
}
@ -216,15 +232,15 @@ func TestServiceUpdateRequestsLimitsMemory(t *testing.T) {
t.Fatal(err)
} else {
if !reflect.DeepEqual(
newConfig.RevisionTemplate.Spec.Container.Resources.Requests,
newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests,
expectedRequestsVars) {
t.Fatalf("wrong requests vars %v", newConfig.RevisionTemplate.Spec.Container.Resources.Requests)
t.Fatalf("wrong requests vars %v", newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests)
}
if !reflect.DeepEqual(
newConfig.RevisionTemplate.Spec.Container.Resources.Limits,
newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits,
expectedLimitsVars) {
t.Fatalf("wrong limits vars %v", newConfig.RevisionTemplate.Spec.Container.Resources.Limits)
t.Fatalf("wrong limits vars %v", newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits)
}
}
}
@ -256,15 +272,15 @@ func TestServiceUpdateRequestsLimitsCPU_and_Memory(t *testing.T) {
t.Fatal(err)
} else {
if !reflect.DeepEqual(
newConfig.RevisionTemplate.Spec.Container.Resources.Requests,
newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests,
expectedRequestsVars) {
t.Fatalf("wrong requests vars %v", newConfig.RevisionTemplate.Spec.Container.Resources.Requests)
t.Fatalf("wrong requests vars %v", newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests)
}
if !reflect.DeepEqual(
newConfig.RevisionTemplate.Spec.Container.Resources.Limits,
newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits,
expectedLimitsVars) {
t.Fatalf("wrong limits vars %v", newConfig.RevisionTemplate.Spec.Container.Resources.Limits)
t.Fatalf("wrong limits vars %v", newConfig.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits)
}
}
}
@ -280,7 +296,15 @@ func createMockServiceWithResources(t *testing.T, requestCPU, requestMemory, lim
Namespace: "default",
},
Spec: v1alpha1.ServiceSpec{
RunLatest: &v1alpha1.RunLatestType{},
DeprecatedRunLatest: &v1alpha1.RunLatestType{
Configuration: v1alpha1.ConfigurationSpec{
DeprecatedRevisionTemplate: &v1alpha1.RevisionTemplateSpec{
Spec: v1alpha1.RevisionSpec{
DeprecatedContainer: &corev1.Container{},
},
},
},
},
},
}
@ -289,7 +313,7 @@ func createMockServiceWithResources(t *testing.T, requestCPU, requestMemory, lim
t.Fatal(err)
}
config.RevisionTemplate.Spec.Container.Resources = corev1.ResourceRequirements{
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources = corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse(requestCPU),
corev1.ResourceMemory: resource.MustParse(requestMemory),

View File

@ -26,8 +26,8 @@ import (
// new env vars and change the values of existing ones.
func UpdateEnvVars(config *servingv1alpha1.ConfigurationSpec, vars map[string]string) error {
set := make(map[string]bool)
for i, _ := range config.RevisionTemplate.Spec.Container.Env {
envVar := &config.RevisionTemplate.Spec.Container.Env[i]
for i, _ := range config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env {
envVar := &config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env[i]
value, present := vars[envVar.Name]
if present {
envVar.Value = value
@ -36,8 +36,8 @@ func UpdateEnvVars(config *servingv1alpha1.ConfigurationSpec, vars map[string]st
}
for name, value := range vars {
if !set[name] {
config.RevisionTemplate.Spec.Container.Env = append(
config.RevisionTemplate.Spec.Container.Env,
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env = append(
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env,
corev1.EnvVar{
Name: name,
Value: value,
@ -63,25 +63,25 @@ func EnvToMap(vars []corev1.EnvVar) (map[string]string, error) {
}
func UpdateImage(config *servingv1alpha1.ConfigurationSpec, image string) error {
config.RevisionTemplate.Spec.Container.Image = image
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Image = image
return nil
}
func UpdateResources(config *servingv1alpha1.ConfigurationSpec, requestsResourceList corev1.ResourceList, limitsResourceList corev1.ResourceList) error {
if config.RevisionTemplate.Spec.Container.Resources.Requests == nil {
config.RevisionTemplate.Spec.Container.Resources.Requests = corev1.ResourceList{}
if config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests == nil {
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests = corev1.ResourceList{}
}
for k, v := range requestsResourceList {
config.RevisionTemplate.Spec.Container.Resources.Requests[k] = v
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Requests[k] = v
}
if config.RevisionTemplate.Spec.Container.Resources.Limits == nil {
config.RevisionTemplate.Spec.Container.Resources.Limits = corev1.ResourceList{}
if config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits == nil {
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits = corev1.ResourceList{}
}
for k, v := range limitsResourceList {
config.RevisionTemplate.Spec.Container.Resources.Limits[k] = v
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Resources.Limits[k] = v
}
return nil

View File

@ -23,7 +23,7 @@ import (
)
func TestUpdateEnvVarsNew(t *testing.T) {
config := servingv1alpha1.ConfigurationSpec{}
config := getEmptyConfigurationSpec()
env := map[string]string{
"a": "foo",
"b": "bar",
@ -32,7 +32,7 @@ func TestUpdateEnvVarsNew(t *testing.T) {
if err != nil {
t.Fatal(err)
}
found, err := EnvToMap(config.RevisionTemplate.Spec.Container.Env)
found, err := EnvToMap(config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
if err != nil {
t.Fatal(err)
}
@ -42,8 +42,8 @@ func TestUpdateEnvVarsNew(t *testing.T) {
}
func TestUpdateEnvVarsAppend(t *testing.T) {
config := servingv1alpha1.ConfigurationSpec{}
config.RevisionTemplate.Spec.Container.Env = []corev1.EnvVar{
config := getEmptyConfigurationSpec()
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env = []corev1.EnvVar{
corev1.EnvVar{Name: "a", Value: "foo"}}
env := map[string]string{
"b": "bar",
@ -58,7 +58,7 @@ func TestUpdateEnvVarsAppend(t *testing.T) {
"b": "bar",
}
found, err := EnvToMap(config.RevisionTemplate.Spec.Container.Env)
found, err := EnvToMap(config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
if err != nil {
t.Fatal(err)
}
@ -68,8 +68,8 @@ func TestUpdateEnvVarsAppend(t *testing.T) {
}
func TestUpdateEnvVarsModify(t *testing.T) {
config := servingv1alpha1.ConfigurationSpec{}
config.RevisionTemplate.Spec.Container.Env = []corev1.EnvVar{
config := getEmptyConfigurationSpec()
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env = []corev1.EnvVar{
corev1.EnvVar{Name: "a", Value: "foo"}}
env := map[string]string{
"a": "fancy",
@ -83,7 +83,7 @@ func TestUpdateEnvVarsModify(t *testing.T) {
"a": "fancy",
}
found, err := EnvToMap(config.RevisionTemplate.Spec.Container.Env)
found, err := EnvToMap(config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
if err != nil {
t.Fatal(err)
}
@ -93,8 +93,8 @@ func TestUpdateEnvVarsModify(t *testing.T) {
}
func TestUpdateEnvVarsBoth(t *testing.T) {
config := servingv1alpha1.ConfigurationSpec{}
config.RevisionTemplate.Spec.Container.Env = []corev1.EnvVar{
config := getEmptyConfigurationSpec()
config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env = []corev1.EnvVar{
corev1.EnvVar{Name: "a", Value: "foo"},
corev1.EnvVar{Name: "c", Value: "caroline"}}
env := map[string]string{
@ -112,7 +112,7 @@ func TestUpdateEnvVarsBoth(t *testing.T) {
"c": "caroline",
}
found, err := EnvToMap(config.RevisionTemplate.Spec.Container.Env)
found, err := EnvToMap(config.DeprecatedRevisionTemplate.Spec.DeprecatedContainer.Env)
if err != nil {
t.Fatal(err)
}
@ -120,3 +120,13 @@ func TestUpdateEnvVarsBoth(t *testing.T) {
t.Fatalf("Env did not match expected %v found %v", env, found)
}
}
func getEmptyConfigurationSpec() servingv1alpha1.ConfigurationSpec {
return servingv1alpha1.ConfigurationSpec{
DeprecatedRevisionTemplate: &servingv1alpha1.RevisionTemplateSpec{
Spec: servingv1alpha1.RevisionSpec{
DeprecatedContainer: &corev1.Container{},
},
},
}
}

View File

@ -21,10 +21,10 @@ import (
)
func GetConfiguration(service *servingv1alpha1.Service) (*servingv1alpha1.ConfigurationSpec, error) {
if service.Spec.RunLatest != nil {
return &service.Spec.RunLatest.Configuration, nil
} else if service.Spec.Release != nil {
return &service.Spec.Release.Configuration, nil
if service.Spec.DeprecatedRunLatest != nil {
return &service.Spec.DeprecatedRunLatest.Configuration, nil
} else if service.Spec.DeprecatedRelease != nil {
return &service.Spec.DeprecatedRelease.Configuration, nil
} else if service.Spec.DeprecatedPinned != nil {
return &service.Spec.DeprecatedPinned.Configuration, nil
} else {

View File

@ -137,7 +137,7 @@ func testOnGCE() bool {
resc := make(chan bool, 2)
// Try two strategies in parallel.
// See https://github.com/googleapis/google-cloud-go/issues/194
// See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194
go func() {
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
req.Header.Set("User-Agent", userAgent)
@ -300,8 +300,8 @@ func (c *Client) getETag(suffix string) (value, etag string, err error) {
// being stable anyway.
host = metadataIP
}
u := "http://" + host + "/computeMetadata/v1/" + suffix
req, _ := http.NewRequest("GET", u, nil)
url := "http://" + host + "/computeMetadata/v1/" + suffix
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Metadata-Flavor", "Google")
req.Header.Set("User-Agent", userAgent)
res, err := c.hc.Do(req)
@ -312,13 +312,13 @@ func (c *Client) getETag(suffix string) (value, etag string, err error) {
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
if res.StatusCode != 200 {
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
if res.StatusCode != 200 {
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
}
return string(all), res.Header.Get("Etag"), nil
}
@ -499,15 +499,3 @@ func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) erro
}
}
}
// Error contains an error response from the server.
type Error struct {
// Code is the HTTP response status code.
Code int
// Message is the server response message.
Message string
}
func (e *Error) Error() string {
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
}

View File

@ -28,10 +28,15 @@ go get -u github.com/evanphx/json-patch
# Configuration
There is a single global configuration variable `jsonpatch.SupportNegativeIndices'. This
defaults to `true` and enables the non-standard practice of allowing negative indices
to mean indices starting at the end of an array. This functionality can be disabled
by setting `jsonpatch.SupportNegativeIndices = false`.
* There is a global configuration variable `jsonpatch.SupportNegativeIndices`.
This defaults to `true` and enables the non-standard practice of allowing
negative indices to mean indices starting at the end of an array. This
functionality can be disabled by setting `jsonpatch.SupportNegativeIndices =
false`.
* There is a global configuration variable `jsonpatch.AccumulatedCopySizeLimit`,
which limits the total size increase in bytes caused by "copy" operations in a
patch. It defaults to 0, which means there is no limit.
## Create and apply a merge patch
Given both an original JSON document and a modified JSON document, you can create

38
vendor/github.com/evanphx/json-patch/errors.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
package jsonpatch
import "fmt"
// AccumulatedCopySizeError is an error type returned when the accumulated size
// increase caused by copy operations in a patch operation has exceeded the
// limit.
type AccumulatedCopySizeError struct {
limit int64
accumulated int64
}
// NewAccumulatedCopySizeError returns an AccumulatedCopySizeError.
func NewAccumulatedCopySizeError(l, a int64) *AccumulatedCopySizeError {
return &AccumulatedCopySizeError{limit: l, accumulated: a}
}
// Error implements the error interface.
func (a *AccumulatedCopySizeError) Error() string {
return fmt.Sprintf("Unable to complete the copy, the accumulated size increase of copy is %d, exceeding the limit %d", a.accumulated, a.limit)
}
// ArraySizeError is an error type returned when the array size has exceeded
// the limit.
type ArraySizeError struct {
limit int
size int
}
// NewArraySizeError returns an ArraySizeError.
func NewArraySizeError(l, s int) *ArraySizeError {
return &ArraySizeError{limit: l, size: s}
}
// Error implements the error interface.
func (a *ArraySizeError) Error() string {
return fmt.Sprintf("Unable to create array of size %d, limit is %d", a.size, a.limit)
}

View File

@ -14,7 +14,15 @@ const (
eAry
)
var SupportNegativeIndices bool = true
var (
// SupportNegativeIndices decides whether to support non-standard practice of
// allowing negative indices to mean indices starting at the end of an array.
// Default to true.
SupportNegativeIndices bool = true
// AccumulatedCopySizeLimit limits the total size increase in bytes caused by
// "copy" operations in a patch.
AccumulatedCopySizeLimit int64 = 0
)
type lazyNode struct {
raw *json.RawMessage
@ -63,6 +71,20 @@ func (n *lazyNode) UnmarshalJSON(data []byte) error {
return nil
}
func deepCopy(src *lazyNode) (*lazyNode, int, error) {
if src == nil {
return nil, 0, nil
}
a, err := src.MarshalJSON()
if err != nil {
return nil, 0, err
}
sz := len(a)
ra := make(json.RawMessage, sz)
copy(ra, a)
return newLazyNode(&ra), sz, nil
}
func (n *lazyNode) intoDoc() (*partialDoc, error) {
if n.which == eDoc {
return &n.doc, nil
@ -344,35 +366,14 @@ func (d *partialDoc) remove(key string) error {
return nil
}
// set should only be used to implement the "replace" operation, so "key" must
// be an already existing index in "d".
func (d *partialArray) set(key string, val *lazyNode) error {
if key == "-" {
*d = append(*d, val)
return nil
}
idx, err := strconv.Atoi(key)
if err != nil {
return err
}
sz := len(*d)
if idx+1 > sz {
sz = idx + 1
}
ary := make([]*lazyNode, sz)
cur := *d
copy(ary, cur)
if idx >= len(ary) {
return fmt.Errorf("Unable to access invalid index: %d", idx)
}
ary[idx] = val
*d = ary
(*d)[idx] = val
return nil
}
@ -387,7 +388,9 @@ func (d *partialArray) add(key string, val *lazyNode) error {
return err
}
ary := make([]*lazyNode, len(*d)+1)
sz := len(*d) + 1
ary := make([]*lazyNode, sz)
cur := *d
@ -527,7 +530,7 @@ func (p Patch) move(doc *container, op operation) error {
return fmt.Errorf("jsonpatch move operation does not apply: doc is missing destination path: %s", path)
}
return con.set(key, val)
return con.add(key, val)
}
func (p Patch) test(doc *container, op operation) error {
@ -561,7 +564,7 @@ func (p Patch) test(doc *container, op operation) error {
return fmt.Errorf("Testing value %s failed", path)
}
func (p Patch) copy(doc *container, op operation) error {
func (p Patch) copy(doc *container, op operation, accumulatedCopySize *int64) error {
from := op.from()
con, key := findObject(doc, from)
@ -583,7 +586,16 @@ func (p Patch) copy(doc *container, op operation) error {
return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing destination path: %s", path)
}
return con.set(key, val)
valCopy, sz, err := deepCopy(val)
if err != nil {
return err
}
(*accumulatedCopySize) += int64(sz)
if AccumulatedCopySizeLimit > 0 && *accumulatedCopySize > AccumulatedCopySizeLimit {
return NewAccumulatedCopySizeError(AccumulatedCopySizeLimit, *accumulatedCopySize)
}
return con.add(key, valCopy)
}
// Equal indicates if 2 JSON documents have the same structural equality.
@ -636,6 +648,8 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
err = nil
var accumulatedCopySize int64
for _, op := range p {
switch op.kind() {
case "add":
@ -649,7 +663,7 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
case "test":
err = p.test(&pd, op)
case "copy":
err = p.copy(&pd, op)
err = p.copy(&pd, op, &accumulatedCopySize)
default:
err = fmt.Errorf("Unexpected kind: %s", op.kind())
}

View File

@ -29,26 +29,17 @@ package cmp
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp/internal/diff"
"github.com/google/go-cmp/cmp/internal/flags"
"github.com/google/go-cmp/cmp/internal/function"
"github.com/google/go-cmp/cmp/internal/value"
)
// BUG(dsnet): Maps with keys containing NaN values cannot be properly compared due to
// the reflection package's inability to retrieve such entries. Equal will panic
// anytime it comes across a NaN key, but this behavior may change.
//
// See https://golang.org/issue/11104 for more details.
var nothing = reflect.Value{}
// Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values:
//
// • If two values are not of the same type, then they are never equal
// and the overall result is false.
//
// • Let S be the set of all Ignore, Transformer, and Comparer options that
// remain after applying all path filters, value filters, and type filters.
// If at least one Ignore exists in S, then the comparison is ignored.
@ -61,43 +52,79 @@ var nothing = reflect.Value{}
//
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
// x.Equal(y) even if x or y is nil.
// Otherwise, no such method exists and evaluation proceeds to the next rule.
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
// evaluation proceeds to the next rule.
//
// • Lastly, try to compare x and y based on their basic kinds.
// Simple kinds like booleans, integers, floats, complex numbers, strings, and
// channels are compared using the equivalent of the == operator in Go.
// Functions are only equal if they are both nil, otherwise they are unequal.
// Pointers are equal if the underlying values they point to are also equal.
// Interfaces are equal if their underlying concrete values are also equal.
//
// Structs are equal if all of their fields are equal. If a struct contains
// unexported fields, Equal panics unless the AllowUnexported option is used or
// an Ignore option (e.g., cmpopts.IgnoreUnexported) ignores that field.
// Structs are equal if recursively calling Equal on all fields report equal.
// If a struct contains unexported fields, Equal panics unless an Ignore option
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the AllowUnexported
// option explicitly permits comparing the unexported field.
//
// Arrays, slices, and maps are equal if they are both nil or both non-nil
// with the same length and the elements at each index or key are equal.
// Note that a non-nil empty slice and a nil slice are not equal.
// To equate empty slices and maps, consider using cmpopts.EquateEmpty.
// Slices are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored slice or array elements report equal.
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
// consider using cmpopts.EquateEmpty.
//
// Maps are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored map entries report equal.
// Map keys are equal according to the == operator.
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
// consider using cmpopts.EquateEmpty.
//
// Pointers and interfaces are equal if they are both nil or both non-nil,
// where they have the same underlying concrete type and recursively
// calling Equal on the underlying values reports equal.
func Equal(x, y interface{}, opts ...Option) bool {
vx := reflect.ValueOf(x)
vy := reflect.ValueOf(y)
// If the inputs are different types, auto-wrap them in an empty interface
// so that they have the same parent type.
var t reflect.Type
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
t = reflect.TypeOf((*interface{})(nil)).Elem()
if vx.IsValid() {
vvx := reflect.New(t).Elem()
vvx.Set(vx)
vx = vvx
}
if vy.IsValid() {
vvy := reflect.New(t).Elem()
vvy.Set(vy)
vy = vvy
}
} else {
t = vx.Type()
}
s := newState(opts)
s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))
s.compareAny(&pathStep{t, vx, vy})
return s.result.Equal()
}
// Diff returns a human-readable report of the differences between two values.
// It returns an empty string if and only if Equal returns true for the same
// input values and options. The output string will use the "-" symbol to
// indicate elements removed from x, and the "+" symbol to indicate elements
// added to y.
// input values and options.
//
// Do not depend on this output being stable.
// The output is displayed as a literal in pseudo-Go syntax.
// At the start of each line, a "-" prefix indicates an element removed from x,
// a "+" prefix to indicates an element added to y, and the lack of a prefix
// indicates an element common to both x and y. If possible, the output
// uses fmt.Stringer.String or error.Error methods to produce more humanly
// readable outputs. In such cases, the string is prefixed with either an
// 's' or 'e' character, respectively, to indicate that the method was called.
//
// Do not depend on this output being stable. If you need the ability to
// programmatically interpret the difference, consider using a custom Reporter.
func Diff(x, y interface{}, opts ...Option) string {
r := new(defaultReporter)
opts = Options{Options(opts), r}
eq := Equal(x, y, opts...)
eq := Equal(x, y, Options(opts), Reporter(r))
d := r.String()
if (d == "") != eq {
panic("inconsistent difference and equality results")
@ -108,9 +135,13 @@ func Diff(x, y interface{}, opts ...Option) string {
type state struct {
// These fields represent the "comparison state".
// Calling statelessCompare must not result in observable changes to these.
result diff.Result // The current result of comparison
curPath Path // The current path in the value tree
reporter reporter // Optional reporter used for difference formatting
result diff.Result // The current result of comparison
curPath Path // The current path in the value tree
reporters []reporter // Optional reporters
// recChecker checks for infinite cycles applying the same set of
// transformers upon the output of itself.
recChecker recChecker
// dynChecker triggers pseudo-random checks for option correctness.
// It is safe for statelessCompare to mutate this value.
@ -122,10 +153,9 @@ type state struct {
}
func newState(opts []Option) *state {
s := new(state)
for _, opt := range opts {
s.processOption(opt)
}
// Always ensure a validator option exists to validate the inputs.
s := &state{opts: Options{validator{}}}
s.processOption(Options(opts))
return s
}
@ -152,10 +182,7 @@ func (s *state) processOption(opt Option) {
s.exporters[t] = true
}
case reporter:
if s.reporter != nil {
panic("difference reporter already registered")
}
s.reporter = opt
s.reporters = append(s.reporters, opt)
default:
panic(fmt.Sprintf("unknown option %T", opt))
}
@ -164,153 +191,88 @@ func (s *state) processOption(opt Option) {
// statelessCompare compares two values and returns the result.
// This function is stateless in that it does not alter the current result,
// or output to any registered reporters.
func (s *state) statelessCompare(vx, vy reflect.Value) diff.Result {
func (s *state) statelessCompare(step PathStep) diff.Result {
// We do not save and restore the curPath because all of the compareX
// methods should properly push and pop from the path.
// It is an implementation bug if the contents of curPath differs from
// when calling this function to when returning from it.
oldResult, oldReporter := s.result, s.reporter
oldResult, oldReporters := s.result, s.reporters
s.result = diff.Result{} // Reset result
s.reporter = nil // Remove reporter to avoid spurious printouts
s.compareAny(vx, vy)
s.reporters = nil // Remove reporters to avoid spurious printouts
s.compareAny(step)
res := s.result
s.result, s.reporter = oldResult, oldReporter
s.result, s.reporters = oldResult, oldReporters
return res
}
func (s *state) compareAny(vx, vy reflect.Value) {
// TODO: Support cyclic data structures.
func (s *state) compareAny(step PathStep) {
// Update the path stack.
s.curPath.push(step)
defer s.curPath.pop()
for _, r := range s.reporters {
r.PushStep(step)
defer r.PopStep()
}
s.recChecker.Check(s.curPath)
// Rule 0: Differing types are never equal.
if !vx.IsValid() || !vy.IsValid() {
s.report(vx.IsValid() == vy.IsValid(), vx, vy)
return
}
if vx.Type() != vy.Type() {
s.report(false, vx, vy) // Possible for path to be empty
return
}
t := vx.Type()
if len(s.curPath) == 0 {
s.curPath.push(&pathStep{typ: t})
defer s.curPath.pop()
}
vx, vy = s.tryExporting(vx, vy)
// Obtain the current type and values.
t := step.Type()
vx, vy := step.Values()
// Rule 1: Check whether an option applies on this node in the value tree.
if s.tryOptions(vx, vy, t) {
if s.tryOptions(t, vx, vy) {
return
}
// Rule 2: Check whether the type has a valid Equal method.
if s.tryMethod(vx, vy, t) {
if s.tryMethod(t, vx, vy) {
return
}
// Rule 3: Recursively descend into each value's underlying kind.
// Rule 3: Compare based on the underlying kind.
switch t.Kind() {
case reflect.Bool:
s.report(vx.Bool() == vy.Bool(), vx, vy)
return
s.report(vx.Bool() == vy.Bool(), 0)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s.report(vx.Int() == vy.Int(), vx, vy)
return
s.report(vx.Int() == vy.Int(), 0)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
s.report(vx.Uint() == vy.Uint(), vx, vy)
return
s.report(vx.Uint() == vy.Uint(), 0)
case reflect.Float32, reflect.Float64:
s.report(vx.Float() == vy.Float(), vx, vy)
return
s.report(vx.Float() == vy.Float(), 0)
case reflect.Complex64, reflect.Complex128:
s.report(vx.Complex() == vy.Complex(), vx, vy)
return
s.report(vx.Complex() == vy.Complex(), 0)
case reflect.String:
s.report(vx.String() == vy.String(), vx, vy)
return
s.report(vx.String() == vy.String(), 0)
case reflect.Chan, reflect.UnsafePointer:
s.report(vx.Pointer() == vy.Pointer(), vx, vy)
return
s.report(vx.Pointer() == vy.Pointer(), 0)
case reflect.Func:
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
case reflect.Ptr:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
s.curPath.push(&indirect{pathStep{t.Elem()}})
defer s.curPath.pop()
s.compareAny(vx.Elem(), vy.Elem())
return
case reflect.Interface:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
if vx.Elem().Type() != vy.Elem().Type() {
s.report(false, vx.Elem(), vy.Elem())
return
}
s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}})
defer s.curPath.pop()
s.compareAny(vx.Elem(), vy.Elem())
return
case reflect.Slice:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
fallthrough
case reflect.Array:
s.compareArray(vx, vy, t)
return
case reflect.Map:
s.compareMap(vx, vy, t)
return
s.report(vx.IsNil() && vy.IsNil(), 0)
case reflect.Struct:
s.compareStruct(vx, vy, t)
return
s.compareStruct(t, vx, vy)
case reflect.Slice, reflect.Array:
s.compareSlice(t, vx, vy)
case reflect.Map:
s.compareMap(t, vx, vy)
case reflect.Ptr:
s.comparePtr(t, vx, vy)
case reflect.Interface:
s.compareInterface(t, vx, vy)
default:
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
}
}
func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) {
if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
if sf.force {
// Use unsafe pointer arithmetic to get read-write access to an
// unexported field in the struct.
vx = unsafeRetrieveField(sf.pvx, sf.field)
vy = unsafeRetrieveField(sf.pvy, sf.field)
} else {
// We are not allowed to export the value, so invalidate them
// so that tryOptions can panic later if not explicitly ignored.
vx = nothing
vy = nothing
}
}
return vx, vy
}
func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool {
// If there were no FilterValues, we will not detect invalid inputs,
// so manually check for them and append invalid if necessary.
// We still evaluate the options since an ignore can override invalid.
opts := s.opts
if !vx.IsValid() || !vy.IsValid() {
opts = Options{opts, invalid{}}
}
func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool {
// Evaluate all filters and apply the remaining options.
if opt := opts.filter(s, vx, vy, t); opt != nil {
if opt := s.opts.filter(s, t, vx, vy); opt != nil {
opt.apply(s, vx, vy)
return true
}
return false
}
func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
// Check if this type even has an Equal method.
m, ok := t.MethodByName("Equal")
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
@ -318,11 +280,11 @@ func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
}
eq := s.callTTBFunc(m.Func, vx, vy)
s.report(eq, vx, vy)
s.report(eq, reportByMethod)
return true
}
func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
v = sanitizeValue(v, f.Type().In(0))
if !s.dynChecker.Next() {
return f.Call([]reflect.Value{v})[0]
@ -333,15 +295,15 @@ func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, v)
got := <-c
want := f.Call([]reflect.Value{v})[0]
if got := <-c; !s.statelessCompare(got, want).Equal() {
if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() {
// To avoid false-positives with non-reflexive equality operations,
// we sanity check whether a value is equal to itself.
if !s.statelessCompare(want, want).Equal() {
if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() {
return want
}
fn := getFuncName(f.Pointer())
panic(fmt.Sprintf("non-deterministic function detected: %s", fn))
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
}
return want
}
@ -359,10 +321,10 @@ func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, y, x)
got := <-c
want := f.Call([]reflect.Value{x, y})[0].Bool()
if got := <-c; !got.IsValid() || got.Bool() != want {
fn := getFuncName(f.Pointer())
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
if !got.IsValid() || got.Bool() != want {
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
}
return want
}
@ -380,140 +342,241 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
// assuming that T is assignable to R.
// Otherwise, it returns the input value as is.
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
// TODO(dsnet): Remove this hacky workaround.
// See https://golang.org/issue/22143
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
return reflect.New(t).Elem()
// TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143).
if !flags.AtLeastGo110 {
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
return reflect.New(t).Elem()
}
}
return v
}
func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) {
step := &sliceIndex{pathStep{t.Elem()}, 0, 0}
s.curPath.push(step)
// Compute an edit-script for slices vx and vy.
es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
step.xkey, step.ykey = ix, iy
return s.statelessCompare(vx.Index(ix), vy.Index(iy))
})
// Report the entire slice as is if the arrays are of primitive kind,
// and the arrays are different enough.
isPrimitive := false
switch t.Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
isPrimitive = true
}
if isPrimitive && es.Dist() > (vx.Len()+vy.Len())/4 {
s.curPath.pop() // Pop first since we are reporting the whole slice
s.report(false, vx, vy)
return
}
// Replay the edit-script.
var ix, iy int
for _, e := range es {
switch e {
case diff.UniqueX:
step.xkey, step.ykey = ix, -1
s.report(false, vx.Index(ix), nothing)
ix++
case diff.UniqueY:
step.xkey, step.ykey = -1, iy
s.report(false, nothing, vy.Index(iy))
iy++
default:
step.xkey, step.ykey = ix, iy
if e == diff.Identity {
s.report(true, vx.Index(ix), vy.Index(iy))
} else {
s.compareAny(vx.Index(ix), vy.Index(iy))
}
ix++
iy++
}
}
s.curPath.pop()
return
}
func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
return
}
// We combine and sort the two map keys so that we can perform the
// comparisons in a deterministic order.
step := &mapIndex{pathStep: pathStep{t.Elem()}}
s.curPath.push(step)
defer s.curPath.pop()
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
step.key = k
vvx := vx.MapIndex(k)
vvy := vy.MapIndex(k)
switch {
case vvx.IsValid() && vvy.IsValid():
s.compareAny(vvx, vvy)
case vvx.IsValid() && !vvy.IsValid():
s.report(false, vvx, nothing)
case !vvx.IsValid() && vvy.IsValid():
s.report(false, nothing, vvy)
default:
// It is possible for both vvx and vvy to be invalid if the
// key contained a NaN value in it. There is no way in
// reflection to be able to retrieve these values.
// See https://golang.org/issue/11104
panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath))
}
}
}
func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) {
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
var vax, vay reflect.Value // Addressable versions of vx and vy
step := &structField{}
s.curPath.push(step)
defer s.curPath.pop()
step := StructField{&structField{}}
for i := 0; i < t.NumField(); i++ {
vvx := vx.Field(i)
vvy := vy.Field(i)
step.typ = t.Field(i).Type
step.vx = vx.Field(i)
step.vy = vy.Field(i)
step.name = t.Field(i).Name
step.idx = i
step.unexported = !isExported(step.name)
if step.unexported {
if step.name == "_" {
continue
}
// Defer checking of unexported fields until later to give an
// Ignore a chance to ignore the field.
if !vax.IsValid() || !vay.IsValid() {
// For unsafeRetrieveField to work, the parent struct must
// For retrieveUnexportedField to work, the parent struct must
// be addressable. Create a new copy of the values if
// necessary to make them addressable.
vax = makeAddressable(vx)
vay = makeAddressable(vy)
}
step.force = s.exporters[t]
step.mayForce = s.exporters[t]
step.pvx = vax
step.pvy = vay
step.field = t.Field(i)
}
s.compareAny(vvx, vvy)
s.compareAny(step)
}
}
// report records the result of a single comparison.
// It also calls Report if any reporter is registered.
func (s *state) report(eq bool, vx, vy reflect.Value) {
if eq {
s.result.NSame++
} else {
s.result.NDiff++
func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
isSlice := t.Kind() == reflect.Slice
if isSlice && (vx.IsNil() || vy.IsNil()) {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
if s.reporter != nil {
s.reporter.Report(vx, vy, eq, s.curPath)
// TODO: Support cyclic data structures.
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}}}
withIndexes := func(ix, iy int) SliceIndex {
if ix >= 0 {
step.vx, step.xkey = vx.Index(ix), ix
} else {
step.vx, step.xkey = reflect.Value{}, -1
}
if iy >= 0 {
step.vy, step.ykey = vy.Index(iy), iy
} else {
step.vy, step.ykey = reflect.Value{}, -1
}
return step
}
// Ignore options are able to ignore missing elements in a slice.
// However, detecting these reliably requires an optimal differencing
// algorithm, for which diff.Difference is not.
//
// Instead, we first iterate through both slices to detect which elements
// would be ignored if standing alone. The index of non-discarded elements
// are stored in a separate slice, which diffing is then performed on.
var indexesX, indexesY []int
var ignoredX, ignoredY []bool
for ix := 0; ix < vx.Len(); ix++ {
ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0
if !ignored {
indexesX = append(indexesX, ix)
}
ignoredX = append(ignoredX, ignored)
}
for iy := 0; iy < vy.Len(); iy++ {
ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0
if !ignored {
indexesY = append(indexesY, iy)
}
ignoredY = append(ignoredY, ignored)
}
// Compute an edit-script for slices vx and vy (excluding ignored elements).
edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result {
return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy]))
})
// Replay the ignore-scripts and the edit-script.
var ix, iy int
for ix < vx.Len() || iy < vy.Len() {
var e diff.EditType
switch {
case ix < len(ignoredX) && ignoredX[ix]:
e = diff.UniqueX
case iy < len(ignoredY) && ignoredY[iy]:
e = diff.UniqueY
default:
e, edits = edits[0], edits[1:]
}
switch e {
case diff.UniqueX:
s.compareAny(withIndexes(ix, -1))
ix++
case diff.UniqueY:
s.compareAny(withIndexes(-1, iy))
iy++
default:
s.compareAny(withIndexes(ix, iy))
ix++
iy++
}
}
}
func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
// TODO: Support cyclic data structures.
// We combine and sort the two map keys so that we can perform the
// comparisons in a deterministic order.
step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}}
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
step.vx = vx.MapIndex(k)
step.vy = vy.MapIndex(k)
step.key = k
if !step.vx.IsValid() && !step.vy.IsValid() {
// It is possible for both vx and vy to be invalid if the
// key contained a NaN value in it.
//
// Even with the ability to retrieve NaN keys in Go 1.12,
// there still isn't a sensible way to compare the values since
// a NaN key may map to multiple unordered values.
// The most reasonable way to compare NaNs would be to compare the
// set of values. However, this is impossible to do efficiently
// since set equality is provably an O(n^2) operation given only
// an Equal function. If we had a Less function or Hash function,
// this could be done in O(n*log(n)) or O(n), respectively.
//
// Rather than adding complex logic to deal with NaNs, make it
// the user's responsibility to compare such obscure maps.
const help = "consider providing a Comparer to compare the map"
panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help))
}
s.compareAny(step)
}
}
func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
// TODO: Support cyclic data structures.
vx, vy = vx.Elem(), vy.Elem()
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
}
func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
vx, vy = vx.Elem(), vy.Elem()
if vx.Type() != vy.Type() {
s.report(false, 0)
return
}
s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}})
}
func (s *state) report(eq bool, rf resultFlags) {
if rf&reportByIgnore == 0 {
if eq {
s.result.NumSame++
rf |= reportEqual
} else {
s.result.NumDiff++
rf |= reportUnequal
}
}
for _, r := range s.reporters {
r.Report(Result{flags: rf})
}
}
// recChecker tracks the state needed to periodically perform checks that
// user provided transformers are not stuck in an infinitely recursive cycle.
type recChecker struct{ next int }
// Check scans the Path for any recursive transformers and panics when any
// recursive transformers are detected. Note that the presence of a
// recursive Transformer does not necessarily imply an infinite cycle.
// As such, this check only activates after some minimal number of path steps.
func (rc *recChecker) Check(p Path) {
const minLen = 1 << 16
if rc.next == 0 {
rc.next = minLen
}
if len(p) < rc.next {
return
}
rc.next <<= 1
// Check whether the same transformer has appeared at least twice.
var ss []string
m := map[Option]int{}
for _, ps := range p {
if t, ok := ps.(Transform); ok {
t := t.Option()
if m[t] == 1 { // Transformer was used exactly once before
tf := t.(*transformer).fnc.Type()
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
}
m[t]++
}
}
if len(ss) > 0 {
const warning = "recursive set of Transformers detected"
const help = "consider using cmpopts.AcyclicTransformer"
set := strings.Join(ss, "\n\t")
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
}
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build purego appengine js
// +build purego
package cmp
@ -10,6 +10,6 @@ import "reflect"
const supportAllowUnexported = false
func unsafeRetrieveField(reflect.Value, reflect.StructField) reflect.Value {
panic("unsafeRetrieveField is not implemented")
func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value {
panic("retrieveUnexportedField is not implemented")
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build !purego,!appengine,!js
// +build !purego
package cmp
@ -13,11 +13,11 @@ import (
const supportAllowUnexported = true
// unsafeRetrieveField uses unsafe to forcibly retrieve any field from a struct
// such that the value has read-write permissions.
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
// a struct such that the value has read-write permissions.
//
// The parent struct, v, must be addressable, while f must be a StructField
// describing the field to retrieve.
func unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value {
func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value {
return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem()
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build !debug
// +build !cmp_debug
package diff

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build debug
// +build cmp_debug
package diff
@ -14,7 +14,7 @@ import (
)
// The algorithm can be seen running in real-time by enabling debugging:
// go test -tags=debug -v
// go test -tags=cmp_debug -v
//
// Example output:
// === RUN TestDifference/#34

View File

@ -85,22 +85,31 @@ func (es EditScript) LenY() int { return len(es) - es.stats().NX }
type EqualFunc func(ix int, iy int) Result
// Result is the result of comparison.
// NSame is the number of sub-elements that are equal.
// NDiff is the number of sub-elements that are not equal.
type Result struct{ NSame, NDiff int }
// NumSame is the number of sub-elements that are equal.
// NumDiff is the number of sub-elements that are not equal.
type Result struct{ NumSame, NumDiff int }
// BoolResult returns a Result that is either Equal or not Equal.
func BoolResult(b bool) Result {
if b {
return Result{NumSame: 1} // Equal, Similar
} else {
return Result{NumDiff: 2} // Not Equal, not Similar
}
}
// Equal indicates whether the symbols are equal. Two symbols are equal
// if and only if NDiff == 0. If Equal, then they are also Similar.
func (r Result) Equal() bool { return r.NDiff == 0 }
// if and only if NumDiff == 0. If Equal, then they are also Similar.
func (r Result) Equal() bool { return r.NumDiff == 0 }
// Similar indicates whether two symbols are similar and may be represented
// by using the Modified type. As a special case, we consider binary comparisons
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
//
// The exact ratio of NSame to NDiff to determine similarity may change.
// The exact ratio of NumSame to NumDiff to determine similarity may change.
func (r Result) Similar() bool {
// Use NSame+1 to offset NSame so that binary comparisons are similar.
return r.NSame+1 >= r.NDiff
// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
return r.NumSame+1 >= r.NumDiff
}
// Difference reports whether two lists of lengths nx and ny are equal
@ -191,9 +200,9 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// that two lists commonly differ because elements were added to the front
// or end of the other list.
//
// Running the tests with the "debug" build tag prints a visualization of
// the algorithm running in real-time. This is educational for understanding
// how the algorithm works. See debug_enable.go.
// Running the tests with the "cmp_debug" build tag prints a visualization
// of the algorithm running in real-time. This is educational for
// understanding how the algorithm works. See debug_enable.go.
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
for {
// Forward search from the beginning.

View File

@ -0,0 +1,9 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package flags
// Deterministic controls whether the output of Diff should be deterministic.
// This is only used for testing.
var Deterministic bool

View File

@ -0,0 +1,10 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build !go1.10
package flags
// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
const AtLeastGo110 = false

View File

@ -0,0 +1,10 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build go1.10
package flags
// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
const AtLeastGo110 = true

View File

@ -2,25 +2,34 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// Package function identifies function types.
// Package function provides functionality for identifying function types.
package function
import "reflect"
import (
"reflect"
"regexp"
"runtime"
"strings"
)
type funcType int
const (
_ funcType = iota
tbFunc // func(T) bool
ttbFunc // func(T, T) bool
trbFunc // func(T, R) bool
tibFunc // func(T, I) bool
trFunc // func(T) R
Equal = ttbFunc // func(T, T) bool
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
Transformer = trFunc // func(T) R
ValueFilter = ttbFunc // func(T, T) bool
Less = ttbFunc // func(T, T) bool
Equal = ttbFunc // func(T, T) bool
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
Transformer = trFunc // func(T) R
ValueFilter = ttbFunc // func(T, T) bool
Less = ttbFunc // func(T, T) bool
ValuePredicate = tbFunc // func(T) bool
KeyValuePredicate = trbFunc // func(T, R) bool
)
var boolType = reflect.TypeOf(true)
@ -32,10 +41,18 @@ func IsType(t reflect.Type, ft funcType) bool {
}
ni, no := t.NumIn(), t.NumOut()
switch ft {
case tbFunc: // func(T) bool
if ni == 1 && no == 1 && t.Out(0) == boolType {
return true
}
case ttbFunc: // func(T, T) bool
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
return true
}
case trbFunc: // func(T, R) bool
if ni == 2 && no == 1 && t.Out(0) == boolType {
return true
}
case tibFunc: // func(T, I) bool
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
return true
@ -47,3 +64,36 @@ func IsType(t reflect.Type, ft funcType) bool {
}
return false
}
var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
// NameOf returns the name of the function value.
func NameOf(v reflect.Value) string {
fnc := runtime.FuncForPC(v.Pointer())
if fnc == nil {
return "<unknown>"
}
fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
// Method closures have a "-fm" suffix.
fullName = strings.TrimSuffix(fullName, "-fm")
var name string
for len(fullName) > 0 {
inParen := strings.HasSuffix(fullName, ")")
fullName = strings.TrimSuffix(fullName, ")")
s := lastIdentRx.FindString(fullName)
if s == "" {
break
}
name = s + "." + name
fullName = strings.TrimSuffix(fullName, s)
if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
fullName = fullName[:i]
}
fullName = strings.TrimSuffix(fullName, ".")
}
return strings.TrimSuffix(name, ".")
}

View File

@ -1,277 +0,0 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// Package value provides functionality for reflect.Value types.
package value
import (
"fmt"
"reflect"
"strconv"
"strings"
"unicode"
)
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
// Format formats the value v as a string.
//
// This is similar to fmt.Sprintf("%+v", v) except this:
// * Prints the type unless it can be elided
// * Avoids printing struct fields that are zero
// * Prints a nil-slice as being nil, not empty
// * Prints map entries in deterministic order
func Format(v reflect.Value, conf FormatConfig) string {
conf.printType = true
conf.followPointers = true
conf.realPointers = true
return formatAny(v, conf, nil)
}
type FormatConfig struct {
UseStringer bool // Should the String method be used if available?
printType bool // Should we print the type before the value?
PrintPrimitiveType bool // Should we print the type of primitives?
followPointers bool // Should we recursively follow pointers?
realPointers bool // Should we print the real address of pointers?
}
func formatAny(v reflect.Value, conf FormatConfig, visited map[uintptr]bool) string {
// TODO: Should this be a multi-line printout in certain situations?
if !v.IsValid() {
return "<non-existent>"
}
if conf.UseStringer && v.Type().Implements(stringerIface) && v.CanInterface() {
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
return "<nil>"
}
const stringerPrefix = "s" // Indicates that the String method was used
s := v.Interface().(fmt.Stringer).String()
return stringerPrefix + formatString(s)
}
switch v.Kind() {
case reflect.Bool:
return formatPrimitive(v.Type(), v.Bool(), conf)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return formatPrimitive(v.Type(), v.Int(), conf)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
// Unnamed uints are usually bytes or words, so use hexadecimal.
return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
}
return formatPrimitive(v.Type(), v.Uint(), conf)
case reflect.Float32, reflect.Float64:
return formatPrimitive(v.Type(), v.Float(), conf)
case reflect.Complex64, reflect.Complex128:
return formatPrimitive(v.Type(), v.Complex(), conf)
case reflect.String:
return formatPrimitive(v.Type(), formatString(v.String()), conf)
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
return formatPointer(v, conf)
case reflect.Ptr:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("(%v)(nil)", v.Type())
}
return "<nil>"
}
if visited[v.Pointer()] || !conf.followPointers {
return formatPointer(v, conf)
}
visited = insertPointer(visited, v.Pointer())
return "&" + formatAny(v.Elem(), conf, visited)
case reflect.Interface:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
return formatAny(v.Elem(), conf, visited)
case reflect.Slice:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
if visited[v.Pointer()] {
return formatPointer(v, conf)
}
visited = insertPointer(visited, v.Pointer())
fallthrough
case reflect.Array:
var ss []string
subConf := conf
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
for i := 0; i < v.Len(); i++ {
s := formatAny(v.Index(i), subConf, visited)
ss = append(ss, s)
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
case reflect.Map:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
if visited[v.Pointer()] {
return formatPointer(v, conf)
}
visited = insertPointer(visited, v.Pointer())
var ss []string
keyConf, valConf := conf, conf
keyConf.printType = v.Type().Key().Kind() == reflect.Interface
keyConf.followPointers = false
valConf.printType = v.Type().Elem().Kind() == reflect.Interface
for _, k := range SortKeys(v.MapKeys()) {
sk := formatAny(k, keyConf, visited)
sv := formatAny(v.MapIndex(k), valConf, visited)
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
case reflect.Struct:
var ss []string
subConf := conf
subConf.printType = true
for i := 0; i < v.NumField(); i++ {
vv := v.Field(i)
if isZero(vv) {
continue // Elide zero value fields
}
name := v.Type().Field(i).Name
subConf.UseStringer = conf.UseStringer
s := formatAny(vv, subConf, visited)
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
default:
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
}
}
func formatString(s string) string {
// Use quoted string if it the same length as a raw string literal.
// Otherwise, attempt to use the raw string form.
qs := strconv.Quote(s)
if len(qs) == 1+len(s)+1 {
return qs
}
// Disallow newlines to ensure output is a single line.
// Only allow printable runes for readability purposes.
rawInvalid := func(r rune) bool {
return r == '`' || r == '\n' || !unicode.IsPrint(r)
}
if strings.IndexFunc(s, rawInvalid) < 0 {
return "`" + s + "`"
}
return qs
}
func formatPrimitive(t reflect.Type, v interface{}, conf FormatConfig) string {
if conf.printType && (conf.PrintPrimitiveType || t.PkgPath() != "") {
return fmt.Sprintf("%v(%v)", t, v)
}
return fmt.Sprintf("%v", v)
}
func formatPointer(v reflect.Value, conf FormatConfig) string {
p := v.Pointer()
if !conf.realPointers {
p = 0 // For deterministic printing purposes
}
s := formatHex(uint64(p))
if conf.printType {
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
}
return s
}
func formatHex(u uint64) string {
var f string
switch {
case u <= 0xff:
f = "0x%02x"
case u <= 0xffff:
f = "0x%04x"
case u <= 0xffffff:
f = "0x%06x"
case u <= 0xffffffff:
f = "0x%08x"
case u <= 0xffffffffff:
f = "0x%010x"
case u <= 0xffffffffffff:
f = "0x%012x"
case u <= 0xffffffffffffff:
f = "0x%014x"
case u <= 0xffffffffffffffff:
f = "0x%016x"
}
return fmt.Sprintf(f, u)
}
// insertPointer insert p into m, allocating m if necessary.
func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
if m == nil {
m = make(map[uintptr]bool)
}
m[p] = true
return m
}
// isZero reports whether v is the zero value.
// This does not rely on Interface and so can be used on unexported fields.
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool() == false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Complex64, reflect.Complex128:
return v.Complex() == 0
case reflect.String:
return v.String() == ""
case reflect.UnsafePointer:
return v.Pointer() == 0
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if !isZero(v.Index(i)) {
return false
}
}
return true
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !isZero(v.Field(i)) {
return false
}
}
return true
}
return false
}

View File

@ -0,0 +1,23 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build purego
package value
import "reflect"
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
type Pointer struct {
p uintptr
t reflect.Type
}
// PointerOf returns a Pointer from v, which must be a
// reflect.Ptr, reflect.Slice, or reflect.Map.
func PointerOf(v reflect.Value) Pointer {
// NOTE: Storing a pointer as an uintptr is technically incorrect as it
// assumes that the GC implementation does not use a moving collector.
return Pointer{v.Pointer(), v.Type()}
}

View File

@ -0,0 +1,26 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build !purego
package value
import (
"reflect"
"unsafe"
)
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
type Pointer struct {
p unsafe.Pointer
t reflect.Type
}
// PointerOf returns a Pointer from v, which must be a
// reflect.Ptr, reflect.Slice, or reflect.Map.
func PointerOf(v reflect.Value) Pointer {
// The proper representation of a pointer is unsafe.Pointer,
// which is necessary if the GC ever uses a moving collector.
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
}

View File

@ -19,7 +19,7 @@ func SortKeys(vs []reflect.Value) []reflect.Value {
}
// Sort the map keys.
sort.Sort(valueSorter(vs))
sort.Slice(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) })
// Deduplicate keys (fails for NaNs).
vs2 := vs[:1]
@ -31,13 +31,6 @@ func SortKeys(vs []reflect.Value) []reflect.Value {
return vs2
}
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
type valueSorter []reflect.Value
func (vs valueSorter) Len() int { return len(vs) }
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
// isLess is a generic function for sorting arbitrary map keys.
// The inputs must be of the same type and must be comparable.
func isLess(x, y reflect.Value) bool {

View File

@ -0,0 +1,45 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package value
import "reflect"
// IsZero reports whether v is the zero value.
// This does not rely on Interface and so can be used on unexported fields.
func IsZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool() == false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Complex64, reflect.Complex128:
return v.Complex() == 0
case reflect.String:
return v.String() == ""
case reflect.UnsafePointer:
return v.Pointer() == 0
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if !IsZero(v.Index(i)) {
return false
}
}
return true
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !IsZero(v.Field(i)) {
return false
}
}
return true
}
return false
}

View File

@ -7,7 +7,7 @@ package cmp
import (
"fmt"
"reflect"
"runtime"
"regexp"
"strings"
"github.com/google/go-cmp/cmp/internal/function"
@ -29,11 +29,11 @@ type Option interface {
// An Options is returned only if multiple comparers or transformers
// can apply simultaneously and will only contain values of those types
// or sub-Options containing values of those types.
filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption
filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption
}
// applicableOption represents the following types:
// Fundamental: ignore | invalid | *comparer | *transformer
// Fundamental: ignore | validator | *comparer | *transformer
// Grouping: Options
type applicableOption interface {
Option
@ -43,7 +43,7 @@ type applicableOption interface {
}
// coreOption represents the following types:
// Fundamental: ignore | invalid | *comparer | *transformer
// Fundamental: ignore | validator | *comparer | *transformer
// Filters: *pathFilter | *valuesFilter
type coreOption interface {
Option
@ -63,19 +63,19 @@ func (core) isCore() {}
// on all individual options held within.
type Options []Option
func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) {
func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) {
for _, opt := range opts {
switch opt := opt.filter(s, vx, vy, t); opt.(type) {
switch opt := opt.filter(s, t, vx, vy); opt.(type) {
case ignore:
return ignore{} // Only ignore can short-circuit evaluation
case invalid:
out = invalid{} // Takes precedence over comparer or transformer
case validator:
out = validator{} // Takes precedence over comparer or transformer
case *comparer, *transformer, Options:
switch out.(type) {
case nil:
out = opt
case invalid:
// Keep invalid
case validator:
// Keep validator
case *comparer, *transformer, Options:
out = Options{out, opt} // Conflicting comparers or transformers
}
@ -106,6 +106,11 @@ func (opts Options) String() string {
// FilterPath returns a new Option where opt is only evaluated if filter f
// returns true for the current Path in the value tree.
//
// This filter is called even if a slice element or map entry is missing and
// provides an opportunity to ignore such cases. The filter function must be
// symmetric such that the filter result is identical regardless of whether the
// missing value is from x or y.
//
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
// a previously filtered Option.
func FilterPath(f func(Path) bool, opt Option) Option {
@ -124,22 +129,22 @@ type pathFilter struct {
opt Option
}
func (f pathFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
if f.fnc(s.curPath) {
return f.opt.filter(s, vx, vy, t)
return f.opt.filter(s, t, vx, vy)
}
return nil
}
func (f pathFilter) String() string {
fn := getFuncName(reflect.ValueOf(f.fnc).Pointer())
return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt)
return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)
}
// FilterValues returns a new Option where opt is only evaluated if filter f,
// which is a function of the form "func(T, T) bool", returns true for the
// current pair of values being compared. If the type of the values is not
// assignable to T, then this filter implicitly returns false.
// current pair of values being compared. If either value is invalid or
// the type of the values is not assignable to T, then this filter implicitly
// returns false.
//
// The filter function must be
// symmetric (i.e., agnostic to the order of the inputs) and
@ -171,19 +176,18 @@ type valuesFilter struct {
opt Option
}
func (f valuesFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
if !vx.IsValid() || !vy.IsValid() {
return invalid{}
func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() {
return nil
}
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
return f.opt.filter(s, vx, vy, t)
return f.opt.filter(s, t, vx, vy)
}
return nil
}
func (f valuesFilter) String() string {
fn := getFuncName(f.fnc.Pointer())
return fmt.Sprintf("FilterValues(%s, %v)", fn, f.opt)
return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)
}
// Ignore is an Option that causes all comparisons to be ignored.
@ -194,19 +198,44 @@ func Ignore() Option { return ignore{} }
type ignore struct{ core }
func (ignore) isFiltered() bool { return false }
func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} }
func (ignore) apply(_ *state, _, _ reflect.Value) { return }
func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }
func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) }
func (ignore) String() string { return "Ignore()" }
// invalid is a sentinel Option type to indicate that some options could not
// be evaluated due to unexported fields.
type invalid struct{ core }
// validator is a sentinel Option type to indicate that some options could not
// be evaluated due to unexported fields, missing slice elements, or
// missing map entries. Both values are validator only for unexported fields.
type validator struct{ core }
func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} }
func (invalid) apply(s *state, _, _ reflect.Value) {
const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption {
if !vx.IsValid() || !vy.IsValid() {
return validator{}
}
if !vx.CanInterface() || !vy.CanInterface() {
return validator{}
}
return nil
}
func (validator) apply(s *state, vx, vy reflect.Value) {
// Implies missing slice element or map entry.
if !vx.IsValid() || !vy.IsValid() {
s.report(vx.IsValid() == vy.IsValid(), 0)
return
}
// Unable to Interface implies unexported field without visibility access.
if !vx.CanInterface() || !vy.CanInterface() {
const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider AllowUnexported or cmpopts.IgnoreUnexported"
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
}
panic("not reachable")
}
// identRx represents a valid identifier according to the Go specification.
const identRx = `[_\p{L}][_\p{L}\p{N}]*`
var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)
// Transformer returns an Option that applies a transformation function that
// converts values of a certain type into that of another.
@ -220,18 +249,25 @@ func (invalid) apply(s *state, _, _ reflect.Value) {
// input and output types are the same), an implicit filter is added such that
// a transformer is applicable only if that exact transformer is not already
// in the tail of the Path since the last non-Transform step.
// For situations where the implicit filter is still insufficient,
// consider using cmpopts.AcyclicTransformer, which adds a filter
// to prevent the transformer from being recursively applied upon itself.
//
// The name is a user provided label that is used as the Transform.Name in the
// transformation PathStep. If empty, an arbitrary name is used.
// transformation PathStep (and eventually shown in the Diff output).
// The name must be a valid identifier or qualified identifier in Go syntax.
// If empty, an arbitrary name is used.
func Transformer(name string, f interface{}) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
panic(fmt.Sprintf("invalid transformer function: %T", f))
}
if name == "" {
name = "λ" // Lambda-symbol as place-holder for anonymous transformer
}
if !isValid(name) {
name = function.NameOf(v)
if !identsRx.MatchString(name) {
name = "λ" // Lambda-symbol as placeholder name
}
} else if !identsRx.MatchString(name) {
panic(fmt.Sprintf("invalid name: %q", name))
}
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
@ -250,9 +286,9 @@ type transformer struct {
func (tr *transformer) isFiltered() bool { return tr.typ != nil }
func (tr *transformer) filter(s *state, _, _ reflect.Value, t reflect.Type) applicableOption {
func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption {
for i := len(s.curPath) - 1; i >= 0; i-- {
if t, ok := s.curPath[i].(*transform); !ok {
if t, ok := s.curPath[i].(Transform); !ok {
break // Hit most recent non-Transform step
} else if tr == t.trans {
return nil // Cannot directly use same Transform
@ -265,18 +301,15 @@ func (tr *transformer) filter(s *state, _, _ reflect.Value, t reflect.Type) appl
}
func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
// Update path before calling the Transformer so that dynamic checks
// will use the updated path.
s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr})
defer s.curPath.pop()
vx = s.callTRFunc(tr.fnc, vx)
vy = s.callTRFunc(tr.fnc, vy)
s.compareAny(vx, vy)
step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}}
vvx := s.callTRFunc(tr.fnc, vx, step)
vvy := s.callTRFunc(tr.fnc, vy, step)
step.vx, step.vy = vvx, vvy
s.compareAny(step)
}
func (tr transformer) String() string {
return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer()))
return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))
}
// Comparer returns an Option that determines whether two values are equal
@ -311,7 +344,7 @@ type comparer struct {
func (cm *comparer) isFiltered() bool { return cm.typ != nil }
func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption {
if cm.typ == nil || t.AssignableTo(cm.typ) {
return cm
}
@ -320,11 +353,11 @@ func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applica
func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
eq := s.callTTBFunc(cm.fnc, vx, vy)
s.report(eq, vx, vy)
s.report(eq, reportByFunc)
}
func (cm comparer) String() string {
return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer()))
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
}
// AllowUnexported returns an Option that forcibly allows operations on
@ -338,7 +371,7 @@ func (cm comparer) String() string {
// defined in an internal package where the semantic meaning of an unexported
// field is in the control of the user.
//
// For some cases, a custom Comparer should be used instead that defines
// In many cases, a custom Comparer should be used instead that defines
// equality as a function of the public API of a type rather than the underlying
// unexported implementation.
//
@ -370,27 +403,92 @@ func AllowUnexported(types ...interface{}) Option {
type visibleStructs map[reflect.Type]bool
func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption {
func (visibleStructs) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
panic("not implemented")
}
// reporter is an Option that configures how differences are reported.
type reporter interface {
// TODO: Not exported yet.
// Result represents the comparison result for a single node and
// is provided by cmp when calling Result (see Reporter).
type Result struct {
_ [0]func() // Make Result incomparable
flags resultFlags
}
// Equal reports whether the node was determined to be equal or not.
// As a special case, ignored nodes are considered equal.
func (r Result) Equal() bool {
return r.flags&(reportEqual|reportByIgnore) != 0
}
// ByIgnore reports whether the node is equal because it was ignored.
// This never reports true if Equal reports false.
func (r Result) ByIgnore() bool {
return r.flags&reportByIgnore != 0
}
// ByMethod reports whether the Equal method determined equality.
func (r Result) ByMethod() bool {
return r.flags&reportByMethod != 0
}
// ByFunc reports whether a Comparer function determined equality.
func (r Result) ByFunc() bool {
return r.flags&reportByFunc != 0
}
type resultFlags uint
const (
_ resultFlags = (1 << iota) / 2
reportEqual
reportUnequal
reportByIgnore
reportByMethod
reportByFunc
)
// Reporter is an Option that can be passed to Equal. When Equal traverses
// the value trees, it calls PushStep as it descends into each node in the
// tree and PopStep as it ascend out of the node. The leaves of the tree are
// either compared (determined to be equal or not equal) or ignored and reported
// as such by calling the Report method.
func Reporter(r interface {
// PushStep is called when a tree-traversal operation is performed.
// The PathStep itself is only valid until the step is popped.
// The PathStep.Values are valid for the duration of the entire traversal
// and must not be mutated.
//
// Perhaps add PushStep and PopStep and change Report to only accept
// a PathStep instead of the full-path? Adding a PushStep and PopStep makes
// it clear that we are traversing the value tree in a depth-first-search
// manner, which has an effect on how values are printed.
// Equal always calls PushStep at the start to provide an operation-less
// PathStep used to report the root values.
//
// Within a slice, the exact set of inserted, removed, or modified elements
// is unspecified and may change in future implementations.
// The entries of a map are iterated through in an unspecified order.
PushStep(PathStep)
Option
// Report is called exactly once on leaf nodes to report whether the
// comparison identified the node as equal, unequal, or ignored.
// A leaf node is one that is immediately preceded by and followed by
// a pair of PushStep and PopStep calls.
Report(Result)
// Report is called for every comparison made and will be provided with
// the two values being compared, the equality result, and the
// current path in the value tree. It is possible for x or y to be an
// invalid reflect.Value if one of the values is non-existent;
// which is possible with maps and slices.
Report(x, y reflect.Value, eq bool, p Path)
// PopStep ascends back up the value tree.
// There is always a matching pop call for every push call.
PopStep()
}) Option {
return reporter{r}
}
type reporter struct{ reporterIface }
type reporterIface interface {
PushStep(PathStep)
Report(Result)
PopStep()
}
func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
panic("not implemented")
}
// normalizeOption normalizes the input options such that all Options groups
@ -424,30 +522,3 @@ func flattenOptions(dst, src Options) Options {
}
return dst
}
// getFuncName returns a short function name from the pointer.
// The string parsing logic works up until Go1.9.
func getFuncName(p uintptr) string {
fnc := runtime.FuncForPC(p)
if fnc == nil {
return "<unknown>"
}
name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
// Strip the package name from method name.
name = strings.TrimSuffix(name, ")-fm")
name = strings.TrimSuffix(name, ")·fm")
if i := strings.LastIndexByte(name, '('); i >= 0 {
methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
methodName = methodName[j+1:] // E.g., "myfunc"
}
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
}
}
if i := strings.LastIndexByte(name, '/'); i >= 0 {
// Strip the package name.
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
}
return name
}

View File

@ -12,80 +12,52 @@ import (
"unicode/utf8"
)
type (
// Path is a list of PathSteps describing the sequence of operations to get
// from some root type to the current position in the value tree.
// The first Path element is always an operation-less PathStep that exists
// simply to identify the initial type.
// Path is a list of PathSteps describing the sequence of operations to get
// from some root type to the current position in the value tree.
// The first Path element is always an operation-less PathStep that exists
// simply to identify the initial type.
//
// When traversing structs with embedded structs, the embedded struct will
// always be accessed as a field before traversing the fields of the
// embedded struct themselves. That is, an exported field from the
// embedded struct will never be accessed directly from the parent struct.
type Path []PathStep
// PathStep is a union-type for specific operations to traverse
// a value's tree structure. Users of this package never need to implement
// these types as values of this type will be returned by this package.
//
// Implementations of this interface are
// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform.
type PathStep interface {
String() string
// Type is the resulting type after performing the path step.
Type() reflect.Type
// Values is the resulting values after performing the path step.
// The type of each valid value is guaranteed to be identical to Type.
//
// When traversing structs with embedded structs, the embedded struct will
// always be accessed as a field before traversing the fields of the
// embedded struct themselves. That is, an exported field from the
// embedded struct will never be accessed directly from the parent struct.
Path []PathStep
// In some cases, one or both may be invalid or have restrictions:
// • For StructField, both are not interface-able if the current field
// is unexported and the struct type is not explicitly permitted by
// AllowUnexported to traverse unexported fields.
// • For SliceIndex, one may be invalid if an element is missing from
// either the x or y slice.
// • For MapIndex, one may be invalid if an entry is missing from
// either the x or y map.
//
// The provided values must not be mutated.
Values() (vx, vy reflect.Value)
}
// PathStep is a union-type for specific operations to traverse
// a value's tree structure. Users of this package never need to implement
// these types as values of this type will be returned by this package.
PathStep interface {
String() string
Type() reflect.Type // Resulting type after performing the path step
isPathStep()
}
// SliceIndex is an index operation on a slice or array at some index Key.
SliceIndex interface {
PathStep
Key() int // May return -1 if in a split state
// SplitKeys returns the indexes for indexing into slices in the
// x and y values, respectively. These indexes may differ due to the
// insertion or removal of an element in one of the slices, causing
// all of the indexes to be shifted. If an index is -1, then that
// indicates that the element does not exist in the associated slice.
//
// Key is guaranteed to return -1 if and only if the indexes returned
// by SplitKeys are not the same. SplitKeys will never return -1 for
// both indexes.
SplitKeys() (x int, y int)
isSliceIndex()
}
// MapIndex is an index operation on a map at some index Key.
MapIndex interface {
PathStep
Key() reflect.Value
isMapIndex()
}
// TypeAssertion represents a type assertion on an interface.
TypeAssertion interface {
PathStep
isTypeAssertion()
}
// StructField represents a struct field access on a field called Name.
StructField interface {
PathStep
Name() string
Index() int
isStructField()
}
// Indirect represents pointer indirection on the parent type.
Indirect interface {
PathStep
isIndirect()
}
// Transform is a transformation from the parent type to the current type.
Transform interface {
PathStep
Name() string
Func() reflect.Value
// Option returns the originally constructed Transformer option.
// The == operator can be used to detect the exact option used.
Option() Option
isTransform()
}
var (
_ PathStep = StructField{}
_ PathStep = SliceIndex{}
_ PathStep = MapIndex{}
_ PathStep = Indirect{}
_ PathStep = TypeAssertion{}
_ PathStep = Transform{}
)
func (pa *Path) push(s PathStep) {
@ -124,7 +96,7 @@ func (pa Path) Index(i int) PathStep {
func (pa Path) String() string {
var ss []string
for _, s := range pa {
if _, ok := s.(*structField); ok {
if _, ok := s.(StructField); ok {
ss = append(ss, s.String())
}
}
@ -144,13 +116,13 @@ func (pa Path) GoString() string {
nextStep = pa[i+1]
}
switch s := s.(type) {
case *indirect:
case Indirect:
numIndirect++
pPre, pPost := "(", ")"
switch nextStep.(type) {
case *indirect:
case Indirect:
continue // Next step is indirection, so let them batch up
case *structField:
case StructField:
numIndirect-- // Automatic indirection on struct fields
case nil:
pPre, pPost = "", "" // Last step; no need for parenthesis
@ -161,19 +133,10 @@ func (pa Path) GoString() string {
}
numIndirect = 0
continue
case *transform:
case Transform:
ssPre = append(ssPre, s.trans.name+"(")
ssPost = append(ssPost, ")")
continue
case *typeAssertion:
// As a special-case, elide type assertions on anonymous types
// since they are typically generated dynamically and can be very
// verbose. For example, some transforms return interface{} because
// of Go's lack of generics, but typically take in and return the
// exact same concrete type.
if s.Type().PkgPath() == "" {
continue
}
}
ssPost = append(ssPost, s.String())
}
@ -183,44 +146,13 @@ func (pa Path) GoString() string {
return strings.Join(ssPre, "") + strings.Join(ssPost, "")
}
type (
pathStep struct {
typ reflect.Type
}
type pathStep struct {
typ reflect.Type
vx, vy reflect.Value
}
sliceIndex struct {
pathStep
xkey, ykey int
}
mapIndex struct {
pathStep
key reflect.Value
}
typeAssertion struct {
pathStep
}
structField struct {
pathStep
name string
idx int
// These fields are used for forcibly accessing an unexported field.
// pvx, pvy, and field are only valid if unexported is true.
unexported bool
force bool // Forcibly allow visibility
pvx, pvy reflect.Value // Parent values
field reflect.StructField // Field information
}
indirect struct {
pathStep
}
transform struct {
pathStep
trans *transformer
}
)
func (ps pathStep) Type() reflect.Type { return ps.typ }
func (ps pathStep) Type() reflect.Type { return ps.typ }
func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy }
func (ps pathStep) String() string {
if ps.typ == nil {
return "<nil>"
@ -232,7 +164,54 @@ func (ps pathStep) String() string {
return fmt.Sprintf("{%s}", s)
}
func (si sliceIndex) String() string {
// StructField represents a struct field access on a field called Name.
type StructField struct{ *structField }
type structField struct {
pathStep
name string
idx int
// These fields are used for forcibly accessing an unexported field.
// pvx, pvy, and field are only valid if unexported is true.
unexported bool
mayForce bool // Forcibly allow visibility
pvx, pvy reflect.Value // Parent values
field reflect.StructField // Field information
}
func (sf StructField) Type() reflect.Type { return sf.typ }
func (sf StructField) Values() (vx, vy reflect.Value) {
if !sf.unexported {
return sf.vx, sf.vy // CanInterface reports true
}
// Forcibly obtain read-write access to an unexported struct field.
if sf.mayForce {
vx = retrieveUnexportedField(sf.pvx, sf.field)
vy = retrieveUnexportedField(sf.pvy, sf.field)
return vx, vy // CanInterface reports true
}
return sf.vx, sf.vy // CanInterface reports false
}
func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) }
// Name is the field name.
func (sf StructField) Name() string { return sf.name }
// Index is the index of the field in the parent struct type.
// See reflect.Type.Field.
func (sf StructField) Index() int { return sf.idx }
// SliceIndex is an index operation on a slice or array at some index Key.
type SliceIndex struct{ *sliceIndex }
type sliceIndex struct {
pathStep
xkey, ykey int
}
func (si SliceIndex) Type() reflect.Type { return si.typ }
func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy }
func (si SliceIndex) String() string {
switch {
case si.xkey == si.ykey:
return fmt.Sprintf("[%d]", si.xkey)
@ -247,63 +226,83 @@ func (si sliceIndex) String() string {
return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
}
}
func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) }
func (in indirect) String() string { return "*" }
func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
func (si sliceIndex) Key() int {
// Key is the index key; it may return -1 if in a split state
func (si SliceIndex) Key() int {
if si.xkey != si.ykey {
return -1
}
return si.xkey
}
func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey }
func (mi mapIndex) Key() reflect.Value { return mi.key }
func (sf structField) Name() string { return sf.name }
func (sf structField) Index() int { return sf.idx }
func (tf transform) Name() string { return tf.trans.name }
func (tf transform) Func() reflect.Value { return tf.trans.fnc }
func (tf transform) Option() Option { return tf.trans }
func (pathStep) isPathStep() {}
func (sliceIndex) isSliceIndex() {}
func (mapIndex) isMapIndex() {}
func (typeAssertion) isTypeAssertion() {}
func (structField) isStructField() {}
func (indirect) isIndirect() {}
func (transform) isTransform() {}
// SplitKeys are the indexes for indexing into slices in the
// x and y values, respectively. These indexes may differ due to the
// insertion or removal of an element in one of the slices, causing
// all of the indexes to be shifted. If an index is -1, then that
// indicates that the element does not exist in the associated slice.
//
// Key is guaranteed to return -1 if and only if the indexes returned
// by SplitKeys are not the same. SplitKeys will never return -1 for
// both indexes.
func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey }
var (
_ SliceIndex = sliceIndex{}
_ MapIndex = mapIndex{}
_ TypeAssertion = typeAssertion{}
_ StructField = structField{}
_ Indirect = indirect{}
_ Transform = transform{}
// MapIndex is an index operation on a map at some index Key.
type MapIndex struct{ *mapIndex }
type mapIndex struct {
pathStep
key reflect.Value
}
_ PathStep = sliceIndex{}
_ PathStep = mapIndex{}
_ PathStep = typeAssertion{}
_ PathStep = structField{}
_ PathStep = indirect{}
_ PathStep = transform{}
)
func (mi MapIndex) Type() reflect.Type { return mi.typ }
func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy }
func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
// Key is the value of the map key.
func (mi MapIndex) Key() reflect.Value { return mi.key }
// Indirect represents pointer indirection on the parent type.
type Indirect struct{ *indirect }
type indirect struct {
pathStep
}
func (in Indirect) Type() reflect.Type { return in.typ }
func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy }
func (in Indirect) String() string { return "*" }
// TypeAssertion represents a type assertion on an interface.
type TypeAssertion struct{ *typeAssertion }
type typeAssertion struct {
pathStep
}
func (ta TypeAssertion) Type() reflect.Type { return ta.typ }
func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }
func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
// Transform is a transformation from the parent type to the current type.
type Transform struct{ *transform }
type transform struct {
pathStep
trans *transformer
}
func (tf Transform) Type() reflect.Type { return tf.typ }
func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy }
func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
// Name is the name of the Transformer.
func (tf Transform) Name() string { return tf.trans.name }
// Func is the function pointer to the transformer function.
func (tf Transform) Func() reflect.Value { return tf.trans.fnc }
// Option returns the originally constructed Transformer option.
// The == operator can be used to detect the exact option used.
func (tf Transform) Option() Option { return tf.trans }
// isExported reports whether the identifier is exported.
func isExported(id string) bool {
r, _ := utf8.DecodeRuneInString(id)
return unicode.IsUpper(r)
}
// isValid reports whether the identifier is valid.
// Empty and underscore-only strings are not valid.
func isValid(id string) bool {
ok := id != "" && id != "_"
for j, c := range id {
ok = ok && (j > 0 || !unicode.IsDigit(c))
ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c))
}
return ok
}

51
vendor/github.com/google/go-cmp/cmp/report.go generated vendored Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
// defaultReporter implements the reporter interface.
//
// As Equal serially calls the PushStep, Report, and PopStep methods, the
// defaultReporter constructs a tree-based representation of the compared value
// and the result of each comparison (see valueNode).
//
// When the String method is called, the FormatDiff method transforms the
// valueNode tree into a textNode tree, which is a tree-based representation
// of the textual output (see textNode).
//
// Lastly, the textNode.String method produces the final report as a string.
type defaultReporter struct {
root *valueNode
curr *valueNode
}
func (r *defaultReporter) PushStep(ps PathStep) {
r.curr = r.curr.PushStep(ps)
if r.root == nil {
r.root = r.curr
}
}
func (r *defaultReporter) Report(rs Result) {
r.curr.Report(rs)
}
func (r *defaultReporter) PopStep() {
r.curr = r.curr.PopStep()
}
// String provides a full report of the differences detected as a structured
// literal in pseudo-Go syntax. String may only be called after the entire tree
// has been traversed.
func (r *defaultReporter) String() string {
assert(r.root != nil && r.curr == nil)
if r.root.NumDiff == 0 {
return ""
}
return formatOptions{}.FormatDiff(r.root).String()
}
func assert(ok bool) {
if !ok {
panic("assertion failure")
}
}

296
vendor/github.com/google/go-cmp/cmp/report_compare.go generated vendored Normal file
View File

@ -0,0 +1,296 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"fmt"
"reflect"
"github.com/google/go-cmp/cmp/internal/value"
)
// TODO: Enforce limits?
// * Enforce maximum number of records to print per node?
// * Enforce maximum size in bytes allowed?
// * As a heuristic, use less verbosity for equal nodes than unequal nodes.
// TODO: Enforce unique outputs?
// * Avoid Stringer methods if it results in same output?
// * Print pointer address if outputs still equal?
// numContextRecords is the number of surrounding equal records to print.
const numContextRecords = 2
type diffMode byte
const (
diffUnknown diffMode = 0
diffIdentical diffMode = ' '
diffRemoved diffMode = '-'
diffInserted diffMode = '+'
)
type typeMode int
const (
// emitType always prints the type.
emitType typeMode = iota
// elideType never prints the type.
elideType
// autoType prints the type only for composite kinds
// (i.e., structs, slices, arrays, and maps).
autoType
)
type formatOptions struct {
// DiffMode controls the output mode of FormatDiff.
//
// If diffUnknown, then produce a diff of the x and y values.
// If diffIdentical, then emit values as if they were equal.
// If diffRemoved, then only emit x values (ignoring y values).
// If diffInserted, then only emit y values (ignoring x values).
DiffMode diffMode
// TypeMode controls whether to print the type for the current node.
//
// As a general rule of thumb, we always print the type of the next node
// after an interface, and always elide the type of the next node after
// a slice or map node.
TypeMode typeMode
// formatValueOptions are options specific to printing reflect.Values.
formatValueOptions
}
func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
opts.DiffMode = d
return opts
}
func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
opts.TypeMode = t
return opts
}
// FormatDiff converts a valueNode tree into a textNode tree, where the later
// is a textual representation of the differences detected in the former.
func (opts formatOptions) FormatDiff(v *valueNode) textNode {
// Check whether we have specialized formatting for this node.
// This is not necessary, but helpful for producing more readable outputs.
if opts.CanFormatDiffSlice(v) {
return opts.FormatDiffSlice(v)
}
// For leaf nodes, format the value based on the reflect.Values alone.
if v.MaxDepth == 0 {
switch opts.DiffMode {
case diffUnknown, diffIdentical:
// Format Equal.
if v.NumDiff == 0 {
outx := opts.FormatValue(v.ValueX, visitedPointers{})
outy := opts.FormatValue(v.ValueY, visitedPointers{})
if v.NumIgnored > 0 && v.NumSame == 0 {
return textEllipsis
} else if outx.Len() < outy.Len() {
return outx
} else {
return outy
}
}
// Format unequal.
assert(opts.DiffMode == diffUnknown)
var list textList
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, visitedPointers{})
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, visitedPointers{})
if outx != nil {
list = append(list, textRecord{Diff: '-', Value: outx})
}
if outy != nil {
list = append(list, textRecord{Diff: '+', Value: outy})
}
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
case diffRemoved:
return opts.FormatValue(v.ValueX, visitedPointers{})
case diffInserted:
return opts.FormatValue(v.ValueY, visitedPointers{})
default:
panic("invalid diff mode")
}
}
// Descend into the child value node.
if v.TransformerName != "" {
out := opts.WithTypeMode(emitType).FormatDiff(v.Value)
out = textWrap{"Inverse(" + v.TransformerName + ", ", out, ")"}
return opts.FormatType(v.Type, out)
} else {
switch k := v.Type.Kind(); k {
case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map:
return opts.FormatType(v.Type, opts.formatDiffList(v.Records, k))
case reflect.Ptr:
return textWrap{"&", opts.FormatDiff(v.Value), ""}
case reflect.Interface:
return opts.WithTypeMode(emitType).FormatDiff(v.Value)
default:
panic(fmt.Sprintf("%v cannot have children", k))
}
}
}
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) textNode {
// Derive record name based on the data structure kind.
var name string
var formatKey func(reflect.Value) string
switch k {
case reflect.Struct:
name = "field"
opts = opts.WithTypeMode(autoType)
formatKey = func(v reflect.Value) string { return v.String() }
case reflect.Slice, reflect.Array:
name = "element"
opts = opts.WithTypeMode(elideType)
formatKey = func(reflect.Value) string { return "" }
case reflect.Map:
name = "entry"
opts = opts.WithTypeMode(elideType)
formatKey = formatMapKey
}
// Handle unification.
switch opts.DiffMode {
case diffIdentical, diffRemoved, diffInserted:
var list textList
var deferredEllipsis bool // Add final "..." to indicate records were dropped
for _, r := range recs {
// Elide struct fields that are zero value.
if k == reflect.Struct {
var isZero bool
switch opts.DiffMode {
case diffIdentical:
isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueX)
case diffRemoved:
isZero = value.IsZero(r.Value.ValueX)
case diffInserted:
isZero = value.IsZero(r.Value.ValueY)
}
if isZero {
continue
}
}
// Elide ignored nodes.
if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
if !deferredEllipsis {
list.AppendEllipsis(diffStats{})
}
continue
}
if out := opts.FormatDiff(r.Value); out != nil {
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
}
}
if deferredEllipsis {
list.AppendEllipsis(diffStats{})
}
return textWrap{"{", list, "}"}
case diffUnknown:
default:
panic("invalid diff mode")
}
// Handle differencing.
var list textList
groups := coalesceAdjacentRecords(name, recs)
for i, ds := range groups {
// Handle equal records.
if ds.NumDiff() == 0 {
// Compute the number of leading and trailing records to print.
var numLo, numHi int
numEqual := ds.NumIgnored + ds.NumIdentical
for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
break
}
numLo++
}
for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
break
}
numHi++
}
if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
numHi++ // Avoid pointless coalescing of a single equal record
}
// Format the equal values.
for _, r := range recs[:numLo] {
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
}
if numEqual > numLo+numHi {
ds.NumIdentical -= numLo + numHi
list.AppendEllipsis(ds)
}
for _, r := range recs[numEqual-numHi : numEqual] {
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
}
recs = recs[numEqual:]
continue
}
// Handle unequal records.
for _, r := range recs[:ds.NumDiff()] {
switch {
case opts.CanFormatDiffSlice(r.Value):
out := opts.FormatDiffSlice(r.Value)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
case r.Value.NumChildren == r.Value.MaxDepth:
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value)
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value)
if outx != nil {
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
}
if outy != nil {
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
}
default:
out := opts.FormatDiff(r.Value)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
}
}
recs = recs[ds.NumDiff():]
}
assert(len(recs) == 0)
return textWrap{"{", list, "}"}
}
// coalesceAdjacentRecords coalesces the list of records into groups of
// adjacent equal, or unequal counts.
func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
var prevCase int // Arbitrary index into which case last occurred
lastStats := func(i int) *diffStats {
if prevCase != i {
groups = append(groups, diffStats{Name: name})
prevCase = i
}
return &groups[len(groups)-1]
}
for _, r := range recs {
switch rv := r.Value; {
case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
lastStats(1).NumIgnored++
case rv.NumDiff == 0:
lastStats(1).NumIdentical++
case rv.NumDiff > 0 && !rv.ValueY.IsValid():
lastStats(2).NumRemoved++
case rv.NumDiff > 0 && !rv.ValueX.IsValid():
lastStats(2).NumInserted++
default:
lastStats(2).NumModified++
}
}
return groups
}

279
vendor/github.com/google/go-cmp/cmp/report_reflect.go generated vendored Normal file
View File

@ -0,0 +1,279 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"fmt"
"reflect"
"strconv"
"strings"
"unicode"
"github.com/google/go-cmp/cmp/internal/flags"
"github.com/google/go-cmp/cmp/internal/value"
)
type formatValueOptions struct {
// AvoidStringer controls whether to avoid calling custom stringer
// methods like error.Error or fmt.Stringer.String.
AvoidStringer bool
// ShallowPointers controls whether to avoid descending into pointers.
// Useful when printing map keys, where pointer comparison is performed
// on the pointer address rather than the pointed-at value.
ShallowPointers bool
// PrintAddresses controls whether to print the address of all pointers,
// slice elements, and maps.
PrintAddresses bool
}
// FormatType prints the type as if it were wrapping s.
// This may return s as-is depending on the current type and TypeMode mode.
func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
// Check whether to emit the type or not.
switch opts.TypeMode {
case autoType:
switch t.Kind() {
case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
if s.Equal(textNil) {
return s
}
default:
return s
}
case elideType:
return s
}
// Determine the type label, applying special handling for unnamed types.
typeName := t.String()
if t.Name() == "" {
// According to Go grammar, certain type literals contain symbols that
// do not strongly bind to the next lexicographical token (e.g., *T).
switch t.Kind() {
case reflect.Chan, reflect.Func, reflect.Ptr:
typeName = "(" + typeName + ")"
}
typeName = strings.Replace(typeName, "struct {", "struct{", -1)
typeName = strings.Replace(typeName, "interface {", "interface{", -1)
}
// Avoid wrap the value in parenthesis if unnecessary.
if s, ok := s.(textWrap); ok {
hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")")
hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}")
if hasParens || hasBraces {
return textWrap{typeName, s, ""}
}
}
return textWrap{typeName + "(", s, ")"}
}
// FormatValue prints the reflect.Value, taking extra care to avoid descending
// into pointers already in m. As pointers are visited, m is also updated.
func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) {
if !v.IsValid() {
return nil
}
t := v.Type()
// Check whether there is an Error or String method to call.
if !opts.AvoidStringer && v.CanInterface() {
// Avoid calling Error or String methods on nil receivers since many
// implementations crash when doing so.
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
switch v := v.Interface().(type) {
case error:
return textLine("e" + formatString(v.Error()))
case fmt.Stringer:
return textLine("s" + formatString(v.String()))
}
}
}
// Check whether to explicitly wrap the result with the type.
var skipType bool
defer func() {
if !skipType {
out = opts.FormatType(t, out)
}
}()
var ptr string
switch t.Kind() {
case reflect.Bool:
return textLine(fmt.Sprint(v.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return textLine(fmt.Sprint(v.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
// Unnamed uints are usually bytes or words, so use hexadecimal.
if t.PkgPath() == "" || t.Kind() == reflect.Uintptr {
return textLine(formatHex(v.Uint()))
}
return textLine(fmt.Sprint(v.Uint()))
case reflect.Float32, reflect.Float64:
return textLine(fmt.Sprint(v.Float()))
case reflect.Complex64, reflect.Complex128:
return textLine(fmt.Sprint(v.Complex()))
case reflect.String:
return textLine(formatString(v.String()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
return textLine(formatPointer(v))
case reflect.Struct:
var list textList
for i := 0; i < v.NumField(); i++ {
vv := v.Field(i)
if value.IsZero(vv) {
continue // Elide fields with zero values
}
s := opts.WithTypeMode(autoType).FormatValue(vv, m)
list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
}
return textWrap{"{", list, "}"}
case reflect.Slice:
if v.IsNil() {
return textNil
}
if opts.PrintAddresses {
ptr = formatPointer(v)
}
fallthrough
case reflect.Array:
var list textList
for i := 0; i < v.Len(); i++ {
vi := v.Index(i)
if vi.CanAddr() { // Check for cyclic elements
p := vi.Addr()
if m.Visit(p) {
var out textNode
out = textLine(formatPointer(p))
out = opts.WithTypeMode(emitType).FormatType(p.Type(), out)
out = textWrap{"*", out, ""}
list = append(list, textRecord{Value: out})
continue
}
}
s := opts.WithTypeMode(elideType).FormatValue(vi, m)
list = append(list, textRecord{Value: s})
}
return textWrap{ptr + "{", list, "}"}
case reflect.Map:
if v.IsNil() {
return textNil
}
if m.Visit(v) {
return textLine(formatPointer(v))
}
var list textList
for _, k := range value.SortKeys(v.MapKeys()) {
sk := formatMapKey(k)
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m)
list = append(list, textRecord{Key: sk, Value: sv})
}
if opts.PrintAddresses {
ptr = formatPointer(v)
}
return textWrap{ptr + "{", list, "}"}
case reflect.Ptr:
if v.IsNil() {
return textNil
}
if m.Visit(v) || opts.ShallowPointers {
return textLine(formatPointer(v))
}
if opts.PrintAddresses {
ptr = formatPointer(v)
}
skipType = true // Let the underlying value print the type instead
return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""}
case reflect.Interface:
if v.IsNil() {
return textNil
}
// Interfaces accept different concrete types,
// so configure the underlying value to explicitly print the type.
skipType = true // Print the concrete type instead
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m)
default:
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
}
}
// formatMapKey formats v as if it were a map key.
// The result is guaranteed to be a single line.
func formatMapKey(v reflect.Value) string {
var opts formatOptions
opts.TypeMode = elideType
opts.AvoidStringer = true
opts.ShallowPointers = true
s := opts.FormatValue(v, visitedPointers{}).String()
return strings.TrimSpace(s)
}
// formatString prints s as a double-quoted or backtick-quoted string.
func formatString(s string) string {
// Use quoted string if it the same length as a raw string literal.
// Otherwise, attempt to use the raw string form.
qs := strconv.Quote(s)
if len(qs) == 1+len(s)+1 {
return qs
}
// Disallow newlines to ensure output is a single line.
// Only allow printable runes for readability purposes.
rawInvalid := func(r rune) bool {
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
}
if strings.IndexFunc(s, rawInvalid) < 0 {
return "`" + s + "`"
}
return qs
}
// formatHex prints u as a hexadecimal integer in Go notation.
func formatHex(u uint64) string {
var f string
switch {
case u <= 0xff:
f = "0x%02x"
case u <= 0xffff:
f = "0x%04x"
case u <= 0xffffff:
f = "0x%06x"
case u <= 0xffffffff:
f = "0x%08x"
case u <= 0xffffffffff:
f = "0x%010x"
case u <= 0xffffffffffff:
f = "0x%012x"
case u <= 0xffffffffffffff:
f = "0x%014x"
case u <= 0xffffffffffffffff:
f = "0x%016x"
}
return fmt.Sprintf(f, u)
}
// formatPointer prints the address of the pointer.
func formatPointer(v reflect.Value) string {
p := v.Pointer()
if flags.Deterministic {
p = 0xdeadf00f // Only used for stable testing purposes
}
return fmt.Sprintf("⟪0x%x⟫", p)
}
type visitedPointers map[value.Pointer]struct{}
// Visit inserts pointer v into the visited map and reports whether it had
// already been visited before.
func (m visitedPointers) Visit(v reflect.Value) bool {
p := value.PointerOf(v)
_, visited := m[p]
m[p] = struct{}{}
return visited
}

333
vendor/github.com/google/go-cmp/cmp/report_slices.go generated vendored Normal file
View File

@ -0,0 +1,333 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"bytes"
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
"github.com/google/go-cmp/cmp/internal/diff"
)
// CanFormatDiffSlice reports whether we support custom formatting for nodes
// that are slices of primitive kinds or strings.
func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
switch {
case opts.DiffMode != diffUnknown:
return false // Must be formatting in diff mode
case v.NumDiff == 0:
return false // No differences detected
case v.NumIgnored+v.NumCompared+v.NumTransformed > 0:
// TODO: Handle the case where someone uses bytes.Equal on a large slice.
return false // Some custom option was used to determined equality
case !v.ValueX.IsValid() || !v.ValueY.IsValid():
return false // Both values must be valid
}
switch t := v.Type; t.Kind() {
case reflect.String:
case reflect.Array, reflect.Slice:
// Only slices of primitive types have specialized handling.
switch t.Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
default:
return false
}
// If a sufficient number of elements already differ,
// use specialized formatting even if length requirement is not met.
if v.NumDiff > v.NumSame {
return true
}
default:
return false
}
// Use specialized string diffing for longer slices or strings.
const minLength = 64
return v.ValueX.Len() >= minLength && v.ValueY.Len() >= minLength
}
// FormatDiffSlice prints a diff for the slices (or strings) represented by v.
// This provides custom-tailored logic to make printing of differences in
// textual strings and slices of primitive kinds more readable.
func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
assert(opts.DiffMode == diffUnknown)
t, vx, vy := v.Type, v.ValueX, v.ValueY
// Auto-detect the type of the data.
var isLinedText, isText, isBinary bool
var sx, sy string
switch {
case t.Kind() == reflect.String:
sx, sy = vx.String(), vy.String()
isText = true // Initial estimate, verify later
case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)):
sx, sy = string(vx.Bytes()), string(vy.Bytes())
isBinary = true // Initial estimate, verify later
case t.Kind() == reflect.Array:
// Arrays need to be addressable for slice operations to work.
vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem()
vx2.Set(vx)
vy2.Set(vy)
vx, vy = vx2, vy2
}
if isText || isBinary {
var numLines, lastLineIdx, maxLineLen int
isBinary = false
for i, r := range sx + sy {
if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError {
isBinary = true
break
}
if r == '\n' {
if maxLineLen < i-lastLineIdx {
lastLineIdx = i - lastLineIdx
}
lastLineIdx = i + 1
numLines++
}
}
isText = !isBinary
isLinedText = isText && numLines >= 4 && maxLineLen <= 256
}
// Format the string into printable records.
var list textList
var delim string
switch {
// If the text appears to be multi-lined text,
// then perform differencing across individual lines.
case isLinedText:
ssx := strings.Split(sx, "\n")
ssy := strings.Split(sy, "\n")
list = opts.formatDiffSlice(
reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line",
func(v reflect.Value, d diffMode) textRecord {
s := formatString(v.Index(0).String())
return textRecord{Diff: d, Value: textLine(s)}
},
)
delim = "\n"
// If the text appears to be single-lined text,
// then perform differencing in approximately fixed-sized chunks.
// The output is printed as quoted strings.
case isText:
list = opts.formatDiffSlice(
reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte",
func(v reflect.Value, d diffMode) textRecord {
s := formatString(v.String())
return textRecord{Diff: d, Value: textLine(s)}
},
)
delim = ""
// If the text appears to be binary data,
// then perform differencing in approximately fixed-sized chunks.
// The output is inspired by hexdump.
case isBinary:
list = opts.formatDiffSlice(
reflect.ValueOf(sx), reflect.ValueOf(sy), 16, "byte",
func(v reflect.Value, d diffMode) textRecord {
var ss []string
for i := 0; i < v.Len(); i++ {
ss = append(ss, formatHex(v.Index(i).Uint()))
}
s := strings.Join(ss, ", ")
comment := commentString(fmt.Sprintf("%c|%v|", d, formatASCII(v.String())))
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
},
)
// For all other slices of primitive types,
// then perform differencing in approximately fixed-sized chunks.
// The size of each chunk depends on the width of the element kind.
default:
var chunkSize int
if t.Elem().Kind() == reflect.Bool {
chunkSize = 16
} else {
switch t.Elem().Bits() {
case 8:
chunkSize = 16
case 16:
chunkSize = 12
case 32:
chunkSize = 8
default:
chunkSize = 8
}
}
list = opts.formatDiffSlice(
vx, vy, chunkSize, t.Elem().Kind().String(),
func(v reflect.Value, d diffMode) textRecord {
var ss []string
for i := 0; i < v.Len(); i++ {
switch t.Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
ss = append(ss, fmt.Sprint(v.Index(i).Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
ss = append(ss, formatHex(v.Index(i).Uint()))
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
ss = append(ss, fmt.Sprint(v.Index(i).Interface()))
}
}
s := strings.Join(ss, ", ")
return textRecord{Diff: d, Value: textLine(s)}
},
)
}
// Wrap the output with appropriate type information.
var out textNode = textWrap{"{", list, "}"}
if !isText {
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
// Emit the type for extra clarity (e.g. "string{...}").
if t.Kind() == reflect.String {
opts = opts.WithTypeMode(emitType)
}
return opts.FormatType(t, out)
}
switch t.Kind() {
case reflect.String:
out = textWrap{"strings.Join(", out, fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf(string("")) {
out = opts.FormatType(t, out)
}
case reflect.Slice:
out = textWrap{"bytes.Join(", out, fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf([]byte(nil)) {
out = opts.FormatType(t, out)
}
}
return out
}
// formatASCII formats s as an ASCII string.
// This is useful for printing binary strings in a semi-legible way.
func formatASCII(s string) string {
b := bytes.Repeat([]byte{'.'}, len(s))
for i := 0; i < len(s); i++ {
if ' ' <= s[i] && s[i] <= '~' {
b[i] = s[i]
}
}
return string(b)
}
func (opts formatOptions) formatDiffSlice(
vx, vy reflect.Value, chunkSize int, name string,
makeRec func(reflect.Value, diffMode) textRecord,
) (list textList) {
es := diff.Difference(vx.Len(), vy.Len(), func(ix int, iy int) diff.Result {
return diff.BoolResult(vx.Index(ix).Interface() == vy.Index(iy).Interface())
})
appendChunks := func(v reflect.Value, d diffMode) int {
n0 := v.Len()
for v.Len() > 0 {
n := chunkSize
if n > v.Len() {
n = v.Len()
}
list = append(list, makeRec(v.Slice(0, n), d))
v = v.Slice(n, v.Len())
}
return n0 - v.Len()
}
groups := coalesceAdjacentEdits(name, es)
groups = coalesceInterveningIdentical(groups, chunkSize/4)
for i, ds := range groups {
// Print equal.
if ds.NumDiff() == 0 {
// Compute the number of leading and trailing equal bytes to print.
var numLo, numHi int
numEqual := ds.NumIgnored + ds.NumIdentical
for numLo < chunkSize*numContextRecords && numLo+numHi < numEqual && i != 0 {
numLo++
}
for numHi < chunkSize*numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
numHi++
}
if numEqual-(numLo+numHi) <= chunkSize && ds.NumIgnored == 0 {
numHi = numEqual - numLo // Avoid pointless coalescing of single equal row
}
// Print the equal bytes.
appendChunks(vx.Slice(0, numLo), diffIdentical)
if numEqual > numLo+numHi {
ds.NumIdentical -= numLo + numHi
list.AppendEllipsis(ds)
}
appendChunks(vx.Slice(numEqual-numHi, numEqual), diffIdentical)
vx = vx.Slice(numEqual, vx.Len())
vy = vy.Slice(numEqual, vy.Len())
continue
}
// Print unequal.
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
vx = vx.Slice(nx, vx.Len())
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
vy = vy.Slice(ny, vy.Len())
}
assert(vx.Len() == 0 && vy.Len() == 0)
return list
}
// coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
// equal or unequal counts.
func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) {
var prevCase int // Arbitrary index into which case last occurred
lastStats := func(i int) *diffStats {
if prevCase != i {
groups = append(groups, diffStats{Name: name})
prevCase = i
}
return &groups[len(groups)-1]
}
for _, e := range es {
switch e {
case diff.Identity:
lastStats(1).NumIdentical++
case diff.UniqueX:
lastStats(2).NumRemoved++
case diff.UniqueY:
lastStats(2).NumInserted++
case diff.Modified:
lastStats(2).NumModified++
}
}
return groups
}
// coalesceInterveningIdentical coalesces sufficiently short (<= windowSize)
// equal groups into adjacent unequal groups that currently result in a
// dual inserted/removed printout. This acts as a high-pass filter to smooth
// out high-frequency changes within the windowSize.
func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats {
groups, groupsOrig := groups[:0], groups
for i, ds := range groupsOrig {
if len(groups) >= 2 && ds.NumDiff() > 0 {
prev := &groups[len(groups)-2] // Unequal group
curr := &groups[len(groups)-1] // Equal group
next := &groupsOrig[i] // Unequal group
hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0
hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0
if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize {
*prev = (*prev).Append(*curr).Append(*next)
groups = groups[:len(groups)-1] // Truncate off equal group
continue
}
}
groups = append(groups, ds)
}
return groups
}

382
vendor/github.com/google/go-cmp/cmp/report_text.go generated vendored Normal file
View File

@ -0,0 +1,382 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"bytes"
"fmt"
"math/rand"
"strings"
"time"
"github.com/google/go-cmp/cmp/internal/flags"
)
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
type indentMode int
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
if flags.Deterministic || randBool {
// Use regular spaces (U+0020).
switch d {
case diffUnknown, diffIdentical:
b = append(b, " "...)
case diffRemoved:
b = append(b, "- "...)
case diffInserted:
b = append(b, "+ "...)
}
} else {
// Use non-breaking spaces (U+00a0).
switch d {
case diffUnknown, diffIdentical:
b = append(b, "  "...)
case diffRemoved:
b = append(b, "- "...)
case diffInserted:
b = append(b, "+ "...)
}
}
return repeatCount(n).appendChar(b, '\t')
}
type repeatCount int
func (n repeatCount) appendChar(b []byte, c byte) []byte {
for ; n > 0; n-- {
b = append(b, c)
}
return b
}
// textNode is a simplified tree-based representation of structured text.
// Possible node types are textWrap, textList, or textLine.
type textNode interface {
// Len reports the length in bytes of a single-line version of the tree.
// Nested textRecord.Diff and textRecord.Comment fields are ignored.
Len() int
// Equal reports whether the two trees are structurally identical.
// Nested textRecord.Diff and textRecord.Comment fields are compared.
Equal(textNode) bool
// String returns the string representation of the text tree.
// It is not guaranteed that len(x.String()) == x.Len(),
// nor that x.String() == y.String() implies that x.Equal(y).
String() string
// formatCompactTo formats the contents of the tree as a single-line string
// to the provided buffer. Any nested textRecord.Diff and textRecord.Comment
// fields are ignored.
//
// However, not all nodes in the tree should be collapsed as a single-line.
// If a node can be collapsed as a single-line, it is replaced by a textLine
// node. Since the top-level node cannot replace itself, this also returns
// the current node itself.
//
// This does not mutate the receiver.
formatCompactTo([]byte, diffMode) ([]byte, textNode)
// formatExpandedTo formats the contents of the tree as a multi-line string
// to the provided buffer. In order for column alignment to operate well,
// formatCompactTo must be called before calling formatExpandedTo.
formatExpandedTo([]byte, diffMode, indentMode) []byte
}
// textWrap is a wrapper that concatenates a prefix and/or a suffix
// to the underlying node.
type textWrap struct {
Prefix string // e.g., "bytes.Buffer{"
Value textNode // textWrap | textList | textLine
Suffix string // e.g., "}"
}
func (s textWrap) Len() int {
return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
}
func (s1 textWrap) Equal(s2 textNode) bool {
if s2, ok := s2.(textWrap); ok {
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
}
return false
}
func (s textWrap) String() string {
var d diffMode
var n indentMode
_, s2 := s.formatCompactTo(nil, d)
b := n.appendIndent(nil, d) // Leading indent
b = s2.formatExpandedTo(b, d, n) // Main body
b = append(b, '\n') // Trailing newline
return string(b)
}
func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
n0 := len(b) // Original buffer length
b = append(b, s.Prefix...)
b, s.Value = s.Value.formatCompactTo(b, d)
b = append(b, s.Suffix...)
if _, ok := s.Value.(textLine); ok {
return b, textLine(b[n0:])
}
return b, s
}
func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
b = append(b, s.Prefix...)
b = s.Value.formatExpandedTo(b, d, n)
b = append(b, s.Suffix...)
return b
}
// textList is a comma-separated list of textWrap or textLine nodes.
// The list may be formatted as multi-lines or single-line at the discretion
// of the textList.formatCompactTo method.
type textList []textRecord
type textRecord struct {
Diff diffMode // e.g., 0 or '-' or '+'
Key string // e.g., "MyField"
Value textNode // textWrap | textLine
Comment fmt.Stringer // e.g., "6 identical fields"
}
// AppendEllipsis appends a new ellipsis node to the list if none already
// exists at the end. If cs is non-zero it coalesces the statistics with the
// previous diffStats.
func (s *textList) AppendEllipsis(ds diffStats) {
hasStats := ds != diffStats{}
if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
if hasStats {
*s = append(*s, textRecord{Value: textEllipsis, Comment: ds})
} else {
*s = append(*s, textRecord{Value: textEllipsis})
}
return
}
if hasStats {
(*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds)
}
}
func (s textList) Len() (n int) {
for i, r := range s {
n += len(r.Key)
if r.Key != "" {
n += len(": ")
}
n += r.Value.Len()
if i < len(s)-1 {
n += len(", ")
}
}
return n
}
func (s1 textList) Equal(s2 textNode) bool {
if s2, ok := s2.(textList); ok {
if len(s1) != len(s2) {
return false
}
for i := range s1 {
r1, r2 := s1[i], s2[i]
if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) {
return false
}
}
return true
}
return false
}
func (s textList) String() string {
return textWrap{"{", s, "}"}.String()
}
func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
s = append(textList(nil), s...) // Avoid mutating original
// Determine whether we can collapse this list as a single line.
n0 := len(b) // Original buffer length
var multiLine bool
for i, r := range s {
if r.Diff == diffInserted || r.Diff == diffRemoved {
multiLine = true
}
b = append(b, r.Key...)
if r.Key != "" {
b = append(b, ": "...)
}
b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff)
if _, ok := s[i].Value.(textLine); !ok {
multiLine = true
}
if r.Comment != nil {
multiLine = true
}
if i < len(s)-1 {
b = append(b, ", "...)
}
}
// Force multi-lined output when printing a removed/inserted node that
// is sufficiently long.
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > 80 {
multiLine = true
}
if !multiLine {
return b, textLine(b[n0:])
}
return b, s
}
func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
alignKeyLens := s.alignLens(
func(r textRecord) bool {
_, isLine := r.Value.(textLine)
return r.Key == "" || !isLine
},
func(r textRecord) int { return len(r.Key) },
)
alignValueLens := s.alignLens(
func(r textRecord) bool {
_, isLine := r.Value.(textLine)
return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
},
func(r textRecord) int { return len(r.Value.(textLine)) },
)
// Format the list as a multi-lined output.
n++
for i, r := range s {
b = n.appendIndent(append(b, '\n'), d|r.Diff)
if r.Key != "" {
b = append(b, r.Key+": "...)
}
b = alignKeyLens[i].appendChar(b, ' ')
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
if !r.Value.Equal(textEllipsis) {
b = append(b, ',')
}
b = alignValueLens[i].appendChar(b, ' ')
if r.Comment != nil {
b = append(b, " // "+r.Comment.String()...)
}
}
n--
return n.appendIndent(append(b, '\n'), d)
}
func (s textList) alignLens(
skipFunc func(textRecord) bool,
lenFunc func(textRecord) int,
) []repeatCount {
var startIdx, endIdx, maxLen int
lens := make([]repeatCount, len(s))
for i, r := range s {
if skipFunc(r) {
for j := startIdx; j < endIdx && j < len(s); j++ {
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
}
startIdx, endIdx, maxLen = i+1, i+1, 0
} else {
if maxLen < lenFunc(r) {
maxLen = lenFunc(r)
}
endIdx = i + 1
}
}
for j := startIdx; j < endIdx && j < len(s); j++ {
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
}
return lens
}
// textLine is a single-line segment of text and is always a leaf node
// in the textNode tree.
type textLine []byte
var (
textNil = textLine("nil")
textEllipsis = textLine("...")
)
func (s textLine) Len() int {
return len(s)
}
func (s1 textLine) Equal(s2 textNode) bool {
if s2, ok := s2.(textLine); ok {
return bytes.Equal([]byte(s1), []byte(s2))
}
return false
}
func (s textLine) String() string {
return string(s)
}
func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
return append(b, s...), s
}
func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte {
return append(b, s...)
}
type diffStats struct {
Name string
NumIgnored int
NumIdentical int
NumRemoved int
NumInserted int
NumModified int
}
func (s diffStats) NumDiff() int {
return s.NumRemoved + s.NumInserted + s.NumModified
}
func (s diffStats) Append(ds diffStats) diffStats {
assert(s.Name == ds.Name)
s.NumIgnored += ds.NumIgnored
s.NumIdentical += ds.NumIdentical
s.NumRemoved += ds.NumRemoved
s.NumInserted += ds.NumInserted
s.NumModified += ds.NumModified
return s
}
// String prints a humanly-readable summary of coalesced records.
//
// Example:
// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
func (s diffStats) String() string {
var ss []string
var sum int
labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"}
counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified}
for i, n := range counts {
if n > 0 {
ss = append(ss, fmt.Sprintf("%d %v", n, labels[i]))
}
sum += n
}
// Pluralize the name (adjusting for some obscure English grammar rules).
name := s.Name
if sum > 1 {
name = name + "s"
if strings.HasSuffix(name, "ys") {
name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries"
}
}
// Format the list according to English grammar (with Oxford comma).
switch n := len(ss); n {
case 0:
return ""
case 1, 2:
return strings.Join(ss, " and ") + " " + name
default:
return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name
}
}
type commentString string
func (s commentString) String() string { return string(s) }

121
vendor/github.com/google/go-cmp/cmp/report_value.go generated vendored Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import "reflect"
// valueNode represents a single node within a report, which is a
// structured representation of the value tree, containing information
// regarding which nodes are equal or not.
type valueNode struct {
parent *valueNode
Type reflect.Type
ValueX reflect.Value
ValueY reflect.Value
// NumSame is the number of leaf nodes that are equal.
// All descendants are equal only if NumDiff is 0.
NumSame int
// NumDiff is the number of leaf nodes that are not equal.
NumDiff int
// NumIgnored is the number of leaf nodes that are ignored.
NumIgnored int
// NumCompared is the number of leaf nodes that were compared
// using an Equal method or Comparer function.
NumCompared int
// NumTransformed is the number of non-leaf nodes that were transformed.
NumTransformed int
// NumChildren is the number of transitive descendants of this node.
// This counts from zero; thus, leaf nodes have no descendants.
NumChildren int
// MaxDepth is the maximum depth of the tree. This counts from zero;
// thus, leaf nodes have a depth of zero.
MaxDepth int
// Records is a list of struct fields, slice elements, or map entries.
Records []reportRecord // If populated, implies Value is not populated
// Value is the result of a transformation, pointer indirect, of
// type assertion.
Value *valueNode // If populated, implies Records is not populated
// TransformerName is the name of the transformer.
TransformerName string // If non-empty, implies Value is populated
}
type reportRecord struct {
Key reflect.Value // Invalid for slice element
Value *valueNode
}
func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) {
vx, vy := ps.Values()
child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy}
switch s := ps.(type) {
case StructField:
assert(parent.Value == nil)
parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child})
case SliceIndex:
assert(parent.Value == nil)
parent.Records = append(parent.Records, reportRecord{Value: child})
case MapIndex:
assert(parent.Value == nil)
parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child})
case Indirect:
assert(parent.Value == nil && parent.Records == nil)
parent.Value = child
case TypeAssertion:
assert(parent.Value == nil && parent.Records == nil)
parent.Value = child
case Transform:
assert(parent.Value == nil && parent.Records == nil)
parent.Value = child
parent.TransformerName = s.Name()
parent.NumTransformed++
default:
assert(parent == nil) // Must be the root step
}
return child
}
func (r *valueNode) Report(rs Result) {
assert(r.MaxDepth == 0) // May only be called on leaf nodes
if rs.ByIgnore() {
r.NumIgnored++
} else {
if rs.Equal() {
r.NumSame++
} else {
r.NumDiff++
}
}
assert(r.NumSame+r.NumDiff+r.NumIgnored == 1)
if rs.ByMethod() {
r.NumCompared++
}
if rs.ByFunc() {
r.NumCompared++
}
assert(r.NumCompared <= 1)
}
func (child *valueNode) PopStep() (parent *valueNode) {
if child.parent == nil {
return nil
}
parent = child.parent
parent.NumSame += child.NumSame
parent.NumDiff += child.NumDiff
parent.NumIgnored += child.NumIgnored
parent.NumCompared += child.NumCompared
parent.NumTransformed += child.NumTransformed
parent.NumChildren += child.NumChildren + 1
if parent.MaxDepth < child.MaxDepth+1 {
parent.MaxDepth = child.MaxDepth + 1
}
return parent
}

View File

@ -1,53 +0,0 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp/internal/value"
)
type defaultReporter struct {
Option
diffs []string // List of differences, possibly truncated
ndiffs int // Total number of differences
nbytes int // Number of bytes in diffs
nlines int // Number of lines in diffs
}
var _ reporter = (*defaultReporter)(nil)
func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
if eq {
return // Ignore equal results
}
const maxBytes = 4096
const maxLines = 256
r.ndiffs++
if r.nbytes < maxBytes && r.nlines < maxLines {
sx := value.Format(x, value.FormatConfig{UseStringer: true})
sy := value.Format(y, value.FormatConfig{UseStringer: true})
if sx == sy {
// Unhelpful output, so use more exact formatting.
sx = value.Format(x, value.FormatConfig{PrintPrimitiveType: true})
sy = value.Format(y, value.FormatConfig{PrintPrimitiveType: true})
}
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
r.diffs = append(r.diffs, s)
r.nbytes += len(s)
r.nlines += strings.Count(s, "\n")
}
}
func (r *defaultReporter) String() string {
s := strings.Join(r.diffs, "")
if r.ndiffs == len(r.diffs) {
return s
}
return fmt.Sprintf("%s... %d more differences ...", s, r.ndiffs-len(r.diffs))
}

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package name defines structured types for representing image references.
package name
import (

View File

@ -0,0 +1,42 @@
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name defines structured types for representing image references.
//
// What's in a name? For image references, not nearly enough!
//
// Image references look a lot like URLs, but they differ in that they don't
// contain the scheme (http or https), they can end with a :tag or a @digest
// (the latter being validated), and they perform defaulting for missing
// components.
//
// Since image references don't contain the scheme, we do our best to infer
// if we use http or https from the given hostname. We allow http fallback for
// any host that looks like localhost (localhost, 127.0.0.1, ::1), ends in
// ".local", or is in the "private" address space per RFC 1918. For everything
// else, we assume https only. To override this heuristic, use the Insecure
// option.
//
// Image references with a digest signal to us that we should verify the content
// of the image matches the digest. E.g. when pulling a Digest reference, we'll
// calculate the sha256 of the manifest returned by the registry and error out
// if it doesn't match what we asked for.
//
// For defaulting, we interpret "ubuntu" as
// "index.docker.io/library/ubuntu:latest" because we add the missing repo
// "library", the missing registry "index.docker.io", and the missing tag
// "latest". To disable this defaulting, use the StrictValidation option. This
// is useful e.g. to only allow image references that explicitly set a tag or
// digest, so that you don't accidentally pull "latest".
package name

335
vendor/github.com/knative/pkg/apis/condition_set.go generated vendored Normal file
View File

@ -0,0 +1,335 @@
/*
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 apis
import (
"reflect"
"sort"
"time"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Conditions is the interface for a Resource that implements the getter and
// setter for accessing a Condition collection.
// +k8s:deepcopy-gen=true
type ConditionsAccessor interface {
GetConditions() Conditions
SetConditions(Conditions)
}
// ConditionSet is an abstract collection of the possible ConditionType values
// that a particular resource might expose. It also holds the "happy condition"
// for that resource, which we define to be one of Ready or Succeeded depending
// on whether it is a Living or Batch process respectively.
// +k8s:deepcopy-gen=false
type ConditionSet struct {
happy ConditionType
dependents []ConditionType
}
// ConditionManager allows a resource to operate on its Conditions using higher
// order operations.
type ConditionManager interface {
// IsHappy looks at the happy condition and returns true if that condition is
// set to true.
IsHappy() bool
// GetCondition finds and returns the Condition that matches the ConditionType
// previously set on Conditions.
GetCondition(t ConditionType) *Condition
// SetCondition sets or updates the Condition on Conditions for Condition.Type.
// If there is an update, Conditions are stored back sorted.
SetCondition(new Condition)
// MarkTrue sets the status of t to true, and then marks the happy condition to
// true if all dependents are true.
MarkTrue(t ConditionType)
// MarkUnknown sets the status of t to Unknown and also sets the happy condition
// to Unknown if no other dependent condition is in an error state.
MarkUnknown(t ConditionType, reason, messageFormat string, messageA ...interface{})
// MarkFalse sets the status of t and the happy condition to False.
MarkFalse(t ConditionType, reason, messageFormat string, messageA ...interface{})
// InitializeConditions updates all Conditions in the ConditionSet to Unknown
// if not set.
InitializeConditions()
}
// NewLivingConditionSet returns a ConditionSet to hold the conditions for the
// living resource. ConditionReady is used as the happy condition.
// The set of condition types provided are those of the terminal subconditions.
func NewLivingConditionSet(d ...ConditionType) ConditionSet {
return newConditionSet(ConditionReady, d...)
}
// NewBatchConditionSet returns a ConditionSet to hold the conditions for the
// batch resource. ConditionSucceeded is used as the happy condition.
// The set of condition types provided are those of the terminal subconditions.
func NewBatchConditionSet(d ...ConditionType) ConditionSet {
return newConditionSet(ConditionSucceeded, d...)
}
// newConditionSet returns a ConditionSet to hold the conditions that are
// important for the caller. The first ConditionType is the overarching status
// for that will be used to signal the resources' status is Ready or Succeeded.
func newConditionSet(happy ConditionType, dependents ...ConditionType) ConditionSet {
var deps []ConditionType
for _, d := range dependents {
// Skip duplicates
if d == happy || contains(deps, d) {
continue
}
deps = append(deps, d)
}
return ConditionSet{
happy: happy,
dependents: deps,
}
}
func contains(ct []ConditionType, t ConditionType) bool {
for _, c := range ct {
if c == t {
return true
}
}
return false
}
// Check that conditionsImpl implements ConditionManager.
var _ ConditionManager = (*conditionsImpl)(nil)
// conditionsImpl implements the helper methods for evaluating Conditions.
// +k8s:deepcopy-gen=false
type conditionsImpl struct {
ConditionSet
accessor ConditionsAccessor
}
// Manage creates a ConditionManager from a accessor object using the original
// ConditionSet as a reference. Status must be or point to a struct.
func (r ConditionSet) Manage(status ConditionsAccessor) ConditionManager {
return conditionsImpl{
accessor: status,
ConditionSet: r,
}
}
// IsHappy looks at the happy condition and returns true if that condition is
// set to true.
func (r conditionsImpl) IsHappy() bool {
if c := r.GetCondition(r.happy); c == nil || !c.IsTrue() {
return false
}
return true
}
// GetCondition finds and returns the Condition that matches the ConditionType
// previously set on Conditions.
func (r conditionsImpl) GetCondition(t ConditionType) *Condition {
if r.accessor == nil {
return nil
}
for _, c := range r.accessor.GetConditions() {
if c.Type == t {
return &c
}
}
return nil
}
// SetCondition sets or updates the Condition on Conditions for Condition.Type.
// If there is an update, Conditions are stored back sorted.
func (r conditionsImpl) SetCondition(new Condition) {
if r.accessor == nil {
return
}
t := new.Type
var conditions Conditions
for _, c := range r.accessor.GetConditions() {
if c.Type != t {
conditions = append(conditions, c)
} else {
// If we'd only update the LastTransitionTime, then return.
new.LastTransitionTime = c.LastTransitionTime
if reflect.DeepEqual(&new, &c) {
return
}
}
}
new.LastTransitionTime = VolatileTime{Inner: metav1.NewTime(time.Now())}
conditions = append(conditions, new)
// Sorted for convenience of the consumer, i.e. kubectl.
sort.Slice(conditions, func(i, j int) bool { return conditions[i].Type < conditions[j].Type })
r.accessor.SetConditions(conditions)
}
func (r conditionsImpl) isTerminal(t ConditionType) bool {
for _, cond := range r.dependents {
if cond == t {
return true
}
}
if t == r.happy {
return true
}
return false
}
func (r conditionsImpl) severity(t ConditionType) ConditionSeverity {
if r.isTerminal(t) {
return ConditionSeverityError
}
return ConditionSeverityInfo
}
// MarkTrue sets the status of t to true, and then marks the happy condition to
// true if all other dependents are also true.
func (r conditionsImpl) MarkTrue(t ConditionType) {
// set the specified condition
r.SetCondition(Condition{
Type: t,
Status: corev1.ConditionTrue,
Severity: r.severity(t),
})
// check the dependents.
for _, cond := range r.dependents {
c := r.GetCondition(cond)
// Failed or Unknown conditions trump true conditions
if !c.IsTrue() {
return
}
}
// set the happy condition
r.SetCondition(Condition{
Type: r.happy,
Status: corev1.ConditionTrue,
Severity: r.severity(r.happy),
})
}
// MarkUnknown sets the status of t to Unknown and also sets the happy condition
// to Unknown if no other dependent condition is in an error state.
func (r conditionsImpl) MarkUnknown(t ConditionType, reason, messageFormat string, messageA ...interface{}) {
// set the specified condition
r.SetCondition(Condition{
Type: t,
Status: corev1.ConditionUnknown,
Reason: reason,
Message: fmt.Sprintf(messageFormat, messageA...),
Severity: r.severity(t),
})
// check the dependents.
isDependent := false
for _, cond := range r.dependents {
c := r.GetCondition(cond)
// Failed conditions trump Unknown conditions
if c.IsFalse() {
// Double check that the happy condition is also false.
happy := r.GetCondition(r.happy)
if !happy.IsFalse() {
r.MarkFalse(r.happy, reason, messageFormat, messageA...)
}
return
}
if cond == t {
isDependent = true
}
}
if isDependent {
// set the happy condition, if it is one of our dependent subconditions.
r.SetCondition(Condition{
Type: r.happy,
Status: corev1.ConditionUnknown,
Reason: reason,
Message: fmt.Sprintf(messageFormat, messageA...),
Severity: r.severity(r.happy),
})
}
}
// MarkFalse sets the status of t and the happy condition to False.
func (r conditionsImpl) MarkFalse(t ConditionType, reason, messageFormat string, messageA ...interface{}) {
types := []ConditionType{t}
for _, cond := range r.dependents {
if cond == t {
types = append(types, r.happy)
}
}
for _, t := range types {
r.SetCondition(Condition{
Type: t,
Status: corev1.ConditionFalse,
Reason: reason,
Message: fmt.Sprintf(messageFormat, messageA...),
Severity: r.severity(t),
})
}
}
// InitializeConditions updates all Conditions in the ConditionSet to Unknown
// if not set.
func (r conditionsImpl) InitializeConditions() {
happy := r.GetCondition(r.happy)
if happy == nil {
happy = &Condition{
Type: r.happy,
Status: corev1.ConditionUnknown,
Severity: ConditionSeverityError,
}
r.SetCondition(*happy)
}
// If the happy state is true, it implies that all of the terminal
// subconditions must be true, so initialize any unset conditions to
// true if our happy condition is true, otherwise unknown.
status := corev1.ConditionUnknown
if happy.Status == corev1.ConditionTrue {
status = corev1.ConditionTrue
}
for _, t := range r.dependents {
r.initializeTerminalCondition(t, status)
}
}
// initializeTerminalCondition initializes a Condition to the given status if unset.
func (r conditionsImpl) initializeTerminalCondition(t ConditionType, status corev1.ConditionStatus) *Condition {
if c := r.GetCondition(t); c != nil {
return c
}
c := Condition{
Type: t,
Status: status,
Severity: ConditionSeverityError,
}
r.SetCondition(c)
return &c
}

109
vendor/github.com/knative/pkg/apis/condition_types.go generated vendored Normal file
View File

@ -0,0 +1,109 @@
/*
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 apis
import (
corev1 "k8s.io/api/core/v1"
)
// Conditions is the schema for the conditions portion of the payload
type Conditions []Condition
// ConditionType is a camel-cased condition type.
type ConditionType string
const (
// ConditionReady specifies that the resource is ready.
// For long-running resources.
ConditionReady ConditionType = "Ready"
// ConditionSucceeded specifies that the resource has finished.
// For resource which run to completion.
ConditionSucceeded ConditionType = "Succeeded"
)
// ConditionSeverity expresses the severity of a Condition Type failing.
type ConditionSeverity string
const (
// ConditionSeverityError specifies that a failure of a condition type
// should be viewed as an error. As "Error" is the default for conditions
// we use the empty string (coupled with omitempty) to avoid confusion in
// the case where the condition is in state "True" (aka nothing is wrong).
ConditionSeverityError ConditionSeverity = ""
// ConditionSeverityWarning specifies that a failure of a condition type
// should be viewed as a warning, but that things could still work.
ConditionSeverityWarning ConditionSeverity = "Warning"
// ConditionSeverityInfo specifies that a failure of a condition type
// should be viewed as purely informational, and that things could still work.
ConditionSeverityInfo ConditionSeverity = "Info"
)
// Conditions defines a readiness condition for a Knative resource.
// See: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#typical-status-properties
// +k8s:deepcopy-gen=true
type Condition struct {
// Type of condition.
// +required
Type ConditionType `json:"type" description:"type of status condition"`
// Status of the condition, one of True, False, Unknown.
// +required
Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"`
// Severity with which to treat failures of this type of condition.
// When this is not specified, it defaults to Error.
// +optional
Severity ConditionSeverity `json:"severity,omitempty" description:"how to interpret failures of this condition, one of Error, Warning, Info"`
// LastTransitionTime is the last time the condition transitioned from one status to another.
// We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic
// differences (all other things held constant).
// +optional
LastTransitionTime VolatileTime `json:"lastTransitionTime,omitempty" description:"last time the condition transit from one status to another"`
// The reason for the condition's last transition.
// +optional
Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"`
// A human readable message indicating details about the transition.
// +optional
Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"`
}
// IsTrue is true if the condition is True
func (c *Condition) IsTrue() bool {
if c == nil {
return false
}
return c.Status == corev1.ConditionTrue
}
// IsFalse is true if the condition is False
func (c *Condition) IsFalse() bool {
if c == nil {
return false
}
return c.Status == corev1.ConditionFalse
}
// IsUnknown is true if the condition is Unknown
func (c *Condition) IsUnknown() bool {
if c == nil {
return true
}
return c.Status == corev1.ConditionUnknown
}

152
vendor/github.com/knative/pkg/apis/contexts.go generated vendored Normal file
View File

@ -0,0 +1,152 @@
/*
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 apis
import (
"context"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// This is attached to contexts passed to webhook interfaces when
// the receiver being validated is being created.
type inCreateKey struct{}
// WithinCreate is used to note that the webhook is calling within
// the context of a Create operation.
func WithinCreate(ctx context.Context) context.Context {
return context.WithValue(ctx, inCreateKey{}, struct{}{})
}
// IsInCreate checks whether the context is a Create.
func IsInCreate(ctx context.Context) bool {
return ctx.Value(inCreateKey{}) != nil
}
// This is attached to contexts passed to webhook interfaces when
// the receiver being validated is being updated.
type inUpdateKey struct{}
// WithinUpdate is used to note that the webhook is calling within
// the context of a Update operation.
func WithinUpdate(ctx context.Context, base interface{}) context.Context {
return context.WithValue(ctx, inUpdateKey{}, base)
}
// IsInUpdate checks whether the context is an Update.
func IsInUpdate(ctx context.Context) bool {
return ctx.Value(inUpdateKey{}) != nil
}
// GetBaseline returns the baseline of the update, or nil when we
// are not within an update context.
func GetBaseline(ctx context.Context) interface{} {
return ctx.Value(inUpdateKey{})
}
// This is attached to contexts passed to webhook interfaces when
// the receiver being validated is being created.
type userInfoKey struct{}
// WithUserInfo is used to note that the webhook is calling within
// the context of a Create operation.
func WithUserInfo(ctx context.Context, ui *authenticationv1.UserInfo) context.Context {
return context.WithValue(ctx, userInfoKey{}, ui)
}
// GetUserInfo accesses the UserInfo attached to the webhook context.
func GetUserInfo(ctx context.Context) *authenticationv1.UserInfo {
if ui, ok := ctx.Value(userInfoKey{}).(*authenticationv1.UserInfo); ok {
return ui
}
return nil
}
// This is attached to contexts as they are passed down through a resource
// being validated or defaulted to signal the ObjectMeta of the enclosing
// resource.
type parentMetaKey struct{}
// WithinParent attaches the ObjectMeta of the resource enclosing the
// nested resources we are validating. This is intended for use with
// interfaces like apis.Defaultable and apis.Validatable.
func WithinParent(ctx context.Context, om metav1.ObjectMeta) context.Context {
return context.WithValue(ctx, parentMetaKey{}, om)
}
// ParentMeta accesses the ObjectMeta of the enclosing parent resource
// from the context. See WithinParent for how to attach the parent's
// ObjectMeta to the context.
func ParentMeta(ctx context.Context) metav1.ObjectMeta {
if om, ok := ctx.Value(parentMetaKey{}).(metav1.ObjectMeta); ok {
return om
}
return metav1.ObjectMeta{}
}
// This is attached to contexts as they are passed down through a resource
// being validated or defaulted to signal that we are within a Spec.
type inSpec struct{}
// WithinSpec notes on the context that further validation or defaulting
// is within the context of a Spec. This is intended for use with
// interfaces like apis.Defaultable and apis.Validatable.
func WithinSpec(ctx context.Context) context.Context {
return context.WithValue(ctx, inSpec{}, struct{}{})
}
// IsInSpec returns whether the context of validation or defaulting is
// the Spec of the parent resource.
func IsInSpec(ctx context.Context) bool {
return ctx.Value(inSpec{}) != nil
}
// This is attached to contexts as they are passed down through a resource
// being validated or defaulted to signal that we are within a Status.
type inStatus struct{}
// WithinStatus notes on the context that further validation or defaulting
// is within the context of a Status. This is intended for use with
// interfaces like apis.Defaultable and apis.Validatable.
func WithinStatus(ctx context.Context) context.Context {
return context.WithValue(ctx, inStatus{}, struct{}{})
}
// IsInStatus returns whether the context of validation or defaulting is
// the Status of the parent resource.
func IsInStatus(ctx context.Context) bool {
return ctx.Value(inStatus{}) != nil
}
// This is attached to contexts as they are passed down through a resource
// being validated to direct them to disallow deprecated fields.
type disallowDeprecated struct{}
// DisallowDeprecated notes on the context that further validation
// should disallow the used of deprecated fields. This may be used
// to ensure that new paths through resources to a common type don't
// allow the mistakes of old versions to be introduced.
func DisallowDeprecated(ctx context.Context) context.Context {
return context.WithValue(ctx, disallowDeprecated{}, struct{}{})
}
// IsDeprecatedAllowed checks the context to see whether deprecated fields
// are allowed.
func IsDeprecatedAllowed(ctx context.Context) bool {
return ctx.Value(disallowDeprecated{}) == nil
}

180
vendor/github.com/knative/pkg/apis/deprecated.go generated vendored Normal file
View File

@ -0,0 +1,180 @@
/*
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 apis
import (
"context"
"reflect"
"strings"
)
const (
deprecatedPrefix = "Deprecated"
)
// CheckDeprecated checks whether the provided named deprecated fields
// are set in a context where deprecation is disallowed.
// This is a shallow check.
func CheckDeprecated(ctx context.Context, obj interface{}) *FieldError {
return CheckDeprecatedUpdate(ctx, obj, nil)
}
// CheckDeprecated checks whether the provided named deprecated fields
// are set in a context where deprecation is disallowed.
// This is a json shallow check. We will recursively check inlined structs.
func CheckDeprecatedUpdate(ctx context.Context, obj interface{}, original interface{}) *FieldError {
if IsDeprecatedAllowed(ctx) {
return nil
}
var errs *FieldError
objFields, objInlined := getPrefixedNamedFieldValues(deprecatedPrefix, obj)
if nonZero(reflect.ValueOf(original)) {
originalFields, originalInlined := getPrefixedNamedFieldValues(deprecatedPrefix, original)
// We only have to walk obj Fields because the assumption is that obj
// and original are of the same type.
for name, value := range objFields {
if nonZero(value) {
if differ(originalFields[name], value) {
// Not allowed to update the value.
errs = errs.Also(ErrDisallowedUpdateDeprecatedFields(name))
}
}
}
// Look for deprecated inlined updates.
if len(objInlined) > 0 {
for name, value := range objInlined {
errs = errs.Also(CheckDeprecatedUpdate(ctx, value, originalInlined[name]))
}
}
} else {
for name, value := range objFields {
if nonZero(value) {
// Not allowed to set the value.
errs = errs.Also(ErrDisallowedFields(name))
}
}
// Look for deprecated inlined creates.
if len(objInlined) > 0 {
for _, value := range objInlined {
errs = errs.Also(CheckDeprecated(ctx, value))
}
}
}
return errs
}
func getPrefixedNamedFieldValues(prefix string, obj interface{}) (map[string]reflect.Value, map[string]interface{}) {
fields := make(map[string]reflect.Value, 0)
inlined := make(map[string]interface{}, 0)
objValue := reflect.Indirect(reflect.ValueOf(obj))
// If res is not valid or a struct, don't even try to use it.
if !objValue.IsValid() || objValue.Kind() != reflect.Struct {
return fields, inlined
}
for i := 0; i < objValue.NumField(); i++ {
tf := objValue.Type().Field(i)
if v := objValue.Field(i); v.IsValid() {
jTag := tf.Tag.Get("json")
if strings.HasPrefix(tf.Name, prefix) {
name := strings.Split(jTag, ",")[0]
if name == "" {
// Default to field name in go struct if no json name.
name = tf.Name
}
fields[name] = v
} else if jTag == ",inline" {
inlined[tf.Name] = getInterface(v)
}
}
}
return fields, inlined
}
// getInterface returns the interface value of the reflected object.
func getInterface(a reflect.Value) interface{} {
switch a.Kind() {
case reflect.Ptr:
if a.IsNil() {
return nil
}
return a.Elem().Interface()
case reflect.Map, reflect.Slice, reflect.Array:
return a.Elem().Interface()
// This is a nil interface{} type.
case reflect.Invalid:
return nil
default:
return a.Interface()
}
}
// nonZero returns true if a is nil or reflect.Zero.
func nonZero(a reflect.Value) bool {
switch a.Kind() {
case reflect.Ptr:
if a.IsNil() {
return false
}
return nonZero(a.Elem())
case reflect.Map, reflect.Slice, reflect.Array:
if a.IsNil() {
return false
}
return true
// This is a nil interface{} type.
case reflect.Invalid:
return false
default:
if reflect.DeepEqual(a.Interface(), reflect.Zero(a.Type()).Interface()) {
return false
}
return true
}
}
// differ returns true if a != b
func differ(a, b reflect.Value) bool {
if a.Kind() != b.Kind() {
return true
}
switch a.Kind() {
case reflect.Ptr:
if a.IsNil() || b.IsNil() {
return a.IsNil() != b.IsNil()
}
return differ(a.Elem(), b.Elem())
default:
if reflect.DeepEqual(a.Interface(), b.Interface()) {
return false
}
return true
}
}

View File

@ -22,18 +22,18 @@ import (
"github.com/knative/pkg/apis"
"github.com/knative/pkg/apis/duck"
"github.com/knative/pkg/apis/duck/v1beta1"
)
// Addressable provides a generic mechanism for a custom resource
// definition to indicate a destination for message delivery.
// (Currently, only hostname is supported, and HTTP is implied. In the
// future, additional schemes may be supported, and path components
// ala UI may also be supported.)
// Addressable is the schema for the destination information. This is
// typically stored in the object's `status`, as this information may
// be generated by the controller.
type Addressable struct {
v1beta1.Addressable `json:",omitempty"`
Hostname string `json:"hostname,omitempty"`
}
@ -60,12 +60,14 @@ type AddressStatus struct {
Address *Addressable `json:"address,omitempty"`
}
// Verify AddressableType resources meet duck contracts.
var _ duck.Populatable = (*AddressableType)(nil)
var _ apis.Listable = (*AddressableType)(nil)
var (
// Verify AddressableType resources meet duck contracts.
_ duck.Populatable = (*AddressableType)(nil)
_ apis.Listable = (*AddressableType)(nil)
)
// GetFullType implements duck.Implementable
func (_ *Addressable) GetFullType() duck.Populatable {
func (*Addressable) GetFullType() duck.Populatable {
return &AddressableType{}
}
@ -80,7 +82,7 @@ func (t *AddressableType) Populate() {
}
// GetListType implements apis.Listable
func (r *AddressableType) GetListType() runtime.Object {
func (*AddressableType) GetListType() runtime.Object {
return &AddressableTypeList{}
}

View File

@ -153,7 +153,7 @@ func (r ConditionSet) Manage(status interface{}) ConditionManager {
}
}
// We tried. This object is not understood by the the condition manager.
// We tried. This object is not understood by the condition manager.
//panic(fmt.Sprintf("Error converting %T into a ConditionsAccessor", status))
// TODO: not sure which way. using panic above means passing nil status panics the system.
return conditionsImpl{

View File

@ -159,7 +159,7 @@ var _ duck.Populatable = (*KResource)(nil)
var _ apis.Listable = (*KResource)(nil)
// GetFullType implements duck.Implementable
func (_ *Conditions) GetFullType() duck.Populatable {
func (*Conditions) GetFullType() duck.Populatable {
return &KResource{}
}
@ -187,7 +187,7 @@ func (t *KResource) Populate() {
}
// GetListType implements apis.Listable
func (r *KResource) GetListType() runtime.Object {
func (*KResource) GetListType() runtime.Object {
return &KResourceList{}
}

View File

@ -67,7 +67,7 @@ var _ duck.Populatable = (*LegacyTarget)(nil)
var _ apis.Listable = (*LegacyTarget)(nil)
// GetFullType implements duck.Implementable
func (_ *LegacyTargetable) GetFullType() duck.Populatable {
func (*LegacyTargetable) GetFullType() duck.Populatable {
return &LegacyTarget{}
}
@ -80,7 +80,7 @@ func (t *LegacyTarget) Populate() {
}
// GetListType implements apis.Listable
func (r *LegacyTarget) GetListType() runtime.Object {
func (*LegacyTarget) GetListType() runtime.Object {
return &LegacyTargetList{}
}

View File

@ -60,14 +60,16 @@ type TargetStatus struct {
Targetable *Targetable `json:"targetable,omitempty"`
}
// In order for Targetable to be Implementable, Target must be Populatable.
var _ duck.Populatable = (*Target)(nil)
var (
// In order for Targetable to be Implementable, Target must be Populatable.
_ duck.Populatable = (*Target)(nil)
// Ensure Target satisfies apis.Listable
var _ apis.Listable = (*Target)(nil)
// Ensure Target satisfies apis.Listable
_ apis.Listable = (*Target)(nil)
)
// GetFullType implements duck.Implementable
func (_ *Targetable) GetFullType() duck.Populatable {
func (*Targetable) GetFullType() duck.Populatable {
return &Target{}
}
@ -82,7 +84,7 @@ func (t *Target) Populate() {
}
// GetListType implements apis.Listable
func (r *Target) GetListType() runtime.Object {
func (*Target) GetListType() runtime.Object {
return &TargetList{}
}

View File

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Knative Authors
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.
@ -30,7 +30,7 @@ func (in *AddressStatus) DeepCopyInto(out *AddressStatus) {
if in.Address != nil {
in, out := &in.Address, &out.Address
*out = new(Addressable)
**out = **in
(*in).DeepCopyInto(*out)
}
return
}
@ -48,6 +48,7 @@ func (in *AddressStatus) DeepCopy() *AddressStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Addressable) DeepCopyInto(out *Addressable) {
*out = *in
in.Addressable.DeepCopyInto(&out.Addressable)
return
}

View File

@ -0,0 +1,97 @@
/*
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 v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/knative/pkg/apis"
"github.com/knative/pkg/apis/duck"
)
// Addressable provides a generic mechanism for a custom resource
// definition to indicate a destination for message delivery.
// Addressable is the schema for the destination information. This is
// typically stored in the object's `status`, as this information may
// be generated by the controller.
type Addressable struct {
URL *apis.URL `json:"url,omitempty"`
}
// Addressable is an Implementable "duck type".
var _ duck.Implementable = (*Addressable)(nil)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AddressableType is a skeleton type wrapping Addressable in the manner we expect
// resource writers defining compatible resources to embed it. We will
// typically use this type to deserialize Addressable ObjectReferences and
// access the Addressable data. This is not a real resource.
type AddressableType struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Status AddressStatus `json:"status"`
}
// AddressStatus shows how we expect folks to embed Addressable in
// their Status field.
type AddressStatus struct {
Address *Addressable `json:"address,omitempty"`
}
var (
// Verify AddressableType resources meet duck contracts.
_ duck.Populatable = (*AddressableType)(nil)
_ apis.Listable = (*AddressableType)(nil)
)
// GetFullType implements duck.Implementable
func (*Addressable) GetFullType() duck.Populatable {
return &AddressableType{}
}
// Populate implements duck.Populatable
func (t *AddressableType) Populate() {
t.Status = AddressStatus{
&Addressable{
// Populate ALL fields
URL: &apis.URL{
Scheme: "http",
Host: "foo.com",
},
},
}
}
// GetListType implements apis.Listable
func (*AddressableType) GetListType() runtime.Object {
return &AddressableTypeList{}
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AddressableTypeList is a list of AddressableType resources
type AddressableTypeList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []AddressableType `json:"items"`
}

23
vendor/github.com/knative/pkg/apis/duck/v1beta1/doc.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
/*
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.
*/
// Api versions allow the api contract for a resource to be changed while keeping
// backward compatibility by support multiple concurrent versions
// of the same resource
// +k8s:deepcopy-gen=package
// +groupName=duck.knative.dev
package v1beta1

View File

@ -0,0 +1,55 @@
/*
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 v1beta1
import (
"github.com/knative/pkg/apis/duck"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: duck.GroupName, Version: "v1beta1"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(
SchemeGroupVersion,
&KResource{},
(&KResource{}).GetListType(),
&AddressableType{},
(&AddressableType{}).GetListType(),
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@ -0,0 +1,140 @@
/*
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 v1beta1
import (
"context"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/knative/pkg/apis"
"github.com/knative/pkg/apis/duck"
)
// Conditions is a simple wrapper around apis.Conditions to implement duck.Implementable.
type Conditions apis.Conditions
// Conditions is an Implementable "duck type".
var _ duck.Implementable = (*Conditions)(nil)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// KResource is a skeleton type wrapping Conditions in the manner we expect
// resource writers defining compatible resources to embed it. We will
// typically use this type to deserialize Conditions ObjectReferences and
// access the Conditions data. This is not a real resource.
type KResource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Status Status `json:"status"`
}
// Status shows how we expect folks to embed Conditions in
// their Status field.
// WARNING: Adding fields to this struct will add them to all Knative resources.
type Status struct {
// ObservedGeneration is the 'Generation' of the Service that
// was last processed by the controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions the latest available observations of a resource's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
var _ apis.ConditionsAccessor = (*Status)(nil)
// GetConditions implements apis.ConditionsAccessor
func (s *Status) GetConditions() apis.Conditions {
return apis.Conditions(s.Conditions)
}
// SetConditions implements apis.ConditionsAccessor
func (s *Status) SetConditions(c apis.Conditions) {
s.Conditions = Conditions(c)
}
// In order for Conditions to be Implementable, KResource must be Populatable.
var _ duck.Populatable = (*KResource)(nil)
// Ensure KResource satisfies apis.Listable
var _ apis.Listable = (*KResource)(nil)
// GetFullType implements duck.Implementable
func (*Conditions) GetFullType() duck.Populatable {
return &KResource{}
}
// GetCondition fetches the condition of the specified type.
func (s *Status) GetCondition(t apis.ConditionType) *apis.Condition {
for _, cond := range s.Conditions {
if cond.Type == t {
return &cond
}
}
return nil
}
// ConvertTo helps implement apis.Convertible for types embedding this Status.
func (source *Status) ConvertTo(ctx context.Context, sink *Status) {
sink.ObservedGeneration = source.ObservedGeneration
for _, c := range source.Conditions {
switch c.Type {
// Copy over the "happy" condition, which is the only condition that
// we can reliably transfer.
case apis.ConditionReady, apis.ConditionSucceeded:
sink.SetConditions(apis.Conditions{c})
return
}
}
}
// Populate implements duck.Populatable
func (t *KResource) Populate() {
t.Status.ObservedGeneration = 42
t.Status.Conditions = Conditions{{
// Populate ALL fields
Type: "Birthday",
Status: corev1.ConditionTrue,
LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Date(1984, 02, 28, 18, 52, 00, 00, time.UTC))},
Reason: "Celebrate",
Message: "n3wScott, find your party hat :tada:",
}}
}
// GetListType implements apis.Listable
func (*KResource) GetListType() runtime.Object {
return &KResourceList{}
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// KResourceList is a list of KResource resources
type KResourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []KResource `json:"items"`
}

View File

@ -0,0 +1,233 @@
// +build !ignore_autogenerated
/*
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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta1
import (
apis "github.com/knative/pkg/apis"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AddressStatus) DeepCopyInto(out *AddressStatus) {
*out = *in
if in.Address != nil {
in, out := &in.Address, &out.Address
*out = new(Addressable)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressStatus.
func (in *AddressStatus) DeepCopy() *AddressStatus {
if in == nil {
return nil
}
out := new(AddressStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Addressable) DeepCopyInto(out *Addressable) {
*out = *in
if in.URL != nil {
in, out := &in.URL, &out.URL
*out = new(apis.URL)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Addressable.
func (in *Addressable) DeepCopy() *Addressable {
if in == nil {
return nil
}
out := new(Addressable)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AddressableType) DeepCopyInto(out *AddressableType) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressableType.
func (in *AddressableType) DeepCopy() *AddressableType {
if in == nil {
return nil
}
out := new(AddressableType)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AddressableType) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AddressableTypeList) DeepCopyInto(out *AddressableTypeList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]AddressableType, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressableTypeList.
func (in *AddressableTypeList) DeepCopy() *AddressableTypeList {
if in == nil {
return nil
}
out := new(AddressableTypeList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AddressableTypeList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in Conditions) DeepCopyInto(out *Conditions) {
{
in := &in
*out = make(Conditions, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions.
func (in Conditions) DeepCopy() Conditions {
if in == nil {
return nil
}
out := new(Conditions)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KResource) DeepCopyInto(out *KResource) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KResource.
func (in *KResource) DeepCopy() *KResource {
if in == nil {
return nil
}
out := new(KResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KResource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KResourceList) DeepCopyInto(out *KResourceList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KResource, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KResourceList.
func (in *KResourceList) DeepCopy() *KResourceList {
if in == nil {
return nil
}
out := new(KResourceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KResourceList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Status) DeepCopyInto(out *Status) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make(Conditions, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status.
func (in *Status) DeepCopy() *Status {
if in == nil {
return nil
}
out := new(Status)
in.DeepCopyInto(out)
return out
}

View File

@ -20,6 +20,8 @@ import (
"fmt"
"sort"
"strings"
"github.com/knative/pkg/kmp"
)
// CurrentField is a constant to supply as a fieldPath for when there is
@ -300,17 +302,26 @@ func ErrDisallowedFields(fieldPaths ...string) *FieldError {
}
}
// ErrInvalidArrayValue consturcts a FieldError for a repetetive `field`
// ErrDisallowedUpdateDeprecatedFields is a variadic helper method for
// constructing a FieldError for updating of deprecated fields.
func ErrDisallowedUpdateDeprecatedFields(fieldPaths ...string) *FieldError {
return &FieldError{
Message: "must not update deprecated field(s)",
Paths: fieldPaths,
}
}
// ErrInvalidArrayValue constructs a FieldError for a repetetive `field`
// at `index` that has received an invalid string value.
func ErrInvalidArrayValue(value, field string, index int) *FieldError {
func ErrInvalidArrayValue(value interface{}, field string, index int) *FieldError {
return ErrInvalidValue(value, CurrentField).ViaFieldIndex(field, index)
}
// ErrInvalidValue constructs a FieldError for a field that has received an
// invalid string value.
func ErrInvalidValue(value, fieldPath string) *FieldError {
func ErrInvalidValue(value interface{}, fieldPath string) *FieldError {
return &FieldError{
Message: fmt.Sprintf("invalid value %q", value),
Message: fmt.Sprintf("invalid value: %v", value),
Paths: []string{fieldPath},
}
}
@ -335,9 +346,9 @@ func ErrMultipleOneOf(fieldPaths ...string) *FieldError {
// ErrInvalidKeyName is a variadic helper method for constructing a FieldError
// that specifies a key name that is invalid.
func ErrInvalidKeyName(value, fieldPath string, details ...string) *FieldError {
func ErrInvalidKeyName(key, fieldPath string, details ...string) *FieldError {
return &FieldError{
Message: fmt.Sprintf("invalid key name %q", value),
Message: fmt.Sprintf("invalid key name %q", key),
Paths: []string{fieldPath},
Details: strings.Join(details, ", "),
}
@ -345,9 +356,24 @@ func ErrInvalidKeyName(value, fieldPath string, details ...string) *FieldError {
// ErrOutOfBoundsValue constructs a FieldError for a field that has received an
// out of bound value.
func ErrOutOfBoundsValue(value, lower, upper, fieldPath string) *FieldError {
func ErrOutOfBoundsValue(value, lower, upper interface{}, fieldPath string) *FieldError {
return &FieldError{
Message: fmt.Sprintf("expected %s <= %s <= %s", lower, value, upper),
Message: fmt.Sprintf("expected %v <= %v <= %v", lower, value, upper),
Paths: []string{fieldPath},
}
}
// CheckDisallowedFields compares the request object against a masked request object. Fields
// that are set in the request object that are unset in the mask are reported back as disallowed fields. If
// there is an error comparing the two objects FieldError of "Internal Error" is returned.
func CheckDisallowedFields(request, maskedRequest interface{}) *FieldError {
if disallowed, err := kmp.CompareSetFields(request, maskedRequest); err != nil {
return &FieldError{
Message: fmt.Sprintf("Internal Error"),
Paths: []string{CurrentField},
}
} else if len(disallowed) > 0 {
return ErrDisallowedFields(disallowed...)
}
return nil
}

View File

@ -19,7 +19,6 @@ package apis
import (
"context"
authenticationv1 "k8s.io/api/authentication/v1"
"k8s.io/apimachinery/pkg/runtime"
)
@ -35,8 +34,19 @@ type Validatable interface {
Validate(context.Context) *FieldError
}
// Convertible indicates that a particular type supports conversions to/from
// "higher" versions of the same type.
type Convertible interface {
// ConvertUp up-converts the receiver into `to`.
ConvertUp(ctx context.Context, to Convertible) error
// ConvertDown down-converts from `from` into the receiver.
ConvertDown(ctx context.Context, from Convertible) error
}
// Immutable indicates that a particular type has fields that should
// not change after creation.
// DEPRECATED: Use WithinUpdate / GetBaseline from within Validatable instead.
type Immutable interface {
// CheckImmutableFields checks that the current instance's immutable
// fields haven't changed from the provided original.
@ -52,6 +62,7 @@ type Listable interface {
}
// Annotatable indicates that a particular type applies various annotations.
type Annotatable interface {
AnnotateUserInfo(ctx context.Context, previous Annotatable, ui *authenticationv1.UserInfo)
}
// DEPRECATED: Use WithUserInfo / GetUserInfo from within SetDefaults instead.
// The webhook functionality for this has been turned down, which is why this
// interface is empty.
type Annotatable interface{}

View File

@ -0,0 +1,62 @@
/*
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 apis
import (
"fmt"
"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ValidateObjectMetadata validates that `metadata` stanza of the
// resources is correct.
func ValidateObjectMetadata(meta metav1.Object) *FieldError {
name := meta.GetName()
generateName := meta.GetGenerateName()
if generateName != "" {
msgs := validation.NameIsDNS1035Label(generateName, true)
if len(msgs) > 0 {
return &FieldError{
Message: fmt.Sprintf("not a DNS 1035 label prefix: %v", msgs),
Paths: []string{"generateName"},
}
}
}
if name != "" {
msgs := validation.NameIsDNS1035Label(name, false)
if len(msgs) > 0 {
return &FieldError{
Message: fmt.Sprintf("not a DNS 1035 label: %v", msgs),
Paths: []string{"name"},
}
}
}
if generateName == "" && name == "" {
return &FieldError{
Message: "name or generateName is required",
Paths: []string{"name"},
}
}
return nil
}

73
vendor/github.com/knative/pkg/apis/url.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
/*
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 apis
import (
"encoding/json"
"fmt"
"net/url"
)
// URL is an alias of url.URL.
// It has custom json marshal methods that enable it to be used in K8s CRDs
// such that the CRD resource will have the URL but operator code can can work with url.URL struct
type URL url.URL
// ParseURL attempts to parse the given string as a URL.
func ParseURL(u string) (*URL, error) {
if u == "" {
return nil, nil
}
pu, err := url.Parse(u)
if err != nil {
return nil, err
}
return (*URL)(pu), nil
}
// MarshalJSON implements a custom json marshal method used when this type is
// marshaled using json.Marshal.
// json.Marshaler impl
func (u URL) MarshalJSON() ([]byte, error) {
b := fmt.Sprintf("%q", u.String())
return []byte(b), nil
}
// UnmarshalJSON implements the json unmarshal method used when this type is
// unmarsheled using json.Unmarshal.
// json.Unmarshaler impl
func (u *URL) UnmarshalJSON(b []byte) error {
var ref string
if err := json.Unmarshal(b, &ref); err != nil {
return err
}
r, err := ParseURL(ref)
if err != nil {
return err
}
*u = *r
return nil
}
// String returns the full string representation of the URL.
func (u *URL) String() string {
if u == nil {
return ""
}
uu := url.URL(*u)
return uu.String()
}

View File

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Knative Authors
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.
@ -20,6 +20,49 @@ limitations under the License.
package apis
import (
url "net/url"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in Conditions) DeepCopyInto(out *Conditions) {
{
in := &in
*out = make(Conditions, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions.
func (in Conditions) DeepCopy() Conditions {
if in == nil {
return nil
}
out := new(Conditions)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FieldError) DeepCopyInto(out *FieldError) {
*out = *in
@ -48,6 +91,27 @@ func (in *FieldError) DeepCopy() *FieldError {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *URL) DeepCopyInto(out *URL) {
*out = *in
if in.User != nil {
in, out := &in.User, &out.User
*out = new(url.Userinfo)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new URL.
func (in *URL) DeepCopy() *URL {
if in == nil {
return nil
}
out := new(URL)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolatileTime) DeepCopyInto(out *VolatileTime) {
*out = *in

View File

@ -8,7 +8,7 @@ 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
istributed under the License is istributed on an "AS IS" BASIS,
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.
@ -21,6 +21,7 @@ import (
"time"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
informers "k8s.io/client-go/informers"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
@ -34,7 +35,7 @@ func NewDefaultWatcher(kc kubernetes.Interface, namespace string) *InformedWatch
return NewInformedWatcher(kc, namespace)
}
// NewInformedWatcherFromFactory watchers a Kubernetes namespace for configmap changs
// NewInformedWatcherFromFactory watches a Kubernetes namespace for configmap changes.
func NewInformedWatcherFromFactory(sif informers.SharedInformerFactory, namespace string) *InformedWatcher {
return &InformedWatcher{
sif: sif,
@ -42,10 +43,11 @@ func NewInformedWatcherFromFactory(sif informers.SharedInformerFactory, namespac
ManualWatcher: ManualWatcher{
Namespace: namespace,
},
defaults: make(map[string]*corev1.ConfigMap),
}
}
// NewInformedWatcher watchers a Kubernetes namespace for configmap changs
// NewInformedWatcher watches a Kubernetes namespace for configmap changes.
func NewInformedWatcher(kc kubernetes.Interface, namespace string) *InformedWatcher {
return NewInformedWatcherFromFactory(informers.NewSharedInformerFactoryWithOptions(
kc,
@ -61,24 +63,58 @@ type InformedWatcher struct {
informer corev1informers.ConfigMapInformer
started bool
// defaults are the default ConfigMaps to use if the real ones do not exist or are deleted.
defaults map[string]*corev1.ConfigMap
// Embedding this struct allows us to reuse the logic
// of registering and notifying observers. This simplifies the
// InformedWatcher to just setting up the Kubernetes informer
// InformedWatcher to just setting up the Kubernetes informer.
ManualWatcher
}
// Asserts that InformedWatcher implements Watcher.
var _ Watcher = (*InformedWatcher)(nil)
// Start implements Watcher
// Asserts that InformedWatcher implements DefaultingWatcher.
var _ DefaultingWatcher = (*InformedWatcher)(nil)
// WatchWithDefault implements DefaultingWatcher.
func (i *InformedWatcher) WatchWithDefault(cm corev1.ConfigMap, o Observer) {
i.defaults[cm.Name] = &cm
i.m.Lock()
started := i.started
i.m.Unlock()
if started {
// TODO make both Watch and WatchWithDefault work after the InformedWatcher has started.
// This likely entails changing this to `o(&cm)` and having Watch check started, if it has
// started, then ensuring i.informer.Lister().ConfigMaps(i.Namespace).Get(cmName) exists and
// calling this observer on it. It may require changing Watch and WatchWithDefault to return
// an error.
panic("cannot WatchWithDefault after the InformedWatcher has started")
}
i.Watch(cm.Name, o)
}
// Start implements Watcher.
func (i *InformedWatcher) Start(stopCh <-chan struct{}) error {
// Pretend that all the defaulted ConfigMaps were just created. This is done before we start
// the informer to ensure that if a defaulted ConfigMap does exist, then the real value is
// processed after the default one.
for k := range i.observers {
if def, ok := i.defaults[k]; ok {
i.addConfigMapEvent(def)
}
}
if err := i.registerCallbackAndStartInformer(stopCh); err != nil {
return err
}
// Wait until it has been synced (WITHOUT holing the mutex, so callbacks happen)
if ok := cache.WaitForCacheSync(stopCh, i.informer.Informer().HasSynced); !ok {
return errors.New("Error waiting for ConfigMap informer to sync.")
return errors.New("error waiting for ConfigMap informer to sync")
}
return i.checkObservedResourcesExist()
@ -88,16 +124,17 @@ func (i *InformedWatcher) registerCallbackAndStartInformer(stopCh <-chan struct{
i.m.Lock()
defer i.m.Unlock()
if i.started {
return errors.New("Watcher already started!")
return errors.New("watcher already started")
}
i.started = true
i.informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: i.addConfigMapEvent,
UpdateFunc: i.updateConfigMapEvent,
DeleteFunc: i.deleteConfigMapEvent,
})
// Start the shared informer factory (non-blocking)
// Start the shared informer factory (non-blocking).
i.sif.Start(stopCh)
return nil
}
@ -109,6 +146,12 @@ func (i *InformedWatcher) checkObservedResourcesExist() error {
for k := range i.observers {
_, err := i.informer.Lister().ConfigMaps(i.Namespace).Get(k)
if err != nil {
if k8serrors.IsNotFound(err) {
if _, ok := i.defaults[k]; ok {
// It is defaulted, so it is OK that it doesn't exist.
continue
}
}
return err
}
}
@ -124,3 +167,11 @@ func (i *InformedWatcher) updateConfigMapEvent(old, new interface{}) {
configMap := new.(*corev1.ConfigMap)
i.OnChange(configMap)
}
func (i *InformedWatcher) deleteConfigMapEvent(obj interface{}) {
configMap := obj.(*corev1.ConfigMap)
if def, ok := i.defaults[configMap.Name]; ok {
i.OnChange(def)
}
// If there is no default value, then don't do anything.
}

View File

@ -29,7 +29,6 @@ type ManualWatcher struct {
// Guards mutations to defaultImpl fields
m sync.Mutex
started bool
observers map[string][]Observer
}

View File

@ -44,7 +44,7 @@ type Constructors map[string]interface{}
// An UntypedStore is a responsible for storing and
// constructing configs from Kubernetes ConfigMaps
//
// WatchConfigs should be used with a configmap,Watcher
// WatchConfigs should be used with a configmap.Watcher
// in order for this store to remain up to date
type UntypedStore struct {
name string

View File

@ -26,7 +26,7 @@ import (
// contents).
type Observer func(*corev1.ConfigMap)
// Watcher defined the interface that a configmap implementation must implement.
// Watcher defines the interface that a configmap implementation must implement.
type Watcher interface {
// Watch is called to register a callback to be notified when a named ConfigMap changes.
Watch(string, Observer)
@ -36,3 +36,14 @@ type Watcher interface {
// initial state of the ConfigMaps they are watching.
Start(<-chan struct{}) error
}
// DefaultingWatcher is similar to Watcher, but if a ConfigMap is absent, then a code provided
// default will be used.
type DefaultingWatcher interface {
Watcher
// WatchWithDefault is called to register a callback to be notified when a named ConfigMap
// changes. The provided default value is always observed before any real ConfigMap with that
// name is. If the real ConfigMap with that name is deleted, then the default value is observed.
WatchWithDefault(cm corev1.ConfigMap, o Observer)
}

View File

@ -36,6 +36,8 @@ func init() {
// SafeDiff wraps cmp.Diff but recovers from panics and uses custom Comparers for:
// * k8s.io/apimachinery/pkg/api/resource.Quantity
// SafeDiff should be used instead of cmp.Diff in non-test code to protect the running
// process from crashing.
func SafeDiff(x, y interface{}, opts ...cmp.Option) (diff string, err error) {
// cmp.Diff will panic if we miss something; return error instead of crashing.
defer func() {
@ -50,6 +52,10 @@ func SafeDiff(x, y interface{}, opts ...cmp.Option) (diff string, err error) {
return
}
// SafeEqual wraps cmp.Equal but recovers from panics and uses custom Comparers for:
// * k8s.io/apimachinery/pkg/api/resource.Quantity
// SafeEqual should be used instead of cmp.Equal in non-test code to protect the running
// process from crashing.
func SafeEqual(x, y interface{}, opts ...cmp.Option) (equal bool, err error) {
// cmp.Equal will panic if we miss something; return error instead of crashing.
defer func() {
@ -63,3 +69,24 @@ func SafeEqual(x, y interface{}, opts ...cmp.Option) (equal bool, err error) {
return
}
// CompareSetFields returns a list of field names that differ between
// x and y. Uses SafeEqual for comparison.
func CompareSetFields(x, y interface{}, opts ...cmp.Option) ([]string, error) {
r := new(FieldListReporter)
opts = append(opts, cmp.Reporter(r))
_, err := SafeEqual(x, y, opts...)
return r.Fields(), err
}
// ShortDiff returns a zero-context, unified human-readable diff.
// Uses SafeEqual for comparison.
func ShortDiff(prev, cur interface{}, opts ...cmp.Option) (string, error) {
r := new(ShortDiffReporter)
opts = append(opts, cmp.Reporter(r))
var err error
if _, err = SafeEqual(prev, cur, opts...); err != nil {
return "", err
}
return r.Diff()
}

136
vendor/github.com/knative/pkg/kmp/reporters.go generated vendored Normal file
View File

@ -0,0 +1,136 @@
/*
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 kmp
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/google/go-cmp/cmp"
)
// FieldListReporter implements the cmp.Reporter interface. It keeps
// track of the field names that differ between two structs and reports
// them through the Fields() function.
type FieldListReporter struct {
path cmp.Path
fieldNames []string
}
// PushStep implements the cmp.Reporter.
func (r *FieldListReporter) PushStep(ps cmp.PathStep) {
r.path = append(r.path, ps)
}
// fieldName returns a readable name for the field. If the field has JSON annotations it
// returns the JSON key. If the field does not have JSON annotations or the JSON annotation
// marks the field as ignored it returns the field's go name
func (r *FieldListReporter) fieldName() string {
if len(r.path) < 2 {
return r.path.Index(0).String()
} else {
fieldName := strings.TrimPrefix(r.path.Index(1).String(), ".")
// Prefer JSON name to fieldName if it exists
structField, exists := r.path.Index(0).Type().FieldByName(fieldName)
if exists {
tag := structField.Tag.Get("json")
if tag != "" && tag != "-" {
return strings.SplitN(tag, ",", 2)[0]
}
}
return fieldName
}
}
// Report implements the cmp.Reporter.
func (r *FieldListReporter) Report(rs cmp.Result) {
if rs.Equal() {
return
}
name := r.fieldName()
// Only append elements we don't already have.
for _, v := range r.fieldNames {
if name == v {
return
}
}
r.fieldNames = append(r.fieldNames, name)
}
// PopStep implements cmp.Reporter.
func (r *FieldListReporter) PopStep() {
r.path = r.path[:len(r.path)-1]
}
// Fields returns the field names that differed between the two
// objects after calling cmp.Equal with the FieldListReporter. Field names
// are returned in alphabetical order.
func (r *FieldListReporter) Fields() []string {
sort.Strings(r.fieldNames)
return r.fieldNames
}
// ShortDiffReporter implements the cmp.Reporter interface. It reports
// on fields which have diffing values in a short zero-context, unified diff
// format.
type ShortDiffReporter struct {
path cmp.Path
diffs []string
err error
}
// PushStep implements the cmp.Reporter.
func (r *ShortDiffReporter) PushStep(ps cmp.PathStep) {
r.path = append(r.path, ps)
}
// Report implements the cmp.Reporter.
func (r *ShortDiffReporter) Report(rs cmp.Result) {
if rs.Equal() {
return
}
cur := r.path.Last()
vx, vy := cur.Values()
t := cur.Type()
var diff string
// Prefix struct values with the types to add clarity in output
if !vx.IsValid() || !vy.IsValid() {
r.err = fmt.Errorf("Unable to diff %+v and %+v on path %#v", vx, vy, r.path)
} else if t.Kind() == reflect.Struct {
diff = fmt.Sprintf("%#v:\n\t-: %+v: \"%+v\"\n\t+: %+v: \"%+v\"\n", r.path, t, vx, t, vy)
} else {
diff = fmt.Sprintf("%#v:\n\t-: \"%+v\"\n\t+: \"%+v\"\n", r.path, vx, vy)
}
r.diffs = append(r.diffs, diff)
}
// PopStep implements the cmp.Reporter.
func (r *ShortDiffReporter) PopStep() {
r.path = r.path[:len(r.path)-1]
}
// Diff returns the generated short diff for this object.
// cmp.Equal should be called before this method.
func (r *ShortDiffReporter) Diff() (string, error) {
if r.err != nil {
return "", r.err
}
return strings.Join(r.diffs, ""), nil
}

18
vendor/github.com/knative/pkg/ptr/doc.go generated vendored Normal file
View File

@ -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 ptr holds utilities for taking pointer references to values.
package ptr

41
vendor/github.com/knative/pkg/ptr/ptr.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
/*
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 ptr
// Int32 is a helper for turning integers into pointers for use in
// API types that want *int32.
func Int32(i int32) *int32 {
return &i
}
// Int64 is a helper for turning integers into pointers for use in
// API types that want *int64.
func Int64(i int64) *int64 {
return &i
}
// Bool is a helper for turning bools into pointers for use in
// API types that want *bool.
func Bool(b bool) *bool {
return &b
}
// String is a helper for turning strings into pointers for use in
// API types that want *string.
func String(s string) *string {
return &s
}

View File

@ -0,0 +1,63 @@
/*
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 autoscaling
import (
"fmt"
"strconv"
"github.com/knative/pkg/apis"
)
func getIntGE0(m map[string]string, k string) (int64, *apis.FieldError) {
v, ok := m[k]
if !ok {
return 0, nil
}
i, err := strconv.ParseInt(v, 10, 32)
if err != nil || i < 0 {
return 0, &apis.FieldError{
Message: fmt.Sprintf("Invalid %s annotation value: must be an integer equal or greater than 0", k),
Paths: []string{k},
}
}
return i, nil
}
func ValidateAnnotations(annotations map[string]string) *apis.FieldError {
if len(annotations) == 0 {
return nil
}
min, err := getIntGE0(annotations, MinScaleAnnotationKey)
if err != nil {
return err
}
max, err := getIntGE0(annotations, MaxScaleAnnotationKey)
if err != nil {
return err
}
if max != 0 && max < min {
return &apis.FieldError{
Message: fmt.Sprintf("%s=%v is less than %s=%v", MaxScaleAnnotationKey, max, MinScaleAnnotationKey, min),
Paths: []string{MaxScaleAnnotationKey, MinScaleAnnotationKey},
}
}
return nil
}

View File

@ -16,6 +16,8 @@ limitations under the License.
package autoscaling
import "time"
const (
InternalGroupName = "autoscaling.internal.knative.dev"
@ -52,8 +54,80 @@ const (
// TargetAnnotationKey is the annotation to specify what metric value the
// PodAutoscaler should attempt to maintain. For example,
// autoscaling.knative.dev/metric: cpu
// autoscaling.knative.dev/target: 75 # target 75% cpu utilization
// autoscaling.knative.dev/target: "75" # target 75% cpu utilization
TargetAnnotationKey = GroupName + "/target"
// TargetMin is the minimum allowable target. Values less than
// zero don't make sense.
TargetMin = 1
// WindowAnnotationKey is the annotation to specify the time
// interval over which to calculate the average metric. Larger
// values result in more smoothing. For example,
// autoscaling.knative.dev/metric: concurrency
// autoscaling.knative.dev/window: "2m"
// Only the kpa.autoscaling.knative.dev class autoscaler supports
// the window annotation.
WindowAnnotationKey = GroupName + "/window"
// WindowMin is the minimum allowable stable autoscaling
// window. KPA-class autoscalers calculate the desired replica
// count every 2 seconds (tick-interval in config-autoscaler) so
// the closer the window gets to that value, the more likely data
// points will be missed entirely by the panic window which is
// smaller than the stable window. Anything less than 6 second
// isn't going to work well.
WindowMin = 6 * time.Second
// PanicWindowPercentageAnnotationKey is the annotation to
// specify the time interval over which to calculate the average
// metric during a spike. Where a spike is defined as the metric
// reaching panic level within the panic window (e.g. panic
// mode). Lower values make panic mode more sensitive. Note:
// Panic threshold can be overridden with the
// PanicThresholdPercentageAnnotationKey. For example,
// autoscaling.knative.dev/panicWindowPercentage: "5.0"
// autoscaling.knative.dev/panicThresholdPercentage: "150.0"
// Only the kpa.autoscaling.knative.dev class autoscaler supports
// the panicWindowPercentage annotation.
// Panic window is specified as a percentag to maintain the
// autoscaler's algorithm behavior when only the stable window is
// specified. The panic window will change along with the stable
// window at the default percentage.
PanicWindowPercentageAnnotationKey = GroupName + "/panicWindowPercentage"
// PanicWindowPercentageMin is the minimum allowable panic window
// percentage. The autoscaler calculates desired replicas every 2
// seconds (tick-interval in config-autoscaler), so a panic
// window less than 2 seconds will be missing data points. One
// percent is a very small ratio and would require a stable
// window of at least 3.4 minutes. Anything less doesn't make
// sense.
PanicWindowPercentageMin = 1.0
// PanicWindowPercentageMax is the maximum allowable panic window
// percentage. The KPA autoscaler's panic feature allows the
// autoscaler to be more responsive over a smaller time scale
// when necessary. So the panic window cannot be larger than the
// stable window.
PanicWindowPercentageMax = 100.0
// PanicThresholdPercentageAnnotationKey is the annotation to specify
// the level at what level panic mode will engage when reached within
// in the panic window. The level is defined as a percentage of
// the metric target. Lower values make panic mode more
// sensitive. For example,
// autoscaling.knative.dev/panicWindowPercentage: "5.0"
// autoscaling.knative.dev/panicThresholdPercentage: "150.0"
// Only the kpa.autoscaling.knative.dev class autoscaler supports
// the panicThresholdPercentage annotation
PanicThresholdPercentageAnnotationKey = GroupName + "/panicThresholdPercentage"
// PanicThresholdPercentageMin is the minimum allowable panic
// threshold percentage. The KPA autoscaler's panic feature
// allows the autoscaler to be more responsive over a smaller
// time scale when necessary. To prevent flapping, during panic
// mode the autoscaler never decreases the number of replicas. If
// the panic threshold was as small as the stable target, the
// autoscaler would always be panicking and the autoscaler would
// never scale down. One hundred and ten percent is about the
// smallest useful value.
PanicThresholdPercentageMin = 110.0
// KPALabelKey is the label key attached to a K8s Service to hint to the KPA
// which services/endpoints should trigger reconciles.

View File

@ -19,12 +19,12 @@ package v1alpha1
import (
"context"
"github.com/knative/pkg/apis"
"github.com/knative/serving/pkg/apis/autoscaling"
servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
)
func (r *PodAutoscaler) SetDefaults(ctx context.Context) {
r.Spec.SetDefaults(ctx)
r.Spec.SetDefaults(apis.WithinSpec(ctx))
if r.Annotations == nil {
r.Annotations = make(map[string]string)
}
@ -45,10 +45,4 @@ func (r *PodAutoscaler) SetDefaults(ctx context.Context) {
}
}
func (rs *PodAutoscalerSpec) SetDefaults(ctx context.Context) {
// When ConcurrencyModel is specified but ContainerConcurrency
// is not (0), use the ConcurrencyModel value.
if rs.ConcurrencyModel == servingv1alpha1.RevisionRequestConcurrencyModelSingle && rs.ContainerConcurrency == 0 {
rs.ContainerConcurrency = 1
}
}
func (rs *PodAutoscalerSpec) SetDefaults(ctx context.Context) {}

View File

@ -21,13 +21,14 @@ import (
"strconv"
"time"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"github.com/knative/pkg/apis"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"github.com/knative/serving/pkg/apis/autoscaling"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var podCondSet = duckv1alpha1.NewLivingConditionSet(
var podCondSet = apis.NewLivingConditionSet(
PodAutoscalerConditionActive,
)
@ -55,6 +56,15 @@ func (pa *PodAutoscaler) annotationInt32(key string) int32 {
return 0
}
func (pa *PodAutoscaler) annotationFloat64(key string) (float64, bool) {
if s, ok := pa.Annotations[key]; ok {
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f, true
}
}
return 0.0, false
}
// ScaleBounds returns scale bounds annotations values as a tuple:
// `(min, max int32)`. The value of 0 for any of min or max means the bound is
// not set
@ -77,78 +87,118 @@ func (pa *PodAutoscaler) Target() (target int32, ok bool) {
return 0, false
}
// IsReady looks at the conditions and if the Status has a condition
// PodAutoscalerConditionReady returns true if ConditionStatus is True
func (rs *PodAutoscalerStatus) IsReady() bool {
return podCondSet.Manage(rs).IsHappy()
// Window returns the window annotation value or false if not present.
func (pa *PodAutoscaler) Window() (window time.Duration, ok bool) {
if s, ok := pa.Annotations[autoscaling.WindowAnnotationKey]; ok {
d, err := time.ParseDuration(s)
if err != nil {
return 0, false
}
if d < autoscaling.WindowMin {
return 0, false
}
return d, true
}
return 0, false
}
// IsActivating assumes the pod autoscaler is Activating if it is neither
// Active nor Inactive
func (rs *PodAutoscalerStatus) IsActivating() bool {
cond := rs.GetCondition(PodAutoscalerConditionActive)
// PanicWindowPercentage returns panic window annotation value or false if not present.
func (pa *PodAutoscaler) PanicWindowPercentage() (percentage float64, ok bool) {
percentage, ok = pa.annotationFloat64(autoscaling.PanicWindowPercentageAnnotationKey)
if !ok || percentage > autoscaling.PanicWindowPercentageMax ||
percentage < autoscaling.PanicWindowPercentageMin {
return 0, false
}
return percentage, ok
}
// PanicThresholdPercentage return the panic target annotation value or false if not present.
func (pa *PodAutoscaler) PanicThresholdPercentage() (percentage float64, ok bool) {
percentage, ok = pa.annotationFloat64(autoscaling.PanicThresholdPercentageAnnotationKey)
if !ok || percentage < autoscaling.PanicThresholdPercentageMin {
return 0, false
}
return percentage, ok
}
// IsReady looks at the conditions and if the Status has a condition
// PodAutoscalerConditionReady returns true if ConditionStatus is True
func (pas *PodAutoscalerStatus) IsReady() bool {
return podCondSet.Manage(pas.duck()).IsHappy()
}
// IsActivating returns true if the pod autoscaler is Activating if it is neither
// Active nor Inactive
func (pas *PodAutoscalerStatus) IsActivating() bool {
cond := pas.GetCondition(PodAutoscalerConditionActive)
return cond != nil && cond.Status == corev1.ConditionUnknown
}
func (rs *PodAutoscalerStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
return podCondSet.Manage(rs).GetCondition(t)
// IsInactive returns true if the pod autoscaler is Inactive.
func (pas *PodAutoscalerStatus) IsInactive() bool {
cond := pas.GetCondition(PodAutoscalerConditionActive)
return cond != nil && cond.Status == corev1.ConditionFalse
}
func (rs *PodAutoscalerStatus) InitializeConditions() {
podCondSet.Manage(rs).InitializeConditions()
// GetCondition gets the condition `t`.
func (pas *PodAutoscalerStatus) GetCondition(t apis.ConditionType) *apis.Condition {
return podCondSet.Manage(pas.duck()).GetCondition(t)
}
func (rs *PodAutoscalerStatus) MarkActive() {
podCondSet.Manage(rs).MarkTrue(PodAutoscalerConditionActive)
// InitializeConditions initializes the conditionhs of the PA.
func (pas *PodAutoscalerStatus) InitializeConditions() {
podCondSet.Manage(pas.duck()).InitializeConditions()
}
func (rs *PodAutoscalerStatus) MarkActivating(reason, message string) {
podCondSet.Manage(rs).MarkUnknown(PodAutoscalerConditionActive, reason, message)
// MarkActive marks the PA active.
func (pas *PodAutoscalerStatus) MarkActive() {
podCondSet.Manage(pas.duck()).MarkTrue(PodAutoscalerConditionActive)
}
func (rs *PodAutoscalerStatus) MarkInactive(reason, message string) {
podCondSet.Manage(rs).MarkFalse(PodAutoscalerConditionActive, reason, message)
// MarkActivating marks the PA as activating.
func (pas *PodAutoscalerStatus) MarkActivating(reason, message string) {
podCondSet.Manage(pas.duck()).MarkUnknown(PodAutoscalerConditionActive, reason, message)
}
// MarkInactive marks the PA as inactive.
func (pas *PodAutoscalerStatus) MarkInactive(reason, message string) {
podCondSet.Manage(pas.duck()).MarkFalse(PodAutoscalerConditionActive, reason, message)
}
// MarkResourceNotOwned changes the "Active" condition to false to reflect that the
// resource of the given kind and name has already been created, and we do not own it.
func (rs *PodAutoscalerStatus) MarkResourceNotOwned(kind, name string) {
rs.MarkInactive("NotOwned",
func (pas *PodAutoscalerStatus) MarkResourceNotOwned(kind, name string) {
pas.MarkInactive("NotOwned",
fmt.Sprintf("There is an existing %s %q that we do not own.", kind, name))
}
// MarkResourceFailedCreation changes the "Active" condition to false to reflect that a
// critical resource of the given kind and name was unable to be created.
func (rs *PodAutoscalerStatus) MarkResourceFailedCreation(kind, name string) {
rs.MarkInactive("FailedCreate",
func (pas *PodAutoscalerStatus) MarkResourceFailedCreation(kind, name string) {
pas.MarkInactive("FailedCreate",
fmt.Sprintf("Failed to create %s %q.", kind, name))
}
// CanScaleToZero checks whether the pod autoscaler has been in an inactive state
// for at least the specified grace period.
func (rs *PodAutoscalerStatus) CanScaleToZero(gracePeriod time.Duration) bool {
if cond := rs.GetCondition(PodAutoscalerConditionActive); cond != nil {
switch cond.Status {
case corev1.ConditionFalse:
// Check that this PodAutoscaler has been inactive for
// at least the grace period.
return time.Now().After(cond.LastTransitionTime.Inner.Add(gracePeriod))
}
}
return false
func (pas *PodAutoscalerStatus) CanScaleToZero(gracePeriod time.Duration) bool {
return pas.inStatusFor(corev1.ConditionFalse, gracePeriod)
}
// CanMarkInactive checks whether the pod autoscaler has been in an active state
// for at least the specified idle period.
func (rs *PodAutoscalerStatus) CanMarkInactive(idlePeriod time.Duration) bool {
if cond := rs.GetCondition(PodAutoscalerConditionActive); cond != nil {
switch cond.Status {
case corev1.ConditionTrue:
// Check that this PodAutoscaler has been active for
// at least the grace period.
return time.Now().After(cond.LastTransitionTime.Inner.Add(idlePeriod))
}
}
return false
func (pas *PodAutoscalerStatus) CanMarkInactive(idlePeriod time.Duration) bool {
return pas.inStatusFor(corev1.ConditionTrue, idlePeriod)
}
// inStatusFor returns true if the PodAutoscalerStatus's Active condition has stayed in
// the specified status for at least the specified duration. Otherwise it returns false,
// including when the status is undetermined (Active condition is not found.)
func (pas *PodAutoscalerStatus) inStatusFor(status corev1.ConditionStatus, dur time.Duration) bool {
cond := pas.GetCondition(PodAutoscalerConditionActive)
return cond != nil && cond.Status == status && time.Now().After(cond.LastTransitionTime.Inner.Add(dur))
}
func (pas *PodAutoscalerStatus) duck() *duckv1beta1.Status {
return (*duckv1beta1.Status)(&pas.Status)
}

View File

@ -18,11 +18,12 @@ package v1alpha1
import (
"github.com/knative/pkg/apis"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"github.com/knative/pkg/kmeta"
net "github.com/knative/serving/pkg/apis/networking"
servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
servingv1beta1 "github.com/knative/serving/pkg/apis/serving/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -52,7 +53,6 @@ var (
// Check that PodAutoscaler can be validated, can be defaulted, and has immutable fields.
_ apis.Validatable = (*PodAutoscaler)(nil)
_ apis.Defaultable = (*PodAutoscaler)(nil)
_ apis.Immutable = (*PodAutoscaler)(nil)
// Check that we can create OwnerReferences to a PodAutoscaler.
_ kmeta.OwnerRefable = (*PodAutoscaler)(nil)
@ -71,11 +71,9 @@ type PodAutoscalerSpec struct {
// +optional
DeprecatedGeneration int64 `json:"generation,omitempty"`
// ConcurrencyModel specifies the desired concurrency model
// (Single or Multi) for the scale target. Defaults to Multi.
// Deprecated in favor of ContainerConcurrency.
// DeprecatedConcurrencyModel no longer does anything, use ContainerConcurrency.
// +optional
ConcurrencyModel servingv1alpha1.RevisionRequestConcurrencyModelType `json:"concurrencyModel,omitempty"`
DeprecatedConcurrencyModel servingv1alpha1.RevisionRequestConcurrencyModelType `json:"concurrencyModel,omitempty"`
// ContainerConcurrency specifies the maximum allowed
// in-flight (concurrent) requests per container of the Revision.
@ -83,15 +81,15 @@ type PodAutoscalerSpec struct {
// This field replaces ConcurrencyModel. A value of `1`
// is equivalent to `Single` and `0` is equivalent to `Multi`.
// +optional
ContainerConcurrency servingv1alpha1.RevisionContainerConcurrencyType `json:"containerConcurrency,omitempty"`
ContainerConcurrency servingv1beta1.RevisionContainerConcurrencyType `json:"containerConcurrency,omitempty"`
// ScaleTargetRef defines the /scale-able resource that this PodAutoscaler
// is responsible for quickly right-sizing.
ScaleTargetRef autoscalingv1.CrossVersionObjectReference `json:"scaleTargetRef"`
ScaleTargetRef corev1.ObjectReference `json:"scaleTargetRef"`
// ServiceName holds the name of a core Kubernetes Service resource that
// DeprecatedServiceName holds the name of a core Kubernetes Service resource that
// load balances over the pods referenced by the ScaleTargetRef.
ServiceName string `json:"serviceName"`
DeprecatedServiceName string `json:"serviceName"`
// The application-layer protocol. Matches `ProtocolType` inferred from the revision spec.
ProtocolType net.ProtocolType
@ -100,13 +98,19 @@ type PodAutoscalerSpec struct {
const (
// PodAutoscalerConditionReady is set when the revision is starting to materialize
// runtime resources, and becomes true when those resources are ready.
PodAutoscalerConditionReady = duckv1alpha1.ConditionReady
PodAutoscalerConditionReady = apis.ConditionReady
// PodAutoscalerConditionActive is set when the PodAutoscaler's ScaleTargetRef is receiving traffic.
PodAutoscalerConditionActive duckv1alpha1.ConditionType = "Active"
PodAutoscalerConditionActive apis.ConditionType = "Active"
)
// PodAutoscalerStatus communicates the observed state of the PodAutoscaler (from the controller).
type PodAutoscalerStatus duckv1alpha1.Status
type PodAutoscalerStatus struct {
duckv1beta1.Status
// ServiceName is the K8s Service name that serves the revision, scaled by this PA.
// The service is created and owned by the ServerlessService object owned by this PA.
ServiceName string `json:"serviceName"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -24,16 +24,44 @@ import (
"github.com/knative/pkg/apis"
"github.com/knative/pkg/kmp"
"github.com/knative/serving/pkg/apis/autoscaling"
servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
"github.com/knative/serving/pkg/apis/serving"
"k8s.io/apimachinery/pkg/api/equality"
)
func (pa *PodAutoscaler) Validate(ctx context.Context) *apis.FieldError {
return servingv1alpha1.ValidateObjectMetadata(pa.GetObjectMeta()).
ViaField("metadata").
Also(pa.Spec.Validate(ctx).ViaField("spec")).
Also(pa.validateMetric())
errs := serving.ValidateObjectMetadata(pa.GetObjectMeta()).ViaField("metadata")
errs = errs.Also(pa.validateMetric())
errs = errs.Also(pa.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec"))
if apis.IsInUpdate(ctx) {
original := apis.GetBaseline(ctx).(*PodAutoscaler)
errs = errs.Also(pa.checkImmutableFields(ctx, original))
}
return errs
}
func (current *PodAutoscaler) checkImmutableFields(ctx context.Context, original *PodAutoscaler) *apis.FieldError {
if diff, err := compareSpec(original, current); err != nil {
return &apis.FieldError{
Message: "Failed to diff PodAutoscaler",
Paths: []string{"spec"},
Details: err.Error(),
}
} else if diff != "" {
return &apis.FieldError{
Message: "Immutable fields changed (-old +new)",
Paths: []string{"spec"},
Details: diff,
}
}
// Verify the PA class does not change.
// For backward compatibility, we allow a new class where there was none before.
if oldClass, newClass, annotationChanged := classAnnotationChanged(original, current); annotationChanged {
return &apis.FieldError{
Message: fmt.Sprintf("Immutable class annotation changed (-%q +%q)", oldClass, newClass),
Paths: []string{"annotations[autoscaling.knative.dev/class]"},
}
}
return nil
}
// Validate validates PodAutoscaler Spec.
@ -41,44 +69,21 @@ func (rs *PodAutoscalerSpec) Validate(ctx context.Context) *apis.FieldError {
if equality.Semantic.DeepEqual(rs, &PodAutoscalerSpec{}) {
return apis.ErrMissingField(apis.CurrentField)
}
errs := validateReference(rs.ScaleTargetRef).ViaField("scaleTargetRef")
if rs.ServiceName == "" {
errs = errs.Also(apis.ErrMissingField("serviceName"))
}
if err := rs.ConcurrencyModel.Validate(ctx); err != nil {
errs = errs.Also(err.ViaField("concurrencyModel"))
} else if err := servingv1alpha1.ValidateContainerConcurrency(rs.ContainerConcurrency, rs.ConcurrencyModel); err != nil {
errs = errs.Also(err)
}
return errs.Also(validateSKSFields(rs))
errs := serving.ValidateNamespacedObjectReference(&rs.ScaleTargetRef).ViaField("scaleTargetRef")
errs = errs.Also(rs.ContainerConcurrency.Validate(ctx).
ViaField("containerConcurrency"))
return errs.Also(validateSKSFields(ctx, rs))
}
func validateSKSFields(rs *PodAutoscalerSpec) *apis.FieldError {
func validateSKSFields(ctx context.Context, rs *PodAutoscalerSpec) *apis.FieldError {
var all *apis.FieldError
// TODO(vagababov) stop permitting empty protocol type, once SKS controller is live.
if string(rs.ProtocolType) != "" {
all = all.Also(rs.ProtocolType.Validate()).ViaField("protocolType")
all = all.Also(rs.ProtocolType.Validate(ctx)).ViaField("protocolType")
}
return all
}
func validateReference(ref autoscalingv1.CrossVersionObjectReference) *apis.FieldError {
if equality.Semantic.DeepEqual(ref, autoscalingv1.CrossVersionObjectReference{}) {
return apis.ErrMissingField(apis.CurrentField)
}
var errs *apis.FieldError
if ref.Kind == "" {
errs = errs.Also(apis.ErrMissingField("kind"))
}
if ref.Name == "" {
errs = errs.Also(apis.ErrMissingField("name"))
}
if ref.APIVersion == "" {
errs = errs.Also(apis.ErrMissingField("apiVersion"))
}
return errs
}
func (pa *PodAutoscaler) validateMetric() *apis.FieldError {
if metric, ok := pa.Annotations[autoscaling.MetricAnnotationKey]; ok {
switch pa.Class() {
@ -106,13 +111,7 @@ func (pa *PodAutoscaler) validateMetric() *apis.FieldError {
return nil
}
// CheckImmutableFields checks the immutability of the PodAutoscaler.
func (current *PodAutoscaler) CheckImmutableFields(ctx context.Context, og apis.Immutable) *apis.FieldError {
original, ok := og.(*PodAutoscaler)
if !ok {
return &apis.FieldError{Message: "The provided original was not a PodAutoscaler"}
}
func compareSpec(original *PodAutoscaler, current *PodAutoscaler) (string, error) {
// TODO(vagababov): remove after 0.6. This is temporary plug for backwards compatibility.
opt := cmp.FilterPath(
func(p cmp.Path) bool {
@ -120,28 +119,18 @@ func (current *PodAutoscaler) CheckImmutableFields(ctx context.Context, og apis.
},
cmp.Ignore(),
)
if diff, err := kmp.SafeDiff(original.Spec, current.Spec, opt); err != nil {
return &apis.FieldError{
Message: "Failed to diff PodAutoscaler",
Paths: []string{"spec"},
Details: err.Error(),
}
} else if diff != "" {
return &apis.FieldError{
Message: "Immutable fields changed (-old +new)",
Paths: []string{"spec"},
Details: diff,
}
}
// Verify the PA class does not change.
// For backward compatibility, we allow a new class where there was none before.
if oldClass, ok := original.Annotations[autoscaling.ClassAnnotationKey]; ok {
if newClass, ok := current.Annotations[autoscaling.ClassAnnotationKey]; !ok || oldClass != newClass {
return &apis.FieldError{
Message: fmt.Sprintf("Immutable class annotation changed (-%q +%q)", oldClass, newClass),
Paths: []string{"annotations[autoscaling.knative.dev/class]"},
}
}
}
return nil
return kmp.ShortDiff(original.Spec, current.Spec, opt)
}
func classAnnotationChanged(original *PodAutoscaler, current *PodAutoscaler) (string, string, bool) {
oldClass, ok := original.Annotations[autoscaling.ClassAnnotationKey]
if !ok {
return "", "", false
}
newClass, ok := current.Annotations[autoscaling.ClassAnnotationKey]
if ok && oldClass == newClass {
return "", "", false
}
return oldClass, newClass, true
}

View File

@ -0,0 +1,112 @@
/*
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 v1alpha1
import (
"github.com/knative/pkg/apis"
"github.com/knative/pkg/apis/duck"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// PodScalable is a duck type that the resources referenced by the
// PodAutoscaler's ScaleTargetRef must implement. They must also
// implement the `/scale` sub-resource for use with `/scale` based
// implementations (e.g. HPA), but this further constrains the shape
// the referenced resources may take.
type PodScalable struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PodScalableSpec `json:"spec"`
Status PodScalableStatus `json:"status"`
}
// PodScalableSpec is the specification for the desired state of a
// PodScalable (or at least our shared portion).
type PodScalableSpec struct {
Replicas *int32 `json:"replicas,omitempty"`
Selector *metav1.LabelSelector `json:"selector"`
Template corev1.PodTemplateSpec `json:"template"`
}
// PodScalableStatus is the observed state of a PodScalable (or at
// least our shared portion).
type PodScalableStatus struct {
Replicas int32 `json:"replicas,omitempty"`
}
var _ duck.Populatable = (*PodScalable)(nil)
var _ duck.Implementable = (*PodScalable)(nil)
var _ apis.Listable = (*PodScalable)(nil)
// GetFullType implements duck.Implementable
func (*PodScalable) GetFullType() duck.Populatable {
return &PodScalable{}
}
// Populate implements duck.Populatable
func (t *PodScalable) Populate() {
twelve := int32(12)
t.Spec = PodScalableSpec{
Replicas: &twelve,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
MatchExpressions: []metav1.LabelSelectorRequirement{{
Key: "foo",
Operator: "In",
Values: []string{"baz", "blah"},
}},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "container-name",
Image: "container-image:latest",
}},
},
},
}
t.Status = PodScalableStatus{
Replicas: 42,
}
}
// GetListType implements apis.Listable
func (*PodScalable) GetListType() runtime.Object {
return &PodScalableList{}
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// PodScalableList is a list of PodScalable resources
type PodScalableList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []PodScalable `json:"items"`
}

View File

@ -16,12 +16,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
duck_v1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@ -49,9 +49,8 @@ func (in *PodAutoscaler) DeepCopy() *PodAutoscaler {
func (in *PodAutoscaler) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -83,9 +82,8 @@ func (in *PodAutoscalerList) DeepCopy() *PodAutoscalerList {
func (in *PodAutoscalerList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -108,13 +106,7 @@ func (in *PodAutoscalerSpec) DeepCopy() *PodAutoscalerSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodAutoscalerStatus) DeepCopyInto(out *PodAutoscalerStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make(duck_v1alpha1.Conditions, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Status.DeepCopyInto(&out.Status)
return
}
@ -127,3 +119,107 @@ func (in *PodAutoscalerStatus) DeepCopy() *PodAutoscalerStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodScalable) DeepCopyInto(out *PodScalable) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodScalable.
func (in *PodScalable) DeepCopy() *PodScalable {
if in == nil {
return nil
}
out := new(PodScalable)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PodScalable) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodScalableList) DeepCopyInto(out *PodScalableList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]PodScalable, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodScalableList.
func (in *PodScalableList) DeepCopy() *PodScalableList {
if in == nil {
return nil
}
out := new(PodScalableList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PodScalableList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodScalableSpec) DeepCopyInto(out *PodScalableSpec) {
*out = *in
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = new(int32)
**out = **in
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
in.Template.DeepCopyInto(&out.Template)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodScalableSpec.
func (in *PodScalableSpec) DeepCopy() *PodScalableSpec {
if in == nil {
return nil
}
out := new(PodScalableSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodScalableStatus) DeepCopyInto(out *PodScalableStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodScalableStatus.
func (in *PodScalableStatus) DeepCopy() *PodScalableStatus {
if in == nil {
return nil
}
out := new(PodScalableStatus)
in.DeepCopyInto(out)
return out
}

View File

@ -55,20 +55,26 @@ func NewDefaultsConfigFromMap(data map[string]string) (*Defaults, error) {
// Process resource quantity fields
for _, rsrc := range []struct {
key string
field *resource.Quantity
// specified exactly when optional
defaultValue resource.Quantity
field **resource.Quantity
}{{
key: "revision-cpu-request",
field: &nc.RevisionCPURequest,
defaultValue: DefaultRevisionCPURequest,
key: "revision-cpu-request",
field: &nc.RevisionCPURequest,
}, {
key: "revision-memory-request",
field: &nc.RevisionMemoryRequest,
}, {
key: "revision-cpu-limit",
field: &nc.RevisionCPULimit,
}, {
key: "revision-memory-limit",
field: &nc.RevisionMemoryLimit,
}} {
if raw, ok := data[rsrc.key]; !ok {
*rsrc.field = rsrc.defaultValue
*rsrc.field = nil
} else if val, err := resource.ParseQuantity(raw); err != nil {
return nil, err
} else {
*rsrc.field = val
*rsrc.field = &val
}
}
@ -85,15 +91,12 @@ const (
DefaultRevisionTimeoutSeconds = 5 * 60
)
// Pseudo-constants
var (
// DefaultRevisionCPURequest will be set if resources.requests.cpu is not specified.
DefaultRevisionCPURequest = resource.MustParse("400m")
)
// Defaults includes the default values to be populated by the webhook.
type Defaults struct {
RevisionTimeoutSeconds int64
RevisionCPURequest resource.Quantity
RevisionCPURequest *resource.Quantity
RevisionCPULimit *resource.Quantity
RevisionMemoryRequest *resource.Quantity
RevisionMemoryLimit *resource.Quantity
}

View File

@ -16,14 +16,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
// 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 *Defaults) DeepCopyInto(out *Defaults) {
*out = *in
out.RevisionCPURequest = in.RevisionCPURequest.DeepCopy()
if in.RevisionCPURequest != nil {
in, out := &in.RevisionCPURequest, &out.RevisionCPURequest
x := (*in).DeepCopy()
*out = &x
}
if in.RevisionCPULimit != nil {
in, out := &in.RevisionCPULimit, &out.RevisionCPULimit
x := (*in).DeepCopy()
*out = &x
}
if in.RevisionMemoryRequest != nil {
in, out := &in.RevisionMemoryRequest, &out.RevisionMemoryRequest
x := (*in).DeepCopy()
*out = &x
}
if in.RevisionMemoryLimit != nil {
in, out := &in.RevisionMemoryLimit, &out.RevisionMemoryLimit
x := (*in).DeepCopy()
*out = &x
}
return
}

View File

@ -16,7 +16,11 @@ limitations under the License.
package networking
import "github.com/knative/pkg/apis"
import (
"context"
"github.com/knative/pkg/apis"
)
// This files contains the versionless types and enums that are strongly
// unlikely to change from version to version.
@ -33,10 +37,10 @@ const (
)
// Validate validates that ProtocolType has a correct enum value.
func (p ProtocolType) Validate() *apis.FieldError {
func (p ProtocolType) Validate(context.Context) *apis.FieldError {
switch p {
case ProtocolH2C, ProtocolHTTP1:
return nil
}
return apis.ErrInvalidValue(string(p), apis.CurrentField)
return apis.ErrInvalidValue(p, apis.CurrentField)
}

View File

@ -0,0 +1,63 @@
/*
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 networking
// The ports we setup on our services.
const (
// ServiceHTTPPort is the port that we setup our Serving and Activator K8s services for
// HTTP/1 endpoints.
ServiceHTTPPort = 80
// ServiceHTTP2Port is the port that we setup our Serving and Activator K8s services for
// HTTP/2 endpoints.
ServiceHTTP2Port = 81
// BackendHTTPPort is the backend, i.e. `targetPort` that we setup for HTTP services.
BackendHTTPPort = 8012
// BackendHTTP2Port is the backend, i.e. `targetPort` that we setup for HTTP services.
BackendHTTP2Port = 8013
// RequestQueueAdminPort specifies the port number for
// health check and lifecyle hooks for queue-proxy.
RequestQueueAdminPort = 8022
// RequestQueueMetricsPort specifies the port number for metrics emitted
// by queue-proxy.
RequestQueueMetricsPort = 9090
// ServicePortNameHTTP1 is the name of the external port of the service for HTTP/1.1
ServicePortNameHTTP1 = "http"
// ServicePortNameH2C is the name of the external port of the service for HTTP/2
ServicePortNameH2C = "http2"
)
// ServicePortName returns the port for the app level protocol.
func ServicePortName(proto ProtocolType) string {
if proto == ProtocolH2C {
return ServicePortNameH2C
}
return ServicePortNameHTTP1
}
// ServicePort chooses the service (load balancer) port for the public service.
func ServicePort(proto ProtocolType) int {
if proto == ProtocolH2C {
return ServiceHTTP2Port
}
return ServiceHTTPPort
}

View File

@ -16,7 +16,12 @@ limitations under the License.
package networking
import (
"time"
)
const (
// GroupName is the name for the networking API group.
GroupName = "networking.internal.knative.dev"
// IngressClassAnnotationKey is the annotation for the
@ -37,4 +42,45 @@ const (
// IngressLabelKey is the label key attached to underlying network programming
// resources to indicate which ClusterIngress triggered their creation.
IngressLabelKey = GroupName + "/clusteringress"
// SKSLabelKey is the label key that SKS Controller attaches to the
// underlying resources it controls.
SKSLabelKey = GroupName + "/serverlessservice"
// ServiceTypeKey is the label key attached to a service specifying the type of service.
// e.g. Public, Metrics
ServiceTypeKey = GroupName + "/serviceType"
// OriginSecretNameLabelKey is the label key attached to the TLS secret to indicate
// the name of the origin secret that the TLS secret is copied from.
OriginSecretNameLabelKey = GroupName + "/originSecretName"
// OriginSecretNamespaceLabelKey is the label key attached to the TLS secret
// to indicate the namespace of the origin secret that the TLS secret is copied from.
OriginSecretNamespaceLabelKey = GroupName + "/originSecretNamespace"
)
// ServiceType is the enumeration type for the Kubernetes services
// that we have in our system, classified by usage purpose.
type ServiceType string
const (
// ServiceTypePrivate is the label value for internal only services
// for user applications.
ServiceTypePrivate ServiceType = "Private"
// ServiceTypePublic is the label value for externally reachable
// services for user applications.
ServiceTypePublic ServiceType = "Public"
// ServiceTypeMetrics is the label value for Metrics services. Such services
// are used for meric scraping.
ServiceTypeMetrics ServiceType = "Metrics"
)
// Pseudo-constants
var (
// DefaultTimeout will be set if timeout not specified.
DefaultTimeout = 10 * time.Minute
// DefaultRetryCount will be set if Attempts not specified.
DefaultRetryCount = 3
)

View File

@ -17,7 +17,10 @@ limitations under the License.
package v1alpha1
import (
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"fmt"
"github.com/knative/pkg/apis"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@ -28,7 +31,24 @@ func (cs *CertificateStatus) InitializeConditions() {
// MarkReady marks the certificate as ready to use.
func (cs *CertificateStatus) MarkReady() {
certificateCondSet.Manage(cs).MarkTrue(CertificateCondidtionReady)
certificateCondSet.Manage(cs).MarkTrue(CertificateConditionReady)
}
// MarkUnknown marks the certificate status as unknown.
func (cs *CertificateStatus) MarkUnknown(reason, message string) {
certificateCondSet.Manage(cs).MarkUnknown(CertificateConditionReady, reason, message)
}
// MarkNotReady marks the certificate as not ready.
func (cs *CertificateStatus) MarkNotReady(reason, message string) {
certificateCondSet.Manage(cs).MarkFalse(CertificateConditionReady, reason, message)
}
// MarkResourceNotOwned changes the ready condition to false to reflect that we don't own the
// resource of the given kind and name.
func (cs *CertificateStatus) MarkResourceNotOwned(kind, name string) {
certificateCondSet.Manage(cs).MarkFalse(CertificateConditionReady, "NotOwned",
fmt.Sprintf("There is an existing %s %q that we do not own.", kind, name))
}
// IsReady returns true is the Certificate is ready.
@ -37,7 +57,7 @@ func (cs *CertificateStatus) IsReady() bool {
}
// GetCondition gets a speicifc condition of the Certificate status.
func (cs *CertificateStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
func (cs *CertificateStatus) GetCondition(t apis.ConditionType) *apis.Condition {
return certificateCondSet.Manage(cs).GetCondition(t)
}
@ -45,12 +65,16 @@ func (cs *CertificateStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1a
const (
// CertificateConditionReady is set when the requested certificate
// is provioned and valid.
CertificateCondidtionReady = duckv1alpha1.ConditionReady
CertificateConditionReady = apis.ConditionReady
)
var certificateCondSet = duckv1alpha1.NewLivingConditionSet(CertificateCondidtionReady)
var certificateCondSet = apis.NewLivingConditionSet(CertificateConditionReady)
// GetGroupVersionKind returns the GroupVersionKind of Certificate.
func (c *Certificate) GetGroupVersionKind() schema.GroupVersionKind {
return SchemeGroupVersion.WithKind("Certificate")
}
func (cs *CertificateStatus) duck() *duckv1beta1.Status {
return &cs.Status
}

View File

@ -18,7 +18,7 @@ package v1alpha1
import (
"github.com/knative/pkg/apis"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"github.com/knative/pkg/kmeta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -83,7 +83,11 @@ type CertificateSpec struct {
// CertificateStatus defines the observed state of a `Certificate`.
type CertificateStatus struct {
duckv1alpha1.Status `json:",inline"`
// When Certificate status is ready, it means:
// - The target secret exists
// - The target secret contains a certificate that has not expired
// - The target secret contains a private key valid for the certificate
duckv1beta1.Status `json:",inline"`
// The expiration time of the TLS certificate stored in the secret named
// by this resource in spec.secretName.

View File

@ -24,7 +24,7 @@ import (
// Validate inspects and validates Certificate object.
func (c *Certificate) Validate(ctx context.Context) *apis.FieldError {
return c.Spec.Validate(ctx).ViaField("spec")
return c.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec")
}
// Validate inspects and validates CertificateSpec object.

View File

@ -18,20 +18,15 @@ package v1alpha1
import (
"context"
"time"
"github.com/knative/pkg/apis"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
// DefaultTimeout will be set if timeout not specified.
DefaultTimeout = 10 * time.Minute
// DefaultRetryCount will be set if Attempts not specified.
DefaultRetryCount = 3
"github.com/knative/serving/pkg/apis/networking"
)
func (c *ClusterIngress) SetDefaults(ctx context.Context) {
c.Spec.SetDefaults(ctx)
c.Spec.SetDefaults(apis.WithinSpec(ctx))
}
func (c *IngressSpec) SetDefaults(ctx context.Context) {
@ -74,16 +69,16 @@ func (p *HTTPClusterIngressPath) SetDefaults(ctx context.Context) {
}
if p.Timeout == nil {
p.Timeout = &metav1.Duration{Duration: DefaultTimeout}
p.Timeout = &metav1.Duration{Duration: networking.DefaultTimeout}
}
if p.Retries == nil {
p.Retries = &HTTPRetry{
PerTryTimeout: &metav1.Duration{Duration: DefaultTimeout},
Attempts: DefaultRetryCount,
PerTryTimeout: &metav1.Duration{Duration: networking.DefaultTimeout},
Attempts: networking.DefaultRetryCount,
}
}
if p.Retries.PerTryTimeout == nil {
p.Retries.PerTryTimeout = &metav1.Duration{Duration: DefaultTimeout}
p.Retries.PerTryTimeout = &metav1.Duration{Duration: networking.DefaultTimeout}
}
}

View File

@ -19,11 +19,12 @@ package v1alpha1
import (
"fmt"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"github.com/knative/pkg/apis"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var clusterIngressCondSet = duckv1alpha1.NewLivingConditionSet(
var clusterIngressCondSet = apis.NewLivingConditionSet(
ClusterIngressConditionNetworkConfigured,
ClusterIngressConditionLoadBalancerReady,
)
@ -37,7 +38,7 @@ func (ci *ClusterIngress) IsPublic() bool {
return ci.Spec.Visibility == "" || ci.Spec.Visibility == IngressVisibilityExternalIP
}
func (cis *IngressStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
func (cis *IngressStatus) GetCondition(t apis.ConditionType) *apis.Condition {
return clusterIngressCondSet.Manage(cis).GetCondition(t)
}
@ -71,3 +72,7 @@ func (cis *IngressStatus) MarkLoadBalancerReady(lbs []LoadBalancerIngressStatus)
func (cis *IngressStatus) IsReady() bool {
return clusterIngressCondSet.Manage(cis).IsHappy()
}
func (cis *IngressStatus) duck() *duckv1beta1.Status {
return &cis.Status
}

View File

@ -18,7 +18,7 @@ package v1alpha1
import (
"github.com/knative/pkg/apis"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"github.com/knative/pkg/kmeta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -142,7 +142,7 @@ type ClusterIngressTLS struct {
SecretNamespace string `json:"secretNamespace,omitempty"`
// ServerCertificate identifies the certificate filename in the secret.
// Defaults to `tls.cert`.
// Defaults to `tls.crt`.
// +optional
ServerCertificate string `json:"serverCertificate,omitempty"`
@ -238,6 +238,13 @@ type ClusterIngressBackendSplit struct {
//
// NOTE: This differs from K8s Ingress to allow percentage split.
Percent int `json:"percent,omitempty"`
// AppendHeaders allow specifying additional HTTP headers to add
// before forwarding a request to the destination service.
//
// NOTE: This differs from K8s Ingress which doesn't allow header appending.
// +optional
AppendHeaders map[string]string `json:"appendHeaders,omitempty"`
}
// ClusterIngressBackend describes all endpoints for a given service and port.
@ -265,7 +272,7 @@ type HTTPRetry struct {
// IngressStatus describe the current state of the ClusterIngress.
type IngressStatus struct {
duckv1alpha1.Status `json:",inline"`
duckv1beta1.Status `json:",inline"`
// LoadBalancer contains the current status of the load-balancer.
// +optional
@ -310,15 +317,15 @@ type LoadBalancerIngressStatus struct {
const (
// ClusterIngressConditionReady is set when the clusterIngress networking setting is
// configured and it has a load balancer address.
ClusterIngressConditionReady = duckv1alpha1.ConditionReady
ClusterIngressConditionReady = apis.ConditionReady
// ClusterIngressConditionNetworkConfigured is set when the ClusterIngress's underlying
// network programming has been configured. This doesn't include conditions of the
// backends, so even if this should remain true when network is configured and backends
// are not ready.
ClusterIngressConditionNetworkConfigured duckv1alpha1.ConditionType = "NetworkConfigured"
ClusterIngressConditionNetworkConfigured apis.ConditionType = "NetworkConfigured"
// ClusterIngressConditionLoadBalancerReady is set when the ClusterIngress has
// a ready LoadBalancer.
ClusterIngressConditionLoadBalancerReady duckv1alpha1.ConditionType = "LoadBalancerReady"
ClusterIngressConditionLoadBalancerReady apis.ConditionType = "LoadBalancerReady"
)

View File

@ -27,7 +27,7 @@ import (
// Validate inspects and validates ClusterIngress object.
func (ci *ClusterIngress) Validate(ctx context.Context) *apis.FieldError {
return ci.Spec.Validate(ctx).ViaField("spec")
return ci.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec")
}
// Validate inspects and validates IngressSpec object.
@ -122,7 +122,7 @@ func (s ClusterIngressBackendSplit) Validate(ctx context.Context) *apis.FieldErr
var all *apis.FieldError
// Percent must be between 0 and 100.
if s.Percent < 0 || s.Percent > 100 {
all = all.Also(apis.ErrInvalidValue(strconv.Itoa(s.Percent), "percent"))
all = all.Also(apis.ErrInvalidValue(s.Percent, "percent"))
}
return all.Also(s.ClusterIngressBackend.Validate(ctx))
}
@ -151,7 +151,7 @@ func (b ClusterIngressBackend) Validate(ctx context.Context) *apis.FieldError {
func (r *HTTPRetry) Validate(ctx context.Context) *apis.FieldError {
// Attempts must be greater than 0.
if r.Attempts < 0 {
return apis.ErrInvalidValue(strconv.Itoa(r.Attempts), "attempts")
return apis.ErrInvalidValue(r.Attempts, "attempts")
}
return nil
}

View File

@ -17,11 +17,12 @@ limitations under the License.
package v1alpha1
import (
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"github.com/knative/pkg/apis"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var serverlessServiceCondSet = duckv1alpha1.NewLivingConditionSet(
var serverlessServiceCondSet = apis.NewLivingConditionSet(
ServerlessServiceConditionEndspointsPopulated,
)
@ -31,7 +32,7 @@ func (ss *ServerlessService) GetGroupVersionKind() schema.GroupVersionKind {
}
// GetCondition returns the value of the condition `t`.
func (sss *ServerlessServiceStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
func (sss *ServerlessServiceStatus) GetCondition(t apis.ConditionType) *apis.Condition {
return serverlessServiceCondSet.Manage(sss).GetCondition(t)
}
@ -40,12 +41,30 @@ func (sss *ServerlessServiceStatus) InitializeConditions() {
serverlessServiceCondSet.Manage(sss).InitializeConditions()
}
// MarkEndpointsPopulated marks the ServerlessServiceStatus endpoints populated condition to true.
func (sss *ServerlessServiceStatus) MarkEndpointsPopulated() {
// MarkEndpointsReady marks the ServerlessServiceStatus endpoints populated condition to true.
func (sss *ServerlessServiceStatus) MarkEndpointsReady() {
serverlessServiceCondSet.Manage(sss).MarkTrue(ServerlessServiceConditionEndspointsPopulated)
}
// MarkEndpointsNotOwned marks that we don't own K8s service.
func (sss *ServerlessServiceStatus) MarkEndpointsNotOwned(kind, name string) {
serverlessServiceCondSet.Manage(sss).MarkFalse(
ServerlessServiceConditionEndspointsPopulated, "NotOwned",
"Resource %s of type %s is not owned by SKS", name, kind)
}
// MarkEndpointsNotReady marks the ServerlessServiceStatus endpoints populated conditiohn to unknown.
func (sss *ServerlessServiceStatus) MarkEndpointsNotReady(reason string) {
serverlessServiceCondSet.Manage(sss).MarkUnknown(
ServerlessServiceConditionEndspointsPopulated, reason,
"K8s Service is not ready")
}
// IsReady returns true if ServerlessService is ready.
func (sss *ServerlessServiceStatus) IsReady() bool {
return serverlessServiceCondSet.Manage(sss).IsHappy()
}
func (sss *ServerlessServiceStatus) duck() *duckv1beta1.Status {
return &sss.Status
}

View File

@ -18,9 +18,10 @@ package v1alpha1
import (
"github.com/knative/pkg/apis"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
"github.com/knative/pkg/kmeta"
networking "github.com/knative/serving/pkg/apis/networking"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -92,10 +93,9 @@ type ServerlessServiceSpec struct {
// Mode describes the mode of operation of the ServerlessService.
Mode ServerlessServiceOperationMode `json:"mode,omitempty"`
// Selector describes the pod labels for selection of pods for the
// revision. Same as K8s service selector.
// See: https://kubernetes.io/docs/concepts/services-networking/service/.
Selector map[string]string `json:"selector,omitempty"`
// ObjectRef defines the resource that this ServerlessService
// is responsible for making "serverless".
ObjectRef corev1.ObjectReference `json:"objectRef"`
// The application-layer protocol. Matches `RevisionProtocolType` set on the owning pa/revision.
// serving imports networking, so just use string.
@ -104,21 +104,26 @@ type ServerlessServiceSpec struct {
// ServerlessServiceStatus describes the current state of the ServerlessService.
type ServerlessServiceStatus struct {
duckv1alpha1.Status `json:",inline"`
duckv1beta1.Status `json:",inline"`
// ServiceName holds the name of a core K8s Service resource that
// load balances over the pods backing this Revision (activator or revision).
// +optional
ServiceName string `json:"serviceName,omitempty"`
// PrivateServiceName holds the name of a core K8s Service resource that
// load balances over the user service pods backing this Revision.
// +optional
PrivateServiceName string `json:"privateServiceName,omitempty"`
}
// ConditionType represents a ServerlessService condition value
const (
// ServerlessServiceConditionReady is set when the clusterIngress networking setting is
// configured and it has a load balancer address.
ServerlessServiceConditionReady = duckv1alpha1.ConditionReady
ServerlessServiceConditionReady = apis.ConditionReady
// ServerlessServiceConditionEndspointsPopulated is set when the ServerlessService's underlying
// Revision K8s Service has been populated with endpoints.
ServerlessServiceConditionEndspointsPopulated duckv1alpha1.ConditionType = "EndpointsPopulated"
ServerlessServiceConditionEndspointsPopulated apis.ConditionType = "EndpointsPopulated"
)

View File

@ -20,12 +20,13 @@ import (
"context"
"github.com/knative/pkg/apis"
"github.com/knative/serving/pkg/apis/serving"
"k8s.io/apimachinery/pkg/api/equality"
)
// Validate inspects and validates ClusterServerlessService object.
func (ci *ServerlessService) Validate(ctx context.Context) *apis.FieldError {
return ci.Spec.Validate(ctx).ViaField("spec")
return ci.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec")
}
// Validate inspects and validates ServerlessServiceSpec object.
@ -42,20 +43,10 @@ func (spec *ServerlessServiceSpec) Validate(ctx context.Context) *apis.FieldErro
case "":
all = all.Also(apis.ErrMissingField("mode"))
default:
all = all.Also(apis.ErrInvalidValue(string(spec.Mode), "mode"))
}
if len(spec.Selector) == 0 {
all = all.Also(apis.ErrMissingField("selector"))
} else {
for k, v := range spec.Selector {
if k == "" {
all = all.Also(apis.ErrInvalidKeyName(k, "selector", "empty key is not permitted"))
}
if v == "" {
all = all.Also(apis.ErrInvalidValue(v, apis.CurrentField).ViaKey(k).ViaField("selector"))
}
}
all = all.Also(apis.ErrInvalidValue(spec.Mode, "mode"))
}
return all.Also(spec.ProtocolType.Validate().ViaField("protocolType"))
all = all.Also(serving.ValidateNamespacedObjectReference(&spec.ObjectRef).ViaField("objectRef"))
return all.Also(spec.ProtocolType.Validate(ctx).ViaField("protocolType"))
}

View File

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
@ -49,9 +49,8 @@ func (in *Certificate) DeepCopy() *Certificate {
func (in *Certificate) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -83,9 +82,8 @@ func (in *CertificateList) DeepCopy() *CertificateList {
func (in *CertificateList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -115,12 +113,7 @@ func (in *CertificateStatus) DeepCopyInto(out *CertificateStatus) {
in.Status.DeepCopyInto(&out.Status)
if in.NotAfter != nil {
in, out := &in.NotAfter, &out.NotAfter
if *in == nil {
*out = nil
} else {
*out = new(v1.Time)
(*in).DeepCopyInto(*out)
}
*out = (*in).DeepCopy()
}
return
}
@ -159,9 +152,8 @@ func (in *ClusterIngress) DeepCopy() *ClusterIngress {
func (in *ClusterIngress) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -185,6 +177,13 @@ func (in *ClusterIngressBackend) DeepCopy() *ClusterIngressBackend {
func (in *ClusterIngressBackendSplit) DeepCopyInto(out *ClusterIngressBackendSplit) {
*out = *in
out.ClusterIngressBackend = in.ClusterIngressBackend
if in.AppendHeaders != nil {
in, out := &in.AppendHeaders, &out.AppendHeaders
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
@ -227,9 +226,8 @@ func (in *ClusterIngressList) DeepCopy() *ClusterIngressList {
func (in *ClusterIngressList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -242,12 +240,8 @@ func (in *ClusterIngressRule) DeepCopyInto(out *ClusterIngressRule) {
}
if in.HTTP != nil {
in, out := &in.HTTP, &out.HTTP
if *in == nil {
*out = nil
} else {
*out = new(HTTPClusterIngressRuleValue)
(*in).DeepCopyInto(*out)
}
*out = new(HTTPClusterIngressRuleValue)
(*in).DeepCopyInto(*out)
}
return
}
@ -289,7 +283,9 @@ func (in *HTTPClusterIngressPath) DeepCopyInto(out *HTTPClusterIngressPath) {
if in.Splits != nil {
in, out := &in.Splits, &out.Splits
*out = make([]ClusterIngressBackendSplit, len(*in))
copy(*out, *in)
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AppendHeaders != nil {
in, out := &in.AppendHeaders, &out.AppendHeaders
@ -300,21 +296,13 @@ func (in *HTTPClusterIngressPath) DeepCopyInto(out *HTTPClusterIngressPath) {
}
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
*out = new(v1.Duration)
**out = **in
}
if in.Retries != nil {
in, out := &in.Retries, &out.Retries
if *in == nil {
*out = nil
} else {
*out = new(HTTPRetry)
(*in).DeepCopyInto(*out)
}
*out = new(HTTPRetry)
(*in).DeepCopyInto(*out)
}
return
}
@ -357,12 +345,8 @@ func (in *HTTPRetry) DeepCopyInto(out *HTTPRetry) {
*out = *in
if in.PerTryTimeout != nil {
in, out := &in.PerTryTimeout, &out.PerTryTimeout
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
*out = new(v1.Duration)
**out = **in
}
return
}
@ -413,12 +397,8 @@ func (in *IngressStatus) DeepCopyInto(out *IngressStatus) {
in.Status.DeepCopyInto(&out.Status)
if in.LoadBalancer != nil {
in, out := &in.LoadBalancer, &out.LoadBalancer
if *in == nil {
*out = nil
} else {
*out = new(LoadBalancerStatus)
(*in).DeepCopyInto(*out)
}
*out = new(LoadBalancerStatus)
(*in).DeepCopyInto(*out)
}
return
}
@ -475,7 +455,7 @@ func (in *ServerlessService) DeepCopyInto(out *ServerlessService) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
return
}
@ -494,9 +474,8 @@ func (in *ServerlessService) DeepCopy() *ServerlessService {
func (in *ServerlessService) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -528,21 +507,14 @@ func (in *ServerlessServiceList) DeepCopy() *ServerlessServiceList {
func (in *ServerlessServiceList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerlessServiceSpec) DeepCopyInto(out *ServerlessServiceSpec) {
*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
}
}
out.ObjectRef = in.ObjectRef
return
}

View File

@ -0,0 +1,492 @@
/*
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 serving
import (
corev1 "k8s.io/api/core/v1"
)
// VolumeMask performs a _shallow_ copy of the Kubernetes Volume object to a new
// Kubernetes Volume object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func VolumeMask(in *corev1.Volume) *corev1.Volume {
if in == nil {
return nil
}
out := new(corev1.Volume)
// Allowed fields
out.Name = in.Name
out.VolumeSource = in.VolumeSource
return out
}
// VolumeSourceMask performs a _shallow_ copy of the Kubernetes VolumeSource object to a new
// Kubernetes VolumeSource object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func VolumeSourceMask(in *corev1.VolumeSource) *corev1.VolumeSource {
if in == nil {
return nil
}
out := new(corev1.VolumeSource)
// Allowed fields
out.Secret = in.Secret
out.ConfigMap = in.ConfigMap
// Too many disallowed fields to list
return out
}
// PodSpecMask performs a _shallow_ copy of the Kubernetes PodSpec object to a new
// Kubernetes PodSpec object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func PodSpecMask(in *corev1.PodSpec) *corev1.PodSpec {
if in == nil {
return nil
}
out := new(corev1.PodSpec)
// Allowed fields
out.ServiceAccountName = in.ServiceAccountName
out.Containers = in.Containers
out.Volumes = in.Volumes
// Disallowed fields
// This list is unnecessary, but added here for clarity
out.InitContainers = nil
out.RestartPolicy = ""
out.TerminationGracePeriodSeconds = nil
out.ActiveDeadlineSeconds = nil
out.DNSPolicy = ""
out.NodeSelector = nil
out.AutomountServiceAccountToken = nil
out.NodeName = ""
out.HostNetwork = false
out.HostPID = false
out.HostIPC = false
out.ShareProcessNamespace = nil
out.SecurityContext = nil
out.ImagePullSecrets = nil
out.Hostname = ""
out.Subdomain = ""
out.Affinity = nil
out.SchedulerName = ""
out.Tolerations = nil
out.HostAliases = nil
out.PriorityClassName = ""
out.Priority = nil
out.DNSConfig = nil
out.ReadinessGates = nil
out.RuntimeClassName = nil
// TODO(mattmoor): Coming in 1.13: out.EnableServiceLinks = nil
return out
}
// ContainerMask performs a _shallow_ copy of the Kubernetes Container object to a new
// Kubernetes Container object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func ContainerMask(in *corev1.Container) *corev1.Container {
if in == nil {
return nil
}
out := new(corev1.Container)
// Allowed fields
out.Args = in.Args
out.Command = in.Command
out.Env = in.Env
out.WorkingDir = in.WorkingDir
out.EnvFrom = in.EnvFrom
out.Image = in.Image
out.ImagePullPolicy = in.ImagePullPolicy
out.LivenessProbe = in.LivenessProbe
out.Ports = in.Ports
out.ReadinessProbe = in.ReadinessProbe
out.Resources = in.Resources
out.SecurityContext = in.SecurityContext
out.TerminationMessagePath = in.TerminationMessagePath
out.TerminationMessagePolicy = in.TerminationMessagePolicy
out.VolumeMounts = in.VolumeMounts
// Disallowed fields
// This list is unnecessary, but added here for clarity
out.Lifecycle = nil
out.Name = ""
out.Stdin = false
out.StdinOnce = false
out.TTY = false
out.VolumeDevices = nil
return out
}
// VolumeMountMask performs a _shallow_ copy of the Kubernetes VolumeMount object to a new
// Kubernetes VolumeMount object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func VolumeMountMask(in *corev1.VolumeMount) *corev1.VolumeMount {
if in == nil {
return nil
}
out := new(corev1.VolumeMount)
// Allowed fields
out.Name = in.Name
out.ReadOnly = in.ReadOnly
out.MountPath = in.MountPath
out.SubPath = in.SubPath
// Disallowed fields
// This list is unnecessary, but added here for clarity
out.MountPropagation = nil
return out
}
// ProbeMask performs a _shallow_ copy of the Kubernetes Probe object to a new
// Kubernetes Probe object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func ProbeMask(in *corev1.Probe) *corev1.Probe {
if in == nil {
return nil
}
out := new(corev1.Probe)
// Allowed fields
out.Handler = in.Handler
out.InitialDelaySeconds = in.InitialDelaySeconds
out.TimeoutSeconds = in.TimeoutSeconds
out.PeriodSeconds = in.PeriodSeconds
out.SuccessThreshold = in.SuccessThreshold
out.FailureThreshold = in.FailureThreshold
return out
}
// HandlerMask performs a _shallow_ copy of the Kubernetes Handler object to a new
// Kubernetes Handler object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func HandlerMask(in *corev1.Handler) *corev1.Handler {
if in == nil {
return nil
}
out := new(corev1.Handler)
// Allowed fields
out.Exec = in.Exec
out.HTTPGet = in.HTTPGet
out.TCPSocket = in.TCPSocket
return out
}
// ExecActionMask performs a _shallow_ copy of the Kubernetes ExecAction object to a new
// Kubernetes ExecAction object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func ExecActionMask(in *corev1.ExecAction) *corev1.ExecAction {
if in == nil {
return nil
}
out := new(corev1.ExecAction)
// Allowed fields
out.Command = in.Command
return out
}
// HTTPGetActionMask performs a _shallow_ copy of the Kubernetes HTTPGetAction object to a new
// Kubernetes HTTPGetAction object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func HTTPGetActionMask(in *corev1.HTTPGetAction) *corev1.HTTPGetAction {
if in == nil {
return nil
}
out := new(corev1.HTTPGetAction)
// Allowed fields
out.Host = in.Host
out.Path = in.Path
out.Scheme = in.Scheme
out.HTTPHeaders = in.HTTPHeaders
return out
}
// TCPSocketActionMask performs a _shallow_ copy of the Kubernetes TCPSocketAction object to a new
// Kubernetes TCPSocketAction object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func TCPSocketActionMask(in *corev1.TCPSocketAction) *corev1.TCPSocketAction {
if in == nil {
return nil
}
out := new(corev1.TCPSocketAction)
// Allowed fields
out.Host = in.Host
return out
}
// ContainerPortMask performs a _shallow_ copy of the Kubernetes ContainerPort object to a new
// Kubernetes ContainerPort object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func ContainerPortMask(in *corev1.ContainerPort) *corev1.ContainerPort {
if in == nil {
return nil
}
out := new(corev1.ContainerPort)
// Allowed fields
out.ContainerPort = in.ContainerPort
out.Name = in.Name
out.Protocol = in.Protocol
//Disallowed fields
// This list is unnecessary, but added here for clarity
out.HostIP = ""
out.HostPort = 0
return out
}
// EnvVarMask performs a _shallow_ copy of the Kubernetes EnvVar object to a new
// Kubernetes EnvVar object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func EnvVarMask(in *corev1.EnvVar) *corev1.EnvVar {
if in == nil {
return nil
}
out := new(corev1.EnvVar)
// Allowed fields
out.Name = in.Name
out.Value = in.Value
out.ValueFrom = in.ValueFrom
return out
}
// EnvVarSourceMask performs a _shallow_ copy of the Kubernetes EnvVarSource object to a new
// Kubernetes EnvVarSource object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func EnvVarSourceMask(in *corev1.EnvVarSource) *corev1.EnvVarSource {
if in == nil {
return nil
}
out := new(corev1.EnvVarSource)
// Allowed fields
out.ConfigMapKeyRef = in.ConfigMapKeyRef
out.SecretKeyRef = in.SecretKeyRef
// Disallowed
// This list is unnecessary, but added here for clarity
out.FieldRef = nil
out.ResourceFieldRef = nil
return out
}
// LocalObjectReferenceMask performs a _shallow_ copy of the Kubernetes LocalObjectReference object to a new
// Kubernetes LocalObjectReference object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func LocalObjectReferenceMask(in *corev1.LocalObjectReference) *corev1.LocalObjectReference {
if in == nil {
return nil
}
out := new(corev1.LocalObjectReference)
out.Name = in.Name
return out
}
// ConfigMapKeySelectorMask performs a _shallow_ copy of the Kubernetes ConfigMapKeySelector object to a new
// Kubernetes ConfigMapKeySelector object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func ConfigMapKeySelectorMask(in *corev1.ConfigMapKeySelector) *corev1.ConfigMapKeySelector {
if in == nil {
return nil
}
out := new(corev1.ConfigMapKeySelector)
// Allowed fields
out.Key = in.Key
out.Optional = in.Optional
out.LocalObjectReference = in.LocalObjectReference
return out
}
// SecretKeySelectorMask performs a _shallow_ copy of the Kubernetes SecretKeySelector object to a new
// Kubernetes SecretKeySelector object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func SecretKeySelectorMask(in *corev1.SecretKeySelector) *corev1.SecretKeySelector {
if in == nil {
return nil
}
out := new(corev1.SecretKeySelector)
// Allowed fields
out.Key = in.Key
out.Optional = in.Optional
out.LocalObjectReference = in.LocalObjectReference
return out
}
// ConfigMapEnvSourceMask performs a _shallow_ copy of the Kubernetes ConfigMapEnvSource object to a new
// Kubernetes ConfigMapEnvSource object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func ConfigMapEnvSourceMask(in *corev1.ConfigMapEnvSource) *corev1.ConfigMapEnvSource {
if in == nil {
return nil
}
out := new(corev1.ConfigMapEnvSource)
// Allowed fields
out.Optional = in.Optional
out.LocalObjectReference = in.LocalObjectReference
return out
}
// SecretEnvSourceMask performs a _shallow_ copy of the Kubernetes SecretEnvSource object to a new
// Kubernetes SecretEnvSource object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func SecretEnvSourceMask(in *corev1.SecretEnvSource) *corev1.SecretEnvSource {
if in == nil {
return nil
}
out := new(corev1.SecretEnvSource)
// Allowed fields
out.Optional = in.Optional
out.LocalObjectReference = in.LocalObjectReference
return out
}
// EnvFromSourceMask performs a _shallow_ copy of the Kubernetes EnvFromSource object to a new
// Kubernetes EnvFromSource object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func EnvFromSourceMask(in *corev1.EnvFromSource) *corev1.EnvFromSource {
if in == nil {
return nil
}
out := new(corev1.EnvFromSource)
// Allowed fields
out.Prefix = in.Prefix
out.ConfigMapRef = in.ConfigMapRef
out.SecretRef = in.SecretRef
return out
}
// ResourceRequirementsMask performs a _shallow_ copy of the Kubernetes ResourceRequirements object to a new
// Kubernetes ResourceRequirements object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func ResourceRequirementsMask(in *corev1.ResourceRequirements) *corev1.ResourceRequirements {
if in == nil {
return nil
}
out := new(corev1.ResourceRequirements)
// Allowed fields
out.Limits = in.Limits
out.Requests = in.Requests
return out
}
// SecurityContextMask performs a _shallow_ copy of the Kubernetes SecurityContext object to a new
// Kubernetes SecurityContext object bringing over only the fields allowed in the Knative API. This
// does not validate the contents or the bounds of the provided fields.
func SecurityContextMask(in *corev1.SecurityContext) *corev1.SecurityContext {
if in == nil {
return nil
}
out := new(corev1.SecurityContext)
// Allowed fields
out.RunAsUser = in.RunAsUser
// Disallowed
// This list is unnecessary, but added here for clarity
out.Capabilities = nil
out.Privileged = nil
out.SELinuxOptions = nil
out.RunAsGroup = nil
out.RunAsNonRoot = nil
out.ReadOnlyRootFilesystem = nil
out.AllowPrivilegeEscalation = nil
out.ProcMount = nil
return out
}
// NamespacedObjectReferenceMask performs a _shallow_ copy of the Kubernetes ObjectReference
// object to a new Kubernetes ObjectReference object bringing over only the fields allowed in
// the Knative API. This does not validate the contents or the bounds of the provided fields.
func NamespacedObjectReferenceMask(in *corev1.ObjectReference) *corev1.ObjectReference {
if in == nil {
return nil
}
out := new(corev1.ObjectReference)
// Allowed fields
out.APIVersion = in.APIVersion
out.Kind = in.Kind
out.Name = in.Name
// Disallowed
// This list is unnecessary, but added here for clarity
out.Namespace = ""
out.FieldPath = ""
out.ResourceVersion = ""
out.UID = ""
return out
}

View File

@ -0,0 +1,382 @@
/*
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 serving
import (
"fmt"
"math"
"path/filepath"
"strings"
"github.com/google/go-containerregistry/pkg/name"
"github.com/knative/pkg/apis"
"github.com/knative/serving/pkg/apis/networking"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
)
const (
minUserID = 0
maxUserID = math.MaxInt32
)
var (
reservedPaths = sets.NewString(
"/",
"/dev",
"/dev/log", // Should be a domain socket
"/tmp",
"/var",
"/var/log",
)
reservedEnvVars = sets.NewString(
"PORT",
"K_SERVICE",
"K_CONFIGURATION",
"K_REVISION",
)
// The port is named "user-port" on the deployment, but a user cannot set an arbitrary name on the port
// in Configuration. The name field is reserved for content-negotiation. Currently 'h2c' and 'http1' are
// allowed.
// https://github.com/knative/serving/blob/master/docs/runtime-contract.md#inbound-network-connectivity
validPortNames = sets.NewString(
"h2c",
"http1",
"",
)
)
func ValidateVolumes(vs []corev1.Volume) (sets.String, *apis.FieldError) {
volumes := sets.NewString()
var errs *apis.FieldError
for i, volume := range vs {
if volumes.Has(volume.Name) {
errs = errs.Also((&apis.FieldError{
Message: fmt.Sprintf("duplicate volume name %q", volume.Name),
Paths: []string{"name"},
}).ViaIndex(i))
}
errs = errs.Also(validateVolume(volume).ViaIndex(i))
volumes.Insert(volume.Name)
}
return volumes, errs
}
func validateVolume(volume corev1.Volume) *apis.FieldError {
errs := apis.CheckDisallowedFields(volume, *VolumeMask(&volume))
if volume.Name == "" {
errs = apis.ErrMissingField("name")
} else if len(validation.IsDNS1123Label(volume.Name)) != 0 {
errs = apis.ErrInvalidValue(volume.Name, "name")
}
vs := volume.VolumeSource
errs = errs.Also(apis.CheckDisallowedFields(vs, *VolumeSourceMask(&vs)))
if vs.Secret == nil && vs.ConfigMap == nil {
errs = errs.Also(apis.ErrMissingOneOf("secret", "configMap"))
}
return errs
}
func validateEnvValueFrom(source *corev1.EnvVarSource) *apis.FieldError {
if source == nil {
return nil
}
return apis.CheckDisallowedFields(*source, *EnvVarSourceMask(source))
}
func validateEnvVar(env corev1.EnvVar) *apis.FieldError {
errs := apis.CheckDisallowedFields(env, *EnvVarMask(&env))
if env.Name == "" {
errs = errs.Also(apis.ErrMissingField("name"))
} else if reservedEnvVars.Has(env.Name) {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("%q is a reserved environment variable", env.Name),
Paths: []string{"name"},
})
}
return errs.Also(validateEnvValueFrom(env.ValueFrom).ViaField("valueFrom"))
}
func validateEnv(envVars []corev1.EnvVar) *apis.FieldError {
var errs *apis.FieldError
for i, env := range envVars {
errs = errs.Also(validateEnvVar(env).ViaIndex(i))
}
return errs
}
func validateEnvFrom(envFromList []corev1.EnvFromSource) *apis.FieldError {
var errs *apis.FieldError
for i, envFrom := range envFromList {
errs = errs.Also(apis.CheckDisallowedFields(envFrom, *EnvFromSourceMask(&envFrom)).ViaIndex(i))
cm := envFrom.ConfigMapRef
sm := envFrom.SecretRef
if sm != nil {
errs = errs.Also(apis.CheckDisallowedFields(*sm, *SecretEnvSourceMask(sm)).ViaIndex(i))
errs = errs.Also(apis.CheckDisallowedFields(
sm.LocalObjectReference, *LocalObjectReferenceMask(&sm.LocalObjectReference))).ViaIndex(i).ViaField("secretRef")
}
if cm != nil {
errs = errs.Also(apis.CheckDisallowedFields(*cm, *ConfigMapEnvSourceMask(cm)).ViaIndex(i))
errs = errs.Also(apis.CheckDisallowedFields(
cm.LocalObjectReference, *LocalObjectReferenceMask(&cm.LocalObjectReference))).ViaIndex(i).ViaField("configMapRef")
}
if cm != nil && sm != nil {
errs = errs.Also(apis.ErrMultipleOneOf("configMapRef", "secretRef"))
} else if cm == nil && sm == nil {
errs = errs.Also(apis.ErrMissingOneOf("configMapRef", "secretRef"))
}
}
return errs
}
func ValidatePodSpec(ps corev1.PodSpec) *apis.FieldError {
if equality.Semantic.DeepEqual(ps, corev1.PodSpec{}) {
return apis.ErrMissingField(apis.CurrentField)
}
errs := apis.CheckDisallowedFields(ps, *PodSpecMask(&ps))
volumes, err := ValidateVolumes(ps.Volumes)
if err != nil {
errs = errs.Also(err.ViaField("volumes"))
}
switch len(ps.Containers) {
case 0:
errs = errs.Also(apis.ErrMissingField("containers"))
case 1:
errs = errs.Also(ValidateContainer(ps.Containers[0], volumes).
ViaFieldIndex("containers", 0))
default:
errs = errs.Also(apis.ErrMultipleOneOf("containers"))
}
return errs
}
func ValidateContainer(container corev1.Container, volumes sets.String) *apis.FieldError {
if equality.Semantic.DeepEqual(container, corev1.Container{}) {
return apis.ErrMissingField(apis.CurrentField)
}
errs := apis.CheckDisallowedFields(container, *ContainerMask(&container))
// Env
errs = errs.Also(validateEnv(container.Env).ViaField("env"))
// EnvFrom
errs = errs.Also(validateEnvFrom(container.EnvFrom).ViaField("envFrom"))
// Image
if _, err := name.ParseReference(container.Image, name.WeakValidation); err != nil {
fe := &apis.FieldError{
Message: "Failed to parse image reference",
Paths: []string{"image"},
Details: fmt.Sprintf("image: %q, error: %v", container.Image, err),
}
errs = errs.Also(fe)
}
// Liveness Probes
errs = errs.Also(validateProbe(container.LivenessProbe).ViaField("livenessProbe"))
// Ports
errs = errs.Also(validateContainerPorts(container.Ports).ViaField("ports"))
// Readiness Probes
errs = errs.Also(validateProbe(container.ReadinessProbe).ViaField("readinessProbe"))
// Resources
errs = errs.Also(validateResources(&container.Resources).ViaField("resources"))
// SecurityContext
errs = errs.Also(validateSecurityContext(container.SecurityContext).ViaField("securityContext"))
// TerminationMessagePolicy
switch container.TerminationMessagePolicy {
case corev1.TerminationMessageReadFile, corev1.TerminationMessageFallbackToLogsOnError, "":
default:
errs = errs.Also(apis.ErrInvalidValue(container.TerminationMessagePolicy, "terminationMessagePolicy"))
}
// VolumeMounts
errs = errs.Also(validateVolumeMounts(container.VolumeMounts, volumes).ViaField("volumeMounts"))
return errs
}
func validateResources(resources *corev1.ResourceRequirements) *apis.FieldError {
if resources == nil {
return nil
}
return apis.CheckDisallowedFields(*resources, *ResourceRequirementsMask(resources))
}
func validateSecurityContext(sc *corev1.SecurityContext) *apis.FieldError {
if sc == nil {
return nil
}
errs := apis.CheckDisallowedFields(*sc, *SecurityContextMask(sc))
if sc.RunAsUser != nil {
uid := *sc.RunAsUser
if uid < minUserID || uid > maxUserID {
errs = errs.Also(apis.ErrOutOfBoundsValue(uid, minUserID, maxUserID, "runAsUser"))
}
}
return errs
}
func validateVolumeMounts(mounts []corev1.VolumeMount, volumes sets.String) *apis.FieldError {
var errs *apis.FieldError
// Check that volume mounts match names in "volumes", that "volumes" has 100%
// coverage, and the field restrictions.
seenName := sets.NewString()
seenMountPath := sets.NewString()
for i, vm := range mounts {
errs = errs.Also(apis.CheckDisallowedFields(vm, *VolumeMountMask(&vm)).ViaIndex(i))
// This effectively checks that Name is non-empty because Volume name must be non-empty.
if !volumes.Has(vm.Name) {
errs = errs.Also((&apis.FieldError{
Message: "volumeMount has no matching volume",
Paths: []string{"name"},
}).ViaIndex(i))
}
seenName.Insert(vm.Name)
if vm.MountPath == "" {
errs = errs.Also(apis.ErrMissingField("mountPath").ViaIndex(i))
} else if reservedPaths.Has(filepath.Clean(vm.MountPath)) {
errs = errs.Also((&apis.FieldError{
Message: fmt.Sprintf("mountPath %q is a reserved path", filepath.Clean(vm.MountPath)),
Paths: []string{"mountPath"},
}).ViaIndex(i))
} else if !filepath.IsAbs(vm.MountPath) {
errs = errs.Also(apis.ErrInvalidValue(vm.MountPath, "mountPath").ViaIndex(i))
} else if seenMountPath.Has(filepath.Clean(vm.MountPath)) {
errs = errs.Also(apis.ErrInvalidValue(
fmt.Sprintf("%q must be unique", vm.MountPath), "mountPath").ViaIndex(i))
}
seenMountPath.Insert(filepath.Clean(vm.MountPath))
if !vm.ReadOnly {
errs = errs.Also(apis.ErrMissingField("readOnly").ViaIndex(i))
}
}
if missing := volumes.Difference(seenName); missing.Len() > 0 {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("volumes not mounted: %v", missing.List()),
Paths: []string{apis.CurrentField},
})
}
return errs
}
func validateContainerPorts(ports []corev1.ContainerPort) *apis.FieldError {
if len(ports) == 0 {
return nil
}
var errs *apis.FieldError
// user can set container port which names "user-port" to define application's port.
// Queue-proxy will use it to send requests to application
// if user didn't set any port, it will set default port user-port=8080.
if len(ports) > 1 {
errs = errs.Also(&apis.FieldError{
Message: "More than one container port is set",
Paths: []string{apis.CurrentField},
Details: "Only a single port is allowed",
})
}
userPort := ports[0]
errs = errs.Also(apis.CheckDisallowedFields(userPort, *ContainerPortMask(&userPort)))
// Only allow empty (defaulting to "TCP") or explicit TCP for protocol
if userPort.Protocol != "" && userPort.Protocol != corev1.ProtocolTCP {
errs = errs.Also(apis.ErrInvalidValue(userPort.Protocol, "protocol"))
}
// Don't allow userPort to conflict with QueueProxy sidecar
if userPort.ContainerPort == networking.BackendHTTPPort ||
userPort.ContainerPort == networking.BackendHTTP2Port ||
userPort.ContainerPort == networking.RequestQueueAdminPort ||
userPort.ContainerPort == networking.RequestQueueMetricsPort {
errs = errs.Also(apis.ErrInvalidValue(userPort.ContainerPort, "containerPort"))
}
if userPort.ContainerPort < 1 || userPort.ContainerPort > 65535 {
errs = errs.Also(apis.ErrOutOfBoundsValue(userPort.ContainerPort,
1, 65535, "containerPort"))
}
if !validPortNames.Has(userPort.Name) {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("Port name %v is not allowed", ports[0].Name),
Paths: []string{apis.CurrentField},
Details: "Name must be empty, or one of: 'h2c', 'http1'",
})
}
return errs
}
func validateProbe(p *corev1.Probe) *apis.FieldError {
if p == nil {
return nil
}
errs := apis.CheckDisallowedFields(*p, *ProbeMask(p))
h := p.Handler
errs = errs.Also(apis.CheckDisallowedFields(h, *HandlerMask(&h)))
switch {
case h.HTTPGet != nil:
errs = errs.Also(apis.CheckDisallowedFields(*h.HTTPGet, *HTTPGetActionMask(h.HTTPGet))).ViaField("httpGet")
case h.TCPSocket != nil:
errs = errs.Also(apis.CheckDisallowedFields(*h.TCPSocket, *TCPSocketActionMask(h.TCPSocket))).ViaField("tcpSocket")
}
return errs
}
func ValidateNamespacedObjectReference(p *corev1.ObjectReference) *apis.FieldError {
if p == nil {
return nil
}
errs := apis.CheckDisallowedFields(*p, *NamespacedObjectReferenceMask(p))
if p.APIVersion == "" {
errs = errs.Also(apis.ErrMissingField("apiVersion"))
} else if verrs := validation.IsQualifiedName(p.APIVersion); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "apiVersion"))
}
if p.Kind == "" {
errs = errs.Also(apis.ErrMissingField("kind"))
} else if verrs := validation.IsCIdentifier(p.Kind); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "kind"))
}
if p.Name == "" {
errs = errs.Also(apis.ErrMissingField("name"))
} else if verrs := validation.IsDNS1123Label(p.Name); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "name"))
}
return errs
}

View File

@ -0,0 +1,30 @@
/*
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 serving
import (
"github.com/knative/pkg/apis"
"github.com/knative/serving/pkg/apis/autoscaling"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ValidateObjectMetadata validates that `metadata` stanza of the
// resources is correct.
func ValidateObjectMetadata(meta metav1.Object) *apis.FieldError {
return apis.ValidateObjectMetadata(meta).Also(
autoscaling.ValidateAnnotations(meta.GetAnnotations()).ViaField("annotations"))
}

View File

@ -57,11 +57,14 @@ const (
// metadata generation of the Configuration that created this revision
ConfigurationGenerationLabelKey = GroupName + "/configurationGeneration"
// DeprecatedConfigurationMetadataGenerationLabelKey is the label key attached to a Revision indicating the
// metadata generation of the Configuration that created this revision
DeprecatedConfigurationMetadataGenerationLabelKey = GroupName + "/configurationMetadataGeneration"
// BuildHashLabelKey is the label key attached to a Build indicating the
// hash of the spec from which they were created.
BuildHashLabelKey = GroupName + "/buildHash"
// CreatorAnnotation is the annotation key to describe the user that
// created the resource.
CreatorAnnotation = GroupName + "/creator"
// UpdaterAnnotation is the annotation key to describe the user that
// last updated the resource.
UpdaterAnnotation = GroupName + "/lastModifier"
)

View File

@ -0,0 +1,104 @@
/*
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 v1alpha1
import (
"context"
"fmt"
"github.com/knative/pkg/apis"
"github.com/knative/serving/pkg/apis/serving/v1beta1"
)
// ConvertUp implements apis.Convertible
func (source *Configuration) ConvertUp(ctx context.Context, obj apis.Convertible) error {
switch sink := obj.(type) {
case *v1beta1.Configuration:
sink.ObjectMeta = source.ObjectMeta
if err := source.Spec.ConvertUp(ctx, &sink.Spec); err != nil {
return err
}
return source.Status.ConvertUp(ctx, &sink.Status)
default:
return fmt.Errorf("unknown version, got: %T", sink)
}
}
// ConvertUp helps implement apis.Convertible
func (source *ConfigurationSpec) ConvertUp(ctx context.Context, sink *v1beta1.ConfigurationSpec) error {
if source.DeprecatedBuild != nil {
return ConvertErrorf("build", "build cannot be migrated forward.")
}
switch {
case source.DeprecatedRevisionTemplate != nil && source.Template != nil:
return apis.ErrMultipleOneOf("revisionTemplate", "template")
case source.DeprecatedRevisionTemplate != nil:
return source.DeprecatedRevisionTemplate.ConvertUp(ctx, &sink.Template)
case source.Template != nil:
return source.Template.ConvertUp(ctx, &sink.Template)
default:
return apis.ErrMissingOneOf("revisionTemplate", "template")
}
}
// ConvertUp helps implement apis.Convertible
func (source *ConfigurationStatus) ConvertUp(ctx context.Context, sink *v1beta1.ConfigurationStatus) error {
source.Status.ConvertTo(ctx, &sink.Status)
return source.ConfigurationStatusFields.ConvertUp(ctx, &sink.ConfigurationStatusFields)
}
// ConvertUp helps implement apis.Convertible
func (source *ConfigurationStatusFields) ConvertUp(ctx context.Context, sink *v1beta1.ConfigurationStatusFields) error {
sink.LatestReadyRevisionName = source.LatestReadyRevisionName
sink.LatestCreatedRevisionName = source.LatestCreatedRevisionName
return nil
}
// ConvertDown implements apis.Convertible
func (sink *Configuration) ConvertDown(ctx context.Context, obj apis.Convertible) error {
switch source := obj.(type) {
case *v1beta1.Configuration:
sink.ObjectMeta = source.ObjectMeta
if err := sink.Spec.ConvertDown(ctx, source.Spec); err != nil {
return err
}
return sink.Status.ConvertDown(ctx, source.Status)
default:
return fmt.Errorf("unknown version, got: %T", source)
}
}
// ConvertDown helps implement apis.Convertible
func (sink *ConfigurationSpec) ConvertDown(ctx context.Context, source v1beta1.ConfigurationSpec) error {
sink.Template = &RevisionTemplateSpec{}
return sink.Template.ConvertDown(ctx, source.Template)
}
// ConvertDown helps implement apis.Convertible
func (sink *ConfigurationStatus) ConvertDown(ctx context.Context, source v1beta1.ConfigurationStatus) error {
source.Status.ConvertTo(ctx, &sink.Status)
return sink.ConfigurationStatusFields.ConvertDown(ctx, source.ConfigurationStatusFields)
}
// ConvertDown helps implement apis.Convertible
func (sink *ConfigurationStatusFields) ConvertDown(ctx context.Context, source v1beta1.ConfigurationStatusFields) error {
sink.LatestReadyRevisionName = source.LatestReadyRevisionName
sink.LatestCreatedRevisionName = source.LatestCreatedRevisionName
return nil
}

View File

@ -16,12 +16,28 @@ limitations under the License.
package v1alpha1
import "context"
import (
"context"
"github.com/knative/pkg/apis"
"github.com/knative/serving/pkg/apis/serving/v1beta1"
)
func (c *Configuration) SetDefaults(ctx context.Context) {
c.Spec.SetDefaults(ctx)
c.Spec.SetDefaults(apis.WithinSpec(ctx))
}
func (cs *ConfigurationSpec) SetDefaults(ctx context.Context) {
cs.RevisionTemplate.Spec.SetDefaults(ctx)
if v1beta1.IsUpgradeViaDefaulting(ctx) {
beta := v1beta1.ConfigurationSpec{}
if cs.ConvertUp(ctx, &beta) == nil {
alpha := ConfigurationSpec{}
if alpha.ConvertDown(ctx, beta) == nil {
*cs = alpha
}
}
}
cs.GetTemplate().Spec.SetDefaults(ctx)
}

View File

@ -17,16 +17,44 @@ limitations under the License.
package v1alpha1
import (
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"github.com/knative/pkg/apis"
duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var confCondSet = duckv1alpha1.NewLivingConditionSet()
var confCondSet = apis.NewLivingConditionSet()
func (r *Configuration) GetGroupVersionKind() schema.GroupVersionKind {
return SchemeGroupVersion.WithKind("Configuration")
}
// MarkResourceNotConvertible adds a Warning-severity condition to the resource noting that
// it cannot be converted to a higher version.
func (cs *ConfigurationStatus) MarkResourceNotConvertible(err *CannotConvertError) {
confCondSet.Manage(cs).SetCondition(apis.Condition{
Type: ConditionTypeConvertible,
Status: corev1.ConditionFalse,
Severity: apis.ConditionSeverityWarning,
Reason: err.Field,
Message: err.Message,
})
}
// GetTemplate returns a pointer to the relevant RevisionTemplateSpec field.
// It is never nil and should be exactly the specified template as guaranteed
// by validation.
func (cs *ConfigurationSpec) GetTemplate() *RevisionTemplateSpec {
if cs.DeprecatedRevisionTemplate != nil {
return cs.DeprecatedRevisionTemplate
}
if cs.Template != nil {
return cs.Template
}
// Should be unreachable post-validation, but here to ease testing.
return &RevisionTemplateSpec{}
}
// IsReady looks at the conditions to see if they are happy.
func (cs *ConfigurationStatus) IsReady() bool {
return confCondSet.Manage(cs).IsHappy()
@ -40,7 +68,7 @@ func (cs *ConfigurationStatus) IsLatestReadyRevisionNameUpToDate() bool {
cs.LatestCreatedRevisionName == cs.LatestReadyRevisionName
}
func (cs *ConfigurationStatus) GetCondition(t duckv1alpha1.ConditionType) *duckv1alpha1.Condition {
func (cs *ConfigurationStatus) GetCondition(t apis.ConditionType) *apis.Condition {
return confCondSet.Manage(cs).GetCondition(t)
}
@ -83,3 +111,7 @@ func (cs *ConfigurationStatus) MarkLatestReadyDeleted() {
"RevisionDeleted",
"Revision %q was deleted.", cs.LatestReadyRevisionName)
}
func (cs *ConfigurationStatus) duck() *duckv1beta1.Status {
return &cs.Status
}

Some files were not shown because too many files have changed in this diff Show More