Skip to content

Commit

Permalink
Fixing the addressable resolver (#38)
Browse files Browse the repository at this point in the history
* Fixing the addressable resolver

* Properly working address resolver

* Working fake & it tests (apart those affected by knative/client#1389)

* Upgrade to latest knative

* Use interim fork until merge of knative/client#1390

* Allow addressable URI to be empty, and by default

* Remove interim personal fork
  • Loading branch information
cardil authored Jul 22, 2021
1 parent 8df56e5 commit 0e1fe36
Show file tree
Hide file tree
Showing 605 changed files with 94,962 additions and 404 deletions.
15 changes: 7 additions & 8 deletions cmd/kn-event/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth"
"knative.dev/kn-plugin-event/pkg/cli"
"knative.dev/kn-plugin-event/pkg/cli/retcode"
"knative.dev/kn-plugin-event/pkg/event"
)

// Cmd represents a command line application entrypoint.
Expand Down Expand Up @@ -70,16 +69,16 @@ building, and parsing, all from command line.`,
}

c.root.PersistentFlags().StringVar(
&c.options.KnConfig, "config", "~/.config/kn/config.yaml",
"kn configuration file",
&c.options.KubeconfigOptions.Path, "kubeconfig", "",
"kubectl configuration file (default: ~/.kube/config)",
)
c.root.PersistentFlags().StringVar(
&c.options.Kubeconfig, "kubeconfig", event.DefaultKubeconfig,
"kubectl configuration file",
&c.options.KubeconfigOptions.Context, "context", "",
"name of the kubeconfig context to use",
)
c.root.PersistentFlags().BoolVar(
&c.options.LogHTTP, "log-http", false,
"log http traffic",
c.root.PersistentFlags().StringVar(
&c.options.KubeconfigOptions.Cluster, "cluster", "",
"name of the kubeconfig cluster to use",
)

c.root.PersistentPreRun = func(cmd *cobra.Command, args []string) {
Expand Down
4 changes: 2 additions & 2 deletions cmd/kn-event/cmd/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ this option isn't specified a current context namespace will be used. This
option can't be used with --to-url option.`,
)
c.Flags().StringVar(
&s.target.AddressableURI, "addressable-uri", "/",
&s.target.AddressableURI, "addressable-uri", "",
`Specify an URI of a target addressable resource. If this option
isn't specified a '/' URI will be used. This option can't be used with
isn't specified target URL will not be changed. This option can't be used with
--to-url option.`,
)
c.PreRunE = func(cmd *cobra.Command, args []string) error {
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ require (
github.com/joho/godotenv v1.3.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/magefile/mage v1.10.0
github.com/mitchellh/go-homedir v1.1.0
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
Expand All @@ -30,7 +29,11 @@ require (
knative.dev/client v0.24.1-0.20210721192038-a7df7c76a0d1
knative.dev/eventing v0.24.1-0.20210720191312-003a8a2d4ef5
knative.dev/hack v0.0.0-20210622141627-e28525d8d260
knative.dev/networking v0.0.0-20210721164339-e3c72d213a1a
knative.dev/pkg v0.0.0-20210715175632-d9b7180af6f2
knative.dev/serving v0.24.1-0.20210721140446-55a613957a4f
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
)

// TODO: unpin for k8s 0.21+, see: https://github.com/knative/client/pull/1209
replace github.com/go-openapi/spec => github.com/go-openapi/spec v0.19.3
37 changes: 32 additions & 5 deletions go.sum

Large diffs are not rendered by default.

35 changes: 5 additions & 30 deletions pkg/cli/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,16 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"time"

"github.com/ghodss/yaml"
"github.com/mitchellh/go-homedir"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"knative.dev/kn-plugin-event/pkg/event"
)

// ErrUnexpected if unexpected error found.
var ErrUnexpected = errors.New("unexpected")

// WithLogger will create an event suitable Options from CLI ones.
func (opts *Options) WithLogger() (*event.Properties, error) {
zc := zap.NewProductionConfig()
Expand Down Expand Up @@ -49,35 +44,12 @@ func (opts *Options) WithLogger() (*event.Properties, error) {
zcore, buildOptions(zc, errSink)...,
)

var (
knOpts event.KnPluginOptions
err error
)
if knOpts, err = resolvePluginOptions(opts.KnPluginOptions); err != nil {
return nil, err
}
return &event.Properties{
KnPluginOptions: knOpts,
KnPluginOptions: opts.KnPluginOptions,
Log: log.Sugar(),
}, nil
}

func resolvePluginOptions(options event.KnPluginOptions) (event.KnPluginOptions, error) {
if options.Kubeconfig == event.DefaultKubeconfig {
if ke, ok := os.LookupEnv("KUBECONFIG"); ok {
options.Kubeconfig = ke
}
}
var err error
if options.Kubeconfig, err = homedir.Expand(options.Kubeconfig); err != nil {
return event.KnPluginOptions{}, unexpected(err)
}
if options.KnConfig, err = homedir.Expand(options.KnConfig); err != nil {
return event.KnPluginOptions{}, unexpected(err)
}
return options, nil
}

func alignCapitalColorLevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
spaces := len(zapcore.FatalLevel.CapitalString()) - len(l.CapitalString())
if spaces > 0 {
Expand Down Expand Up @@ -135,5 +107,8 @@ func (y *yamlEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (
}

func unexpected(err error) error {
return fmt.Errorf("%w: %v", ErrUnexpected, err)
if errors.Is(err, event.ErrUnexpected) {
return err
}
return fmt.Errorf("%w: %v", event.ErrUnexpected, err)
}
17 changes: 10 additions & 7 deletions pkg/cli/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ func ValidateTarget(args *TargetArgs) error {
return ErrInvalidToFormat
}
}
_, err := url.ParseRequestURI(args.AddressableURI)
if err != nil {
return fmt.Errorf("--addressable-uri %w: %s", ErrInvalidURLFormat, err.Error())
return validateAddressableURI(args.AddressableURI)
}

func validateAddressableURI(uri string) error {
if len(uri) > 0 {
_, err := url.ParseRequestURI(uri)
if err != nil {
return fmt.Errorf("--addressable-uri %w: %s", ErrInvalidURLFormat, err.Error())
}
}
return nil
}
Expand All @@ -62,10 +68,7 @@ func createTarget(args *TargetArgs, props *event.Properties) (*event.Target, err
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrInvalidToFormat, err.Error())
}
uri, err := apis.ParseURL(args.AddressableURI)
if err != nil {
return nil, fmt.Errorf("--addressable-uri %w: %s", ErrInvalidURLFormat, err.Error())
}
uri := &apis.URL{Path: args.AddressableURI}
return &event.Target{
Type: event.TargetTypeAddressable,
// FIXME: .Reference.Namespace and .SenderNamespace needs to be filled in if they eql ""
Expand Down
4 changes: 4 additions & 0 deletions pkg/event/constants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package event

import (
"errors"
"fmt"

"github.com/google/uuid"
Expand All @@ -12,6 +13,9 @@ const (
DefaultType = "dev.knative.cli.plugin.event.generic"
)

// ErrUnexpected if unexpected error found.
var ErrUnexpected = errors.New("unexpected")

// DefaultSource holds a default source of an event.
func DefaultSource() string {
return fmt.Sprintf("%s/%s", pkg.PluginName, pkg.Version)
Expand Down
19 changes: 8 additions & 11 deletions pkg/event/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
"knative.dev/pkg/tracker"
)

// DefaultKubeconfig is a default location of kubeconfig.
const DefaultKubeconfig = "~/.kube/config"

// Spec holds specification of event to be created.
type Spec struct {
Type string
Expand Down Expand Up @@ -56,16 +53,16 @@ type Target struct {
*Properties
}

// KubeconfigOptions holds options for Kubernetes Client.
type KubeconfigOptions struct {
Path string
Context string
Cluster string
}

// KnPluginOptions holds options inherited to every Kn plugin.
type KnPluginOptions struct {
// KnConfig holds kn configuration file (default: ~/.config/kn/config.yaml)
KnConfig string

// Kubeconfig holds kubectl configuration file (default: ~/.kube/config)
Kubeconfig string

// LogHTTP tells if kn-event plugin should log HTTP requests it makes
LogHTTP bool
KubeconfigOptions
}

// Properties holds a general properties.
Expand Down
90 changes: 52 additions & 38 deletions pkg/k8s/addressresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
memory "k8s.io/client-go/discovery/cached"
"k8s.io/client-go/restmapper"
"k8s.io/apimachinery/pkg/types"
"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck"
duckv1 "knative.dev/pkg/apis/duck/v1"
"knative.dev/pkg/client/injection/ducks/duck/v1/addressable"
"knative.dev/pkg/injection/clients/dynamicclient"
"knative.dev/pkg/kmeta"
"knative.dev/pkg/resolver"
"knative.dev/pkg/tracker"
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
)

// ReferenceAddressResolver will resolve the tracker.Reference to an url.URL, or
Expand All @@ -25,8 +28,9 @@ type ReferenceAddressResolver interface {
// CreateAddressResolver will create ReferenceAddressResolver, or return an
// error.
func CreateAddressResolver(kube Clients) ReferenceAddressResolver {
ctx := ctxWithDynamic(kube)
return &addressResolver{
kube: kube, ctx: kube.Context(),
kube: kube, ctx: addressable.WithDuck(ctx),
}
}

Expand All @@ -40,25 +44,35 @@ func (a *addressResolver) ResolveAddress(
ref *tracker.Reference,
uri *apis.URL,
) (*url.URL, error) {
gvr, err := a.toGVR(ref)
if err != nil {
return nil, err
if isKsvc(ref) {
// knative.dev/pkg/resolver doesn't resolve proper URL for knative service
return a.resolveKsvcAddress(ref, uri)
}
gvr := a.toGVR(ref)
dest, err := a.toDestination(gvr, ref, uri)
if err != nil {
return nil, err
}
un, err := a.kube.Dynamic().Resource(gvr).
Namespace(ref.Namespace).Get(a.ctx, dest.Ref.Name, metav1.GetOptions{})
parent := toAccessor(ref)
r := resolver.NewURIResolver(a.ctx, noopCallback)
u, err := r.URIFromDestinationV1(a.ctx, *dest, parent)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrNotFound, err)
return nil, fmt.Errorf("%w: %v", ErrNotAddressable, err)
}
addr, err := a.toAddressable(un)
resolved := u.URL()
return resolved, nil
}

func (a *addressResolver) resolveKsvcAddress(
ref *tracker.Reference,
uri *apis.URL,
) (*url.URL, error) {
ksvc, err := a.kube.Serving().Services(ref.Namespace).
Get(a.ctx, ref.Name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrNotAddressable, err)
return nil, fmt.Errorf("%w: %v", ErrNotFound, err)
}
u := addr.URL.ResolveReference(uri).URL()
return u, nil
return ksvc.Status.URL.ResolveReference(uri).URL(), nil
}

func (a *addressResolver) toDestination(
Expand Down Expand Up @@ -95,31 +109,31 @@ func (a *addressResolver) toDestination(
return dest, nil
}

func (a *addressResolver) toGVR(ref *tracker.Reference) (schema.GroupVersionResource, error) {
func (a *addressResolver) toGVR(ref *tracker.Reference) schema.GroupVersionResource {
gvk := ref.GroupVersionKind()
dc := a.kube.Typed().Discovery()
mapper := restmapper.NewDeferredDiscoveryRESTMapper(
memory.NewMemCacheClient(dc),
)
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return schema.GroupVersionResource{},
fmt.Errorf("%w: %v", ErrInvalidReference, err)
}
return mapping.Resource, nil
gvr := apis.KindToResource(gvk)
return gvr
}

func (a *addressResolver) toAddressable(un *unstructured.Unstructured) (*duckv1.Addressable, error) {
gvk := un.GroupVersionKind()
if gvk.Version == "v1" && gvk.Kind == "Service" && gvk.Group == "" {
return &duckv1.Addressable{
URL: apis.HTTP(fmt.Sprintf("%s.%s.svc", un.GetName(), un.GetNamespace())),
}, nil
}
addr := &duckv1.Addressable{}
err := duck.VerifyType(un, addr)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrNotAddressable, err)
}
return addr, nil
func isKsvc(ref *tracker.Reference) bool {
return ref.Kind == "Service" &&
ref.APIVersion == servingv1.SchemeGroupVersion.String()
}

func toAccessor(ref *tracker.Reference) kmeta.Accessor {
return &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": ref.APIVersion,
"kind": ref.Kind,
"metadata": map[string]interface{}{
"name": ref.Name,
"namespace": ref.Namespace,
},
}}
}

func ctxWithDynamic(kube Clients) context.Context {
return context.WithValue(kube.Context(), dynamicclient.Key{}, kube.Dynamic())
}

func noopCallback(_ types.NamespacedName) {
}
Loading

0 comments on commit 0e1fe36

Please sign in to comment.