Skip to content

Commit

Permalink
Merge pull request #33 from erocchi/master
Browse files Browse the repository at this point in the history
Add an empty implementation to list all events and to create a new event
  • Loading branch information
x13n authored Sep 1, 2017
2 parents d0abd53 + 6e79238 commit 9cd1caa
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 135 deletions.
2 changes: 1 addition & 1 deletion event-adapter/adapter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ rules:
- "v1events"
resources:
- "*"
verbs: ["list", "get", "watch"]
verbs: ["list", "get", "post", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
Expand Down
26 changes: 19 additions & 7 deletions event-adapter/cmd/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,42 @@ package provider
import (
"fmt"
"github.com/GoogleCloudPlatform/k8s-stackdriver/event-adapter/pkg/provider"
"github.com/GoogleCloudPlatform/k8s-stackdriver/event-adapter/pkg/types"
stackdriver "google.golang.org/api/monitoring/v3"
"k8s.io/client-go/pkg/api"
)

type sdService stackdriver.Service
//TODO(erocchi) Use the Kubernetes AIP - compatible errors, see example definitions here: https://github.com/kubernetes-incubator/custom-metrics-apiserver/blob/master/pkg/provider/errors.go

//TODO(erocchi) add integration with Stackdriver
type sdService stackdriver.Service

// StackdriverProvider implements EventsProvider
type StackdriverProvider struct {
//TODO(erocchi) Define fields for this struct. It should contain what will be needed to communicate with Stackdriver.
}

//TODO(erocchi) add implementation to communicate with Stackdriver

// NewStackdriverProvider creates a new Provider with standard settings
func NewStackdriverProvider() provider.EventsProvider {
return &StackdriverProvider{}
}

// GetNamespacedEventsByName gets the event with the given name
func (p *StackdriverProvider) GetNamespacedEventsByName(namespace, eventName string) (*types.EventValue, error) {
// GetNamespacedEventsByName gets the event with the given namespace and name
func (p *StackdriverProvider) GetNamespacedEventsByName(namespace, eventName string) (*api.Event, error) {
return nil, fmt.Errorf("GetNamespacedEventsByName is not implemented yet.")
}

// ListAllEventsByNamespace gets all the events
func (p *StackdriverProvider) ListAllEventsByNamespace(namespace string) (*types.EventValueList, error) {
// ListAllEventsByNamespace gets all the events with the given namespace
func (p *StackdriverProvider) ListAllEventsByNamespace(namespace string) (*api.EventList, error) {
return nil, fmt.Errorf("ListAllEventsByNamespace is not implemented yet.")
}

// ListAllEvents gets all the events
func (p *StackdriverProvider) ListAllEvents() (*api.EventList, error) {
return nil, fmt.Errorf("ListAllEvents is not implemented yet.")
}

// CreateNewEvent creates a new event in the given namespace
func (p *StackdriverProvider) CreateNewEvent(namespace string) (*api.Event, error) {
return nil, fmt.Errorf("CreateNewEvent is not implemented yet.")
}
1 change: 0 additions & 1 deletion event-adapter/cmd/server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ func NewCommandStartSampleAdapterServer(out, errOut io.Writer, stopCh <-chan str
flags.StringVar(&o.RemoteKubeConfigFile, "lister-kubeconfig", o.RemoteKubeConfigFile, ""+
"kubeconfig file pointing at the 'core' kubernetes server with enough rights to list "+
"any described objets")

return cmd
}

Expand Down
29 changes: 25 additions & 4 deletions event-adapter/pkg/apiserver/installer/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/GoogleCloudPlatform/k8s-stackdriver/event-adapter/pkg/provider"
restorage "github.com/GoogleCloudPlatform/k8s-stackdriver/event-adapter/pkg/registry"
"github.com/GoogleCloudPlatform/k8s-stackdriver/event-adapter/pkg/types"
"k8s.io/client-go/pkg/api"
)

// defaultAPIServer exposes nested objects for testability.
Expand Down Expand Up @@ -136,9 +137,9 @@ func handle(prov provider.EventsProvider) http.Handler {

type fakeProvider struct{}

func (p *fakeProvider) GetNamespacedEventsByName(namespace, name string) (*types.EventValue, error) {
func (p *fakeProvider) GetNamespacedEventsByName(namespace, name string) (*api.Event, error) {
if namespace == "default" && name == "existing_event" {
return &types.EventValue{}, nil
return &api.Event{}, nil
}

err := NotFoundError{
Expand All @@ -157,8 +158,16 @@ func (p *fakeProvider) GetNamespacedEventsByName(namespace, name string) (*types
return nil, fmt.Errorf("Namespace : %s, event name: %s", namespace, name)
}

func (p *fakeProvider) ListAllEventsByNamespace(namespace string) (*types.EventValueList, error) {
return &types.EventValueList{}, nil
func (p *fakeProvider) ListAllEventsByNamespace(namespace string) (*api.EventList, error) {
return &api.EventList{}, nil
}

func (p *fakeProvider) ListAllEvents() (*api.EventList, error) {
return &api.EventList{}, nil
}

func (p *fakeProvider) CreateNewEvent(namespace string) (*api.Event, error) {
return &api.Event{}, nil
}

func TestEventsAPI(t *testing.T) {
Expand All @@ -185,6 +194,18 @@ func TestEventsAPI(t *testing.T) {

"GET no namespace": {"GET", prefix + "/" + group + "/foo/default/events/foo", http.StatusNotFound},
"GET wrong prefix": {"GET", "//apis/v1foo/v1alpha1/", http.StatusNotFound},

"GET list all events": {"GET", prefix + "/" + group + "/events", http.StatusOK},
"GET list all events with typo": {"GET", prefix + "/" + group + "/foo", http.StatusNotFound},

"POST create a new event": {"POST", prefix + "/" + group + "/namespaces/default/events", http.StatusOK},
"POST create a new event without namespace": {"POST", prefix + "/" + group + "/events", http.StatusMethodNotAllowed},
"POST create a new event giving name": {"POST", prefix + "/" + group + "/namespaces/default/events/foo", http.StatusMethodNotAllowed},
"POST prefix": {"POST", prefix + "/" + group, http.StatusMethodNotAllowed},

"POST no namespace": {"POST", prefix + "/" + group + "/foo/default/events", http.StatusNotFound},
"POST no events": {"POST", prefix + "/" + group + "/namespaces/default/foo", http.StatusNotFound},
"POST wrong prefix": {"POST", "//apis/v1foo/v1alpha1/", http.StatusNotFound},
}

prov := &fakeProvider{}
Expand Down
37 changes: 28 additions & 9 deletions event-adapter/pkg/apiserver/installer/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ import (
"k8s.io/apiserver/pkg/endpoints/request"
)

// resourceInformation holds the resource and subresource for a request in the context.
type resourceInformation struct {
// requestInformation holds the resource and method for a request in the context.
type requestInformation struct {
resource string
method string
}

type requestListInformation struct {
method string
}

// contextKey is the type of the keys for the context in this file.
Expand All @@ -31,16 +36,30 @@ type contextKey int

const resourceKey contextKey = iota

// WithResourceInformation returns a copy of parent in which the resource and subresource values are set
func WithResourceInformation(parent request.Context, resource string) request.Context {
return request.WithValue(parent, resourceKey, resourceInformation{resource})
// WithRequestInformation returns a copy of parent in which the resource and method values are set
func WithRequestInformation(parent request.Context, resource, method string) request.Context {
return request.WithValue(parent, resourceKey, requestInformation{resource, method})
}

// WithRequestListInformation returns a copy of parent in which the method values is set
func WithRequestListInformation(parent request.Context, method string) request.Context {
return request.WithValue(parent, resourceKey, requestListInformation{method})
}

// RequestInformationFrom returns resource and method on the ctx
func RequestInformationFrom(ctx request.Context) (resource string, method string, ok bool) {
requestInfo, ok := ctx.Value(resourceKey).(requestInformation)
if !ok {
return "", "", ok
}
return requestInfo.resource, requestInfo.method, ok
}

// ResourceInformationFrom returns resource and subresource on the ctx
func ResourceInformationFrom(ctx request.Context) (resource string, ok bool) {
resourceInfo, ok := ctx.Value(resourceKey).(resourceInformation)
// RequestListInformationFrom returns method on the ctx
func RequestListInformationFrom(ctx request.Context) (method string, ok bool) {
requestInfo, ok := ctx.Value(resourceKey).(requestListInformation)
if !ok {
return "", ok
}
return resourceInfo.resource, ok
return requestInfo.method, ok
}
123 changes: 82 additions & 41 deletions event-adapter/pkg/apiserver/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"strings"
"time"

"github.com/emicklei/go-restful"
restful "github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -172,71 +172,60 @@ func (a *EventsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws *
return err
}

ctxFn := func(req *restful.Request) request.Context {
var ctx request.Context
if ctx != nil {
if existingCtx, ok := context.Get(req.Request); ok {
ctx = existingCtx
}
}
if ctx == nil {
ctx = request.NewContext()
}

// inject the name here so that
// we don't have to write custom handler logic

ctx = request.WithUserAgent(ctx, req.HeaderParameter("User-Agent"))
name := req.PathParameter("name")
ctx = specificcontext.WithResourceInformation(ctx, name)

return ctx
}

scope := mapping.Scope
nameParam := ws.PathParameter("name", "name of the described event").DataType("string")
resourceParam := ws.PathParameter("resource", "the name of the resource").DataType("string")
namespaceParam := ws.PathParameter(scope.ArgumentName(), scope.ParamDescription()).DataType("string")

namespacedParams := []*restful.Parameter{
nameParam,
resourceParam,
namespaceParam,
}

//EVENT REGEXP:
// REGISTER: BY NAME & NAMESPACE
namespacedPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/events/{name}"

mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
allMediaTypes := append(mediaTypes, streamMediaTypes...)
ws.Produces(allMediaTypes...)

reqScope := handlers.RequestScope{
ContextFunc: ctxFn,
Serializer: a.group.Serializer,
ParameterCodec: a.group.ParameterCodec,
Creater: a.group.Creater,
Convertor: a.group.Convertor,
Copier: a.group.Copier,
Typer: a.group.Typer,
UnsafeConvertor: a.group.UnsafeConvertor,

// TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this.
Resource: a.group.GroupVersion.WithResource("*"),
Subresource: "*",
Kind: fqKindToRegister,

Resource: a.group.GroupVersion.WithResource("*"),
Subresource: "*",
Kind: fqKindToRegister,
MetaGroupVersion: metav1.SchemeGroupVersion,
}

ctxFn := func(req *restful.Request) request.Context {
var ctx request.Context
if ctx != nil {
if existingCtx, ok := context.Get(req.Request); ok {
ctx = existingCtx
}
}
if ctx == nil {
ctx = request.NewContext()
}
ctx = request.WithUserAgent(ctx, req.HeaderParameter("User-Agent"))
name := req.PathParameter("name")
ctx = specificcontext.WithRequestInformation(ctx, name, "GET")
return ctx
}
reqScope.ContextFunc = ctxFn
if a.group.MetaGroupVersion != nil {
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
}

// we need one path for namespaced resources, one for non-namespaced resources
doc := "list events"
// install the namespace-scoped route
reqScope.Namer = scopeNaming{scope, a.group.Linker, nil, false}
namespacedHandler := handlers.ListResource(lister, nil, reqScope, false, a.minRequestTimeout)
doc := "get event with the given name and namespace"
namespacedRoute := ws.GET(namespacedPath).To(namespacedHandler).
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Expand All @@ -250,13 +239,63 @@ func (a *EventsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws *
addParams(namespacedRoute, namespacedParams)
ws.Route(namespacedRoute)

//REGISTER LIST ALL EVENT
//PATH namespaces/{namespace}/events/{eventName}

namespacedListPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/events"
namespacedListParams := []*restful.Parameter{
namespaceParam,
resourceParam,
}

docList := map[string]string{
"GET": "list events with the given namespace",
"POST": "create an event with the given namepsace",
}

methods := map[string]func(ws *restful.WebService, path string) *restful.RouteBuilder{
"GET": func(ws *restful.WebService, path string) *restful.RouteBuilder {
return ws.GET(path)
},
"POST": func(ws *restful.WebService, path string) *restful.RouteBuilder {
return ws.POST(path)
},
}

for k, v := range methods {
method := k
namespacedCtxFn := func(req *restful.Request) request.Context {
var ctx request.Context
if ctx != nil {
if existingCtx, ok := context.Get(req.Request); ok {
ctx = existingCtx
}
}
if ctx == nil {
ctx = request.NewContext()
}

ctx = request.WithUserAgent(ctx, req.HeaderParameter("User-Agent"))
ctx = specificcontext.WithRequestListInformation(ctx, method)
return ctx
}
reqScope.ContextFunc = namespacedCtxFn
namespacedHandler := handlers.ListResource(lister, nil, reqScope, false, a.minRequestTimeout)
routeBuilder := v(ws, namespacedListPath)
namespacedRoute := routeBuilder.To(namespacedHandler).
Doc(docList[method]).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Produces(allMediaTypes...).
Returns(http.StatusOK, "OK", versionedList).
Writes(versionedList)
if err := addObjectParams(ws, namespacedRoute, versionedListOptions); err != nil {
return err
}
addParams(namespacedRoute, namespacedListParams)
ws.Route(namespacedRoute)
}

//LIST ALL EVENTS
eventsListPath := "events"

ctxFnList := func(req *restful.Request) request.Context {
var ctx request.Context
if ctx != nil {
Expand All @@ -269,24 +308,26 @@ func (a *EventsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws *
}

ctx = request.WithUserAgent(ctx, req.HeaderParameter("User-Agent"))
ctx = specificcontext.WithRequestListInformation(ctx, "GET")

return ctx
}

reqScope.ContextFunc = ctxFnList
namespacedHandler = handlers.ListResource(lister, nil, reqScope, false, a.minRequestTimeout)
namespacedRoute = ws.GET(namespacedListPath).To(namespacedHandler).
reqScope.Namer = rootScopeNaming{scope, a.group.Linker, a.prefix, eventsListPath}
rootScopedHandler := handlers.ListResource(lister, nil, reqScope, false, a.minRequestTimeout)
doc = "list all the events"
rootScopedRoute := ws.GET(eventsListPath).To(rootScopedHandler).
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("listNamespaced"+kind).
Produces(allMediaTypes...).
Returns(http.StatusOK, "OK", versionedList).
Writes(versionedList)
if err := addObjectParams(ws, namespacedRoute, versionedListOptions); err != nil {
if err := addObjectParams(ws, rootScopedRoute, versionedListOptions); err != nil {
return err
}
addParams(namespacedRoute, namespacedListParams)
ws.Route(namespacedRoute)
ws.Route(rootScopedRoute)

return nil
}
Expand Down
1 change: 1 addition & 0 deletions event-adapter/pkg/apiserver/installer/installer_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
Copyright 2017 The Kubernetes 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
Expand Down
Loading

0 comments on commit 9cd1caa

Please sign in to comment.