diff --git a/pkg/crd/schema.go b/pkg/crd/schema.go index 61917371f..79dd48758 100644 --- a/pkg/crd/schema.go +++ b/pkg/crd/schema.go @@ -19,6 +19,7 @@ package crd import ( "fmt" "go/ast" + "go/token" "go/types" "strings" @@ -104,6 +105,11 @@ func (c *schemaContext) requestSchema(pkgPath, typeName string) { // infoToSchema creates a schema for the type in the given set of type information. func infoToSchema(ctx *schemaContext) *apiext.JSONSchemaProps { + if obj := ctx.pkg.Types.Scope().Lookup(ctx.info.Name); obj != nil && implementsJSONMarshaler(obj.Type()) { + schema := &apiext.JSONSchemaProps{Type: "Any"} + applyMarkers(ctx, ctx.info.Markers, schema, ctx.info.RawSpec.Type) + return schema + } return typeToSchema(ctx, ctx.info.RawSpec.Type) } @@ -419,3 +425,16 @@ func builtinToType(basic *types.Basic) (typ string, format string, err error) { return typ, format, nil } + +// Open coded go/types representation of encoding/json.Marshaller +var jsonMarshaler = types.NewInterfaceType([]*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalJSON", + types.NewSignature(nil, nil, + types.NewTuple( + types.NewVar(token.NoPos, nil, "", types.NewSlice(types.Universe.Lookup("byte").Type())), + types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("error").Type())), false)), +}, nil).Complete() + +func implementsJSONMarshaler(typ types.Type) bool { + return types.Implements(typ, jsonMarshaler) || types.Implements(types.NewPointer(typ), jsonMarshaler) +} diff --git a/pkg/crd/testdata/cronjob_types.go b/pkg/crd/testdata/cronjob_types.go index 3cacb7b8b..e825d4684 100644 --- a/pkg/crd/testdata/cronjob_types.go +++ b/pkg/crd/testdata/cronjob_types.go @@ -23,7 +23,9 @@ limitations under the License. package cronjob import ( + "encoding/json" "fmt" + "net/url" batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" @@ -190,6 +192,84 @@ func (t *TotallyABool) UnmarshalJSON(in []byte) error { return nil } +// +kubebuilder:validation:Type=string +// URL wraps 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 struct { + url.URL +} + +func (u *URL) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", u.String())), nil +} + +func (u *URL) UnmarshalJSON(b []byte) error { + var ref string + if err := json.Unmarshal(b, &ref); err != nil { + return err + } + if ref == "" { + *u = URL{} + return nil + } + + r, err := url.Parse(ref) + if err != nil { + return err + } else if r != nil { + *u = URL{*r} + } else { + *u = URL{} + } + return nil +} + +func (u *URL) String() string { + if u == nil { + return "" + } + return u.URL.String() +} + +// +kubebuilder:validation:Type=string +// URL2 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 URL2 url.URL + +func (u *URL2) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", u.String())), nil +} + +func (u *URL2) UnmarshalJSON(b []byte) error { + var ref string + if err := json.Unmarshal(b, &ref); err != nil { + return err + } + if ref == "" { + *u = URL2{} + return nil + } + + r, err := url.Parse(ref) + if err != nil { + return err + } else if r != nil { + *u = *(*URL2)(r) + } else { + *u = URL2{} + } + return nil +} + +func (u *URL2) String() string { + if u == nil { + return "" + } + return (*url.URL)(u).String() +} + // ConcurrencyPolicy describes how the job will be handled. // Only one of the following concurrent policies may be specified. // If none of the following policies is specified, the default one @@ -226,6 +306,14 @@ type CronJobStatus struct { // with microsecond precision. // +optional LastScheduleMicroTime *metav1.MicroTime `json:"lastScheduleMicroTime,omitempty"` + + // LastActiveLogURL specifies the logging url for the last started job + // +optional + LastActiveLogURL *URL `json:"lastActiveLogURL,omitempty"` + + // LastActiveLogURL2 specifies the logging url for the last started job + // +optional + LastActiveLogURL2 *URL2 `json:"lastActiveLogURL2,omitempty"` } // +kubebuilder:object:root=true diff --git a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml index 0957c8998..3f2907a74 100644 --- a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml +++ b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml @@ -5167,6 +5167,14 @@ spec: type: string type: object type: array + lastActiveLogURL: + description: LastActiveLogURL specifies the logging url for the last + started job + type: string + lastActiveLogURL2: + description: LastActiveLogURL2 specifies the logging url for the last + started job + type: string lastScheduleMicroTime: description: Information about the last time the job was successfully scheduled, with microsecond precision.