mirror of https://github.com/knative/client.git
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:
parent
a264f060c1
commit
52b6b236fe
26
go.mod
26
go.mod
|
|
@ -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
19
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
10
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go
generated
vendored
Normal file
10
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go
generated
vendored
Normal 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
|
||||
10
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go
generated
vendored
Normal file
10
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go
generated
vendored
Normal 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
|
||||
|
|
@ -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, ".")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
23
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go
generated
vendored
Normal file
23
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go
generated
vendored
Normal 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()}
|
||||
}
|
||||
26
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go
generated
vendored
Normal file
26
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go
generated
vendored
Normal 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()}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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{}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
97
vendor/github.com/knative/pkg/apis/duck/v1beta1/addressable_types.go
generated
vendored
Normal file
97
vendor/github.com/knative/pkg/apis/duck/v1beta1/addressable_types.go
generated
vendored
Normal 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"`
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
233
vendor/github.com/knative/pkg/apis/duck/v1beta1/zz_generated.deepcopy.go
generated
vendored
Normal file
233
vendor/github.com/knative/pkg/apis/duck/v1beta1/zz_generated.deepcopy.go
generated
vendored
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ type ManualWatcher struct {
|
|||
// Guards mutations to defaultImpl fields
|
||||
m sync.Mutex
|
||||
|
||||
started bool
|
||||
observers map[string][]Observer
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
63
vendor/github.com/knative/serving/pkg/apis/autoscaling/annotation_validation.go
generated
vendored
Normal file
63
vendor/github.com/knative/serving/pkg/apis/autoscaling/annotation_validation.go
generated
vendored
Normal 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
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
12
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/pa_defaults.go
generated
vendored
12
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/pa_defaults.go
generated
vendored
|
|
@ -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) {}
|
||||
|
|
|
|||
138
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go
generated
vendored
138
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go
generated
vendored
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
121
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/pa_validation.go
generated
vendored
121
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/pa_validation.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
112
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/podscalable_types.go
generated
vendored
Normal file
112
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/podscalable_types.go
generated
vendored
Normal 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"`
|
||||
}
|
||||
122
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/zz_generated.deepcopy.go
generated
vendored
122
vendor/github.com/knative/serving/pkg/apis/autoscaling/v1alpha1/zz_generated.deepcopy.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
34
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/certificate_lifecycle.go
generated
vendored
34
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/certificate_lifecycle.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
8
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/certificate_types.go
generated
vendored
8
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/certificate_types.go
generated
vendored
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
19
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/clusteringress_types.go
generated
vendored
19
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/clusteringress_types.go
generated
vendored
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
88
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go
generated
vendored
88
vendor/github.com/knative/serving/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
382
vendor/github.com/knative/serving/pkg/apis/serving/k8s_validation.go
generated
vendored
Normal file
382
vendor/github.com/knative/serving/pkg/apis/serving/k8s_validation.go
generated
vendored
Normal 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
|
||||
}
|
||||
30
vendor/github.com/knative/serving/pkg/apis/serving/metadata_validation.go
generated
vendored
Normal file
30
vendor/github.com/knative/serving/pkg/apis/serving/metadata_validation.go
generated
vendored
Normal 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"))
|
||||
}
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
104
vendor/github.com/knative/serving/pkg/apis/serving/v1alpha1/configuration_conversion.go
generated
vendored
Normal file
104
vendor/github.com/knative/serving/pkg/apis/serving/v1alpha1/configuration_conversion.go
generated
vendored
Normal 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
|
||||
}
|
||||
22
vendor/github.com/knative/serving/pkg/apis/serving/v1alpha1/configuration_defaults.go
generated
vendored
22
vendor/github.com/knative/serving/pkg/apis/serving/v1alpha1/configuration_defaults.go
generated
vendored
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
38
vendor/github.com/knative/serving/pkg/apis/serving/v1alpha1/configuration_lifecycle.go
generated
vendored
38
vendor/github.com/knative/serving/pkg/apis/serving/v1alpha1/configuration_lifecycle.go
generated
vendored
|
|
@ -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
Loading…
Reference in New Issue