Commit 19e18d18 authored by Scarlett Perry's avatar Scarlett Perry
Browse files

Merge branch 'main' into sperry-project-selector

parents 883c1df0 b808fb78
Showing with 2397 additions and 1946 deletions
+2397 -1946
......@@ -101,6 +101,13 @@ service K8sAPI {
option (clutch.api.v1.action).type = READ;
}
rpc ListServices(ListServicesRequest) returns (ListServicesResponse) {
option (google.api.http) = {
post : "/v1/k8s/listServices"
body : "*"
};
option (clutch.api.v1.action).type = READ;
}
rpc DeleteService(DeleteServiceRequest) returns (DeleteServiceResponse) {
option (google.api.http) = {
post : "/v1/k8s/deleteService"
......@@ -303,12 +310,6 @@ message PodCondition {
Status status = 2;
}
message Event {
string name = 1;
string reason = 2;
string description = 3;
}
message Pod {
option (clutch.api.v1.id).patterns = {
type_url : "clutch.k8s.v1.Pod",
......@@ -362,9 +363,6 @@ message Pod {
// issues for well-known types like google.protobuf.Timestamp
// Unix timestamp (milliseconds since Jan 01 1970)
int64 start_time_millis = 15;
// pod events that represent state changes
repeated Event events = 16;
}
message ListOptions {
......@@ -751,6 +749,11 @@ message Service {
map<string, string> labels = 5;
map<string, string> annotations = 6;
// service traffic routed to pods with label keys
// and values matching this selector.
// ref: https://pkg.go.dev/k8s.io/api/core/v1#ServiceSpec
map<string, string> selector = 7;
}
message DescribeServiceRequest {
......@@ -771,6 +774,20 @@ message DescribeServiceResponse {
Service service = 1;
}
message ListServicesRequest {
string clientset = 1 [ (validate.rules).string = {min_bytes : 1} ];
string cluster = 2 [ (validate.rules).string = {min_bytes : 1} ];
string namespace = 3 [ (validate.rules).string = {min_bytes : 1} ];
ListOptions options = 4 [ (validate.rules).message = {required : true} ];
}
message ListServicesResponse {
option (clutch.api.v1.reference).fields = "services";
repeated Service services = 1;
}
message DeleteServiceRequest {
option (clutch.api.v1.id).patterns = {
type_url : "clutch.k8s.v1.Service",
......
This diff is collapsed.
......@@ -405,6 +405,40 @@ func local_request_K8SAPI_DescribeService_0(ctx context.Context, marshaler runti
}
func request_K8SAPI_ListServices_0(ctx context.Context, marshaler runtime.Marshaler, client K8SAPIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListServicesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListServices(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_K8SAPI_ListServices_0(ctx context.Context, marshaler runtime.Marshaler, server K8SAPIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListServicesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListServices(ctx, &protoReq)
return msg, metadata, err
}
func request_K8SAPI_DeleteService_0(ctx context.Context, marshaler runtime.Marshaler, client K8SAPIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteServiceRequest
var metadata runtime.ServerMetadata
......@@ -1174,6 +1208,29 @@ func RegisterK8SAPIHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser
})
mux.Handle("POST", pattern_K8SAPI_ListServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/clutch.k8s.v1.K8SAPI/ListServices")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_K8SAPI_ListServices_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_K8SAPI_ListServices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_K8SAPI_DeleteService_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
......@@ -1780,6 +1837,26 @@ func RegisterK8SAPIHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli
})
mux.Handle("POST", pattern_K8SAPI_ListServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/clutch.k8s.v1.K8SAPI/ListServices")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_K8SAPI_ListServices_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_K8SAPI_ListServices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_K8SAPI_DeleteService_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
......@@ -2106,6 +2183,8 @@ var (
pattern_K8SAPI_DescribeService_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "k8s", "describeService"}, ""))
pattern_K8SAPI_ListServices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "k8s", "listServices"}, ""))
pattern_K8SAPI_DeleteService_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "k8s", "deleteService"}, ""))
pattern_K8SAPI_DescribeStatefulSet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "k8s", "describeStatefulSet"}, ""))
......@@ -2160,6 +2239,8 @@ var (
forward_K8SAPI_DescribeService_0 = runtime.ForwardResponseMessage
forward_K8SAPI_ListServices_0 = runtime.ForwardResponseMessage
forward_K8SAPI_DeleteService_0 = runtime.ForwardResponseMessage
forward_K8SAPI_DescribeStatefulSet_0 = runtime.ForwardResponseMessage
......
......@@ -609,76 +609,6 @@ var _ interface {
ErrorName() string
} = PodConditionValidationError{}
// Validate checks the field values on Event with the rules defined in the
// proto definition for this message. If any rules are violated, an error is returned.
func (m *Event) Validate() error {
if m == nil {
return nil
}
// no validation rules for Name
// no validation rules for Reason
// no validation rules for Description
return nil
}
// EventValidationError is the validation error returned by Event.Validate if
// the designated constraints aren't met.
type EventValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e EventValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e EventValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e EventValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e EventValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e EventValidationError) ErrorName() string { return "EventValidationError" }
// Error satisfies the builtin error interface
func (e EventValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sEvent.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = EventValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = EventValidationError{}
// Validate checks the field values on Pod with the rules defined in the proto
// definition for this message. If any rules are violated, an error is returned.
func (m *Pod) Validate() error {
......@@ -763,21 +693,6 @@ func (m *Pod) Validate() error {
// no validation rules for StartTimeMillis
for idx, item := range m.GetEvents() {
_, _ = idx, item
if v, ok := interface{}(item).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return PodValidationError{
field: fmt.Sprintf("Events[%v]", idx),
reason: "embedded message failed validation",
cause: err,
}
}
}
}
return nil
}
......@@ -3451,6 +3366,8 @@ func (m *Service) Validate() error {
// no validation rules for Annotations
// no validation rules for Selector
return nil
}
......@@ -3680,6 +3597,193 @@ var _ interface {
ErrorName() string
} = DescribeServiceResponseValidationError{}
// Validate checks the field values on ListServicesRequest with the rules
// defined in the proto definition for this message. If any rules are
// violated, an error is returned.
func (m *ListServicesRequest) Validate() error {
if m == nil {
return nil
}
if len(m.GetClientset()) < 1 {
return ListServicesRequestValidationError{
field: "Clientset",
reason: "value length must be at least 1 bytes",
}
}
if len(m.GetCluster()) < 1 {
return ListServicesRequestValidationError{
field: "Cluster",
reason: "value length must be at least 1 bytes",
}
}
if len(m.GetNamespace()) < 1 {
return ListServicesRequestValidationError{
field: "Namespace",
reason: "value length must be at least 1 bytes",
}
}
if m.GetOptions() == nil {
return ListServicesRequestValidationError{
field: "Options",
reason: "value is required",
}
}
if v, ok := interface{}(m.GetOptions()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return ListServicesRequestValidationError{
field: "Options",
reason: "embedded message failed validation",
cause: err,
}
}
}
return nil
}
// ListServicesRequestValidationError is the validation error returned by
// ListServicesRequest.Validate if the designated constraints aren't met.
type ListServicesRequestValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e ListServicesRequestValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e ListServicesRequestValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e ListServicesRequestValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e ListServicesRequestValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e ListServicesRequestValidationError) ErrorName() string {
return "ListServicesRequestValidationError"
}
// Error satisfies the builtin error interface
func (e ListServicesRequestValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sListServicesRequest.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = ListServicesRequestValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = ListServicesRequestValidationError{}
// Validate checks the field values on ListServicesResponse with the rules
// defined in the proto definition for this message. If any rules are
// violated, an error is returned.
func (m *ListServicesResponse) Validate() error {
if m == nil {
return nil
}
for idx, item := range m.GetServices() {
_, _ = idx, item
if v, ok := interface{}(item).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return ListServicesResponseValidationError{
field: fmt.Sprintf("Services[%v]", idx),
reason: "embedded message failed validation",
cause: err,
}
}
}
}
return nil
}
// ListServicesResponseValidationError is the validation error returned by
// ListServicesResponse.Validate if the designated constraints aren't met.
type ListServicesResponseValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e ListServicesResponseValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e ListServicesResponseValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e ListServicesResponseValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e ListServicesResponseValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e ListServicesResponseValidationError) ErrorName() string {
return "ListServicesResponseValidationError"
}
// Error satisfies the builtin error interface
func (e ListServicesResponseValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sListServicesResponse.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = ListServicesResponseValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = ListServicesResponseValidationError{}
// Validate checks the field values on DeleteServiceRequest with the rules
// defined in the proto definition for this message. If any rules are
// violated, an error is returned.
......
......@@ -29,6 +29,7 @@ type K8SAPIClient interface {
UpdateDeployment(ctx context.Context, in *UpdateDeploymentRequest, opts ...grpc.CallOption) (*UpdateDeploymentResponse, error)
DeleteDeployment(ctx context.Context, in *DeleteDeploymentRequest, opts ...grpc.CallOption) (*DeleteDeploymentResponse, error)
DescribeService(ctx context.Context, in *DescribeServiceRequest, opts ...grpc.CallOption) (*DescribeServiceResponse, error)
ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error)
DeleteService(ctx context.Context, in *DeleteServiceRequest, opts ...grpc.CallOption) (*DeleteServiceResponse, error)
DescribeStatefulSet(ctx context.Context, in *DescribeStatefulSetRequest, opts ...grpc.CallOption) (*DescribeStatefulSetResponse, error)
ListStatefulSets(ctx context.Context, in *ListStatefulSetsRequest, opts ...grpc.CallOption) (*ListStatefulSetsResponse, error)
......@@ -153,6 +154,15 @@ func (c *k8SAPIClient) DescribeService(ctx context.Context, in *DescribeServiceR
return out, nil
}
func (c *k8SAPIClient) ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error) {
out := new(ListServicesResponse)
err := c.cc.Invoke(ctx, "/clutch.k8s.v1.K8sAPI/ListServices", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *k8SAPIClient) DeleteService(ctx context.Context, in *DeleteServiceRequest, opts ...grpc.CallOption) (*DeleteServiceResponse, error) {
out := new(DeleteServiceResponse)
err := c.cc.Invoke(ctx, "/clutch.k8s.v1.K8sAPI/DeleteService", in, out, opts...)
......@@ -303,6 +313,7 @@ type K8SAPIServer interface {
UpdateDeployment(context.Context, *UpdateDeploymentRequest) (*UpdateDeploymentResponse, error)
DeleteDeployment(context.Context, *DeleteDeploymentRequest) (*DeleteDeploymentResponse, error)
DescribeService(context.Context, *DescribeServiceRequest) (*DescribeServiceResponse, error)
ListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error)
DeleteService(context.Context, *DeleteServiceRequest) (*DeleteServiceResponse, error)
DescribeStatefulSet(context.Context, *DescribeStatefulSetRequest) (*DescribeStatefulSetResponse, error)
ListStatefulSets(context.Context, *ListStatefulSetsRequest) (*ListStatefulSetsResponse, error)
......@@ -357,6 +368,9 @@ func (UnimplementedK8SAPIServer) DeleteDeployment(context.Context, *DeleteDeploy
func (UnimplementedK8SAPIServer) DescribeService(context.Context, *DescribeServiceRequest) (*DescribeServiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DescribeService not implemented")
}
func (UnimplementedK8SAPIServer) ListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListServices not implemented")
}
func (UnimplementedK8SAPIServer) DeleteService(context.Context, *DeleteServiceRequest) (*DeleteServiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteService not implemented")
}
......@@ -612,6 +626,24 @@ func _K8SAPI_DescribeService_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _K8SAPI_ListServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListServicesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(K8SAPIServer).ListServices(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/clutch.k8s.v1.K8sAPI/ListServices",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(K8SAPIServer).ListServices(ctx, req.(*ListServicesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _K8SAPI_DeleteService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteServiceRequest)
if err := dec(in); err != nil {
......@@ -933,6 +965,10 @@ var K8SAPI_ServiceDesc = grpc.ServiceDesc{
MethodName: "DescribeService",
Handler: _K8SAPI_DescribeService_Handler,
},
{
MethodName: "ListServices",
Handler: _K8SAPI_ListServices_Handler,
},
{
MethodName: "DeleteService",
Handler: _K8SAPI_DeleteService_Handler,
......
......@@ -181,6 +181,22 @@ func (s *svc) DescribeService(_ context.Context, clientset, cluster, namespace,
}, nil
}
func (s *svc) ListServices(_ context.Context, clientset, cluster, namespace string, listOptions *k8sv1.ListOptions) ([]*k8sv1.Service, error) {
services := []*k8sv1.Service{
&k8sv1.Service{
Cluster: "fake-cluster-name",
Namespace: namespace,
Name: "service1",
},
&k8sv1.Service{
Cluster: "fake-cluster-name",
Namespace: namespace,
Name: "service2",
},
}
return services, nil
}
func (*svc) DeleteService(ctx context.Context, clientset, cluster, namespace, name string) error {
return nil
}
......
......@@ -47,7 +47,10 @@ func (m *moduleImpl) Register(r module.Registrar) error {
}
func (m *moduleImpl) GetFlags(ctx context.Context, req *featureflagv1.GetFlagsRequest) (*featureflagv1.GetFlagsResponse, error) {
flags := make(map[string]*featureflagv1.Flag, len(m.simple.Flags))
flags := make(map[string]*featureflagv1.Flag)
if m.simple == nil {
return &featureflagv1.GetFlagsResponse{Flags: flags}, nil
}
for i, flag := range m.simple.Flags {
flags[i] = &featureflagv1.Flag{Type: &featureflagv1.Flag_BooleanValue{BooleanValue: flag}}
}
......
......@@ -25,7 +25,7 @@ func TestModule(t *testing.T) {
assert.True(t, r.JSONRegistered())
}
func TestAPI(t *testing.T) {
func TestAPIWithFlags(t *testing.T) {
m, err := newModuleImpl(
&featureflagv1.Simple{
Flags: map[string]bool{
......@@ -44,3 +44,17 @@ func TestAPI(t *testing.T) {
assert.Equal(t, false, resp.Flags["bar"].GetBooleanValue())
assert.Len(t, resp.Flags, 2)
}
func TestAPIWithoutFlags(t *testing.T) {
m, err := newModuleImpl(
&featureflagv1.Simple{
Flags: map[string]bool{},
},
)
assert.NoError(t, err)
assert.NotNil(t, m)
_, respErr := m.GetFlags(context.Background(), nil)
assert.NoError(t, respErr)
}
......@@ -21,3 +21,11 @@ func (a *k8sAPI) DeleteService(ctx context.Context, req *k8sapiv1.DeleteServiceR
}
return &k8sapiv1.DeleteServiceResponse{}, nil
}
func (a *k8sAPI) ListServices(ctx context.Context, req *k8sapiv1.ListServicesRequest) (*k8sapiv1.ListServicesResponse, error) {
services, err := a.k8s.ListServices(ctx, req.Clientset, req.Cluster, req.Namespace, req.Options)
if err != nil {
return nil, err
}
return &k8sapiv1.ListServicesResponse{Services: services}, nil
}
......@@ -180,7 +180,7 @@ func (s *svc) informerDeleteHandler(obj interface{}) {
func (s *svc) processInformerEvent(obj interface{}, action topologyv1.UpdateCacheRequest_Action) {
switch objType := obj.(type) {
case *corev1.Pod:
pod := podDescription(objType, "", []corev1.Event{})
pod := podDescription(objType, "")
protoPod, err := anypb.New(pod)
if err != nil {
s.log.Error("unable to marshal pod", zap.Error(err))
......
......@@ -32,7 +32,7 @@ func TestProcessInformerEvent(t *testing.T) {
},
}
expectedClutchPod := podDescription(pod, "", []corev1.Event{})
expectedClutchPod := podDescription(pod, "")
protoPod, err := anypb.New(expectedClutchPod)
assert.NoError(t, err)
......
......@@ -72,6 +72,7 @@ type Service interface {
// Service management functions.
DescribeService(ctx context.Context, clientset, cluster, namespace, name string) (*k8sapiv1.Service, error)
ListServices(ctx context.Context, clientset, cluster, namespace string, listOptions *k8sapiv1.ListOptions) ([]*k8sapiv1.Service, error)
DeleteService(ctx context.Context, clientset, cluster, namespace, name string) error
// StatefulSet management functions.
......
......@@ -9,8 +9,6 @@ import (
"google.golang.org/grpc/status"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/reference"
k8sapiv1 "github.com/lyft/clutch/backend/api/k8s/v1"
)
......@@ -29,26 +27,13 @@ func (s *svc) DescribePod(ctx context.Context, clientset, cluster, namespace, na
}
if len(pods.Items) == 1 {
return podDescription(&pods.Items[0], cs.Cluster(), getPodEvents(&pods.Items[0], cs)), nil
return podDescription(&pods.Items[0], cs.Cluster()), nil
} else if len(pods.Items) > 1 {
return nil, status.Error(codes.FailedPrecondition, "located multiple pods")
}
return nil, status.Error(codes.NotFound, "unable to locate specified pod")
}
func getPodEvents(pod *corev1.Pod, cs ContextClientset) []corev1.Event {
var k8sEvents []corev1.Event
ref, err := reference.GetReference(scheme.Scheme, pod)
if err != nil {
return k8sEvents
}
eventList, err := cs.CoreV1().Events(pod.Namespace).Search(scheme.Scheme, ref)
if err != nil {
return k8sEvents
}
return eventList.Items
}
func (s *svc) DeletePod(ctx context.Context, clientset, cluster, namespace, name string) error {
cs, err := s.manager.GetK8sClientset(ctx, clientset, cluster, namespace)
if err != nil {
......@@ -81,7 +66,7 @@ func (s *svc) ListPods(ctx context.Context, clientset, cluster, namespace string
var pods []*k8sapiv1.Pod
for _, p := range podList.Items {
pod := p
pods = append(pods, podDescription(&pod, cs.Cluster(), getPodEvents(&pod, cs)))
pods = append(pods, podDescription(&pod, cs.Cluster()))
}
return pods, nil
......@@ -183,7 +168,7 @@ func (s *svc) checkExpectedObjectMetaFields(expectedObjectMetaFields *k8sapiv1.E
return &ExpectedObjectMetaFieldsCheckError{MismatchedAnnotations: mismatchedAnnotations}
}
func podDescription(k8spod *corev1.Pod, cluster string, k8sEvents []corev1.Event) *k8sapiv1.Pod {
func podDescription(k8spod *corev1.Pod, cluster string) *k8sapiv1.Pod {
// TODO: There's a mismatch between the serialization of the timestamp here and what's expected
// on the frontend.
// var launch *timestamp.Timestamp
......@@ -217,26 +202,9 @@ func podDescription(k8spod *corev1.Pod, cluster string, k8sEvents []corev1.Event
pod.StartTimeMillis = k8spod.Status.StartTime.UnixNano() / 1e6
}
if len(k8sEvents) > 0 {
pod.Events = makeEvents(k8sEvents)
}
return pod
}
func makeEvents(k8sEvents []corev1.Event) []*k8sapiv1.Event {
events := make([]*k8sapiv1.Event, 0, len(k8sEvents))
for _, k8sEvent := range k8sEvents {
event := &k8sapiv1.Event{
Name: k8sEvent.Name,
Reason: k8sEvent.Reason,
Description: k8sEvent.Message,
}
events = append(events, event)
}
return events
}
func makeConditions(conditions []corev1.PodCondition) []*k8sapiv1.PodCondition {
podConditions := make([]*k8sapiv1.PodCondition, 0, len(conditions))
for _, condition := range conditions {
......
......@@ -223,7 +223,6 @@ func TestPodDescription(t *testing.T) {
inputClusterName string
expectedClusterName string
pod *corev1.Pod
events []corev1.Event
}{
{
id: "clustername already set",
......@@ -254,15 +253,6 @@ func TestPodDescription(t *testing.T) {
},
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test-1",
},
Reason: "reason-test-1",
Message: "description-test-1",
},
},
},
{
id: "custername is not set",
......@@ -293,15 +283,6 @@ func TestPodDescription(t *testing.T) {
},
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test-2",
},
Reason: "reason-test-2",
Message: "description-test-2",
},
},
},
}
......@@ -310,7 +291,7 @@ func TestPodDescription(t *testing.T) {
t.Run(tt.id, func(t *testing.T) {
t.Parallel()
pod := podDescription(tt.pod, tt.inputClusterName, tt.events)
pod := podDescription(tt.pod, tt.inputClusterName)
assert.Equal(t, tt.expectedClusterName, pod.Cluster)
assert.Equal(t, tt.pod.Status.Reason, pod.StateReason)
assert.Equal(t, tt.pod.Status.InitContainerStatuses[0].Name, pod.InitContainers[0].Name)
......@@ -318,7 +299,6 @@ func TestPodDescription(t *testing.T) {
assert.Equal(t, k8sv1.PodCondition_Status(1), pod.PodConditions[0].Status)
assert.Equal(t, "Init: 0/0", pod.Status)
assert.NotNil(t, pod.StartTimeMillis)
assert.Equal(t, 1, len(pod.Events))
})
}
}
......@@ -547,7 +527,6 @@ func TestPodStatus(t *testing.T) {
id string
expectedPodStatus string
pod *corev1.Pod
events []corev1.Event
}{
{
id: "Error Status",
......@@ -571,15 +550,6 @@ func TestPodStatus(t *testing.T) {
},
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test",
},
Reason: "reason-test",
Message: "description-test",
},
},
},
{
id: "Error Status",
......@@ -603,15 +573,6 @@ func TestPodStatus(t *testing.T) {
},
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test",
},
Reason: "reason-test",
Message: "description-test",
},
},
},
{
id: "Running Successfully",
......@@ -636,15 +597,6 @@ func TestPodStatus(t *testing.T) {
},
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test",
},
Reason: "reason-test",
Message: "description-test",
},
},
},
{
id: "Terminating",
......@@ -654,15 +606,6 @@ func TestPodStatus(t *testing.T) {
DeletionTimestamp: &timeStamp,
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test",
},
Reason: "reason-test",
Message: "description-test",
},
},
},
{
id: "PodInitializing",
......@@ -686,15 +629,6 @@ func TestPodStatus(t *testing.T) {
},
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test",
},
Reason: "reason-test",
Message: "description-test",
},
},
},
{
id: "PodTerminating",
......@@ -718,15 +652,6 @@ func TestPodStatus(t *testing.T) {
},
},
},
events: []corev1.Event{
corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "event-test",
},
Reason: "reason-test",
Message: "description-test",
},
},
},
}
......@@ -735,12 +660,8 @@ func TestPodStatus(t *testing.T) {
t.Run(tt.id, func(t *testing.T) {
t.Parallel()
pod := podDescription(tt.pod, "staging", tt.events)
pod := podDescription(tt.pod, "staging")
assert.Equal(t, tt.expectedPodStatus, pod.Status)
assert.Equal(t, 1, len(pod.Events))
assert.Equal(t, tt.events[0].Name, pod.Events[0].Name)
assert.Equal(t, tt.events[0].Reason, pod.Events[0].Reason)
assert.Equal(t, tt.events[0].Message, pod.Events[0].Description)
})
}
}
......@@ -33,6 +33,31 @@ func (s *svc) DescribeService(ctx context.Context, clientset, cluster, namespace
return nil, status.Error(codes.NotFound, "unable to locate specified service")
}
func (s *svc) ListServices(ctx context.Context, clientset, cluster, namespace string, listOptions *k8sapiv1.ListOptions) ([]*k8sapiv1.Service, error) {
cs, err := s.manager.GetK8sClientset(ctx, clientset, cluster, namespace)
if err != nil {
return nil, err
}
opts, err := ApplyListOptions(listOptions)
if err != nil {
return nil, err
}
serviceList, err := cs.CoreV1().Services(cs.Namespace()).List(ctx, opts)
if err != nil {
return nil, err
}
var services []*k8sapiv1.Service
for _, d := range serviceList.Items {
service := d
services = append(services, ProtoForService(cs.Cluster(), &service))
}
return services, nil
}
func (s *svc) DeleteService(ctx context.Context, clientset, cluster, namespace, name string) error {
cs, err := s.manager.GetK8sClientset(ctx, clientset, cluster, namespace)
if err != nil {
......@@ -55,6 +80,7 @@ func ProtoForService(cluster string, k8sservice *corev1.Service) *k8sapiv1.Servi
Type: protoForServiceType(k8sservice.Spec.Type),
Labels: k8sservice.Labels,
Annotations: k8sservice.Annotations,
Selector: k8sservice.Spec.Selector,
}
}
......
......@@ -21,6 +21,9 @@ func testServiceClientset() k8s.Interface {
Labels: map[string]string{"foo": "bar"},
Annotations: map[string]string{"baz": "quuz"},
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{"key1": "value1"},
},
}
return fake.NewSimpleClientset(svc)
......@@ -66,6 +69,26 @@ func TestDeleteService(t *testing.T) {
assert.Error(t, err)
}
func TestListServices(t *testing.T) {
cs := testServiceClientset()
s := &svc{
manager: &managerImpl{
clientsets: map[string]*ctxClientsetImpl{"foo": &ctxClientsetImpl{
Interface: cs,
namespace: "default",
cluster: "core-testing",
}},
},
}
opts := &k8sv1.ListOptions{}
list, err := s.ListServices(context.Background(), "foo", "core-testing", "testing-namespace", opts)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
assert.Equal(t, 1, len(list[0].Selector))
assert.Equal(t, "value1", list[0].Selector["key1"])
}
func TestProtoForServiceClusterName(t *testing.T) {
t.Parallel()
......
......@@ -37,6 +37,7 @@
"@types/react-helmet": "^6.1.0",
"@types/react-router-dom": "^5.1.7",
"chokidar": "^3.5.1",
"react-tiny-popover": "^6.0.5",
"typescript": "^4.1.3"
}
}
import React from "react";
import BrowserOnly from "@docusaurus/BrowserOnly";
import { ArrowContainer, Popover } from "react-tiny-popover"
import "./styles.css";
interface ShareLinkProps {
className: string;
label: string;
color: string;
link?: string;
onClick?: () => void;
}
const ShareLink = ({ className, label, color, link, onClick }: ShareLinkProps) => (
<a
className="share-link"
href={link}
target="_blank"
rel="noopener noreferrer"
onClick={onClick}
>
<span className={`fe ${className}`} style={{ margin: "7px 10px", color }} />
{label}
</a>
);
interface ShareProps {
title: string;
authors: {
name: string;
url: string;
twitter_username?: string;
}[];
}
const Share = ({title, authors}: ShareProps) => {
const [open, setOpen] = React.useState(false);
return (
<BrowserOnly>
{() => {
const twitter_authors = authors.map(a => a.twitter_username ? `@${a.twitter_username}` : a.name);
const tweet = encodeURI(
`https://twitter.com/intent/tweet?text=${title} by ${twitter_authors.join(", ")} ${window.location.href}`
);
return (
<Popover
isOpen={open}
positions={["bottom"]}
padding={10}
align={"center"}
onClickOutside={() => setOpen(false)}
content={({ position, childRect, popoverRect }) => (
<ArrowContainer
position={position}
childRect={childRect}
popoverRect={popoverRect}
arrowColor="var(--ifm-color-content-secondary)"
arrowSize={10}
arrowStyle={{ opacity: ".1" }}
className="popover-arrow-container"
arrowClassName="popover-arrow"
>
<div className="share-popover-container">
<div className="share-link-list">
<ShareLink
className="fe-twitter"
label="Twitter" color="#1DA1F2"
link={tweet}
onClick={() => setOpen(false)}
/>
<ShareLink
className="fe-linkedin"
label="LinkedIn"
color="#0072b1"
link={`https://www.linkedin.com/sharing/share-offsite/?url=${window.location.href}`}
onClick={() => setOpen(false)}
/>
<ShareLink
className="fe-link"
label="Copy Link"
color="var(--ifm-color-content-secondary)"
onClick={() => {
var tmp = document.createElement("input"),
href = window.location.href;
document.body.appendChild(tmp);
tmp.value = href;
tmp.select();
document.execCommand("copy");
document.body.removeChild(tmp);
setOpen(false);
}}
/>
</div>
</div>
</ArrowContainer>
)}
>
<span className={"fe fe-share"} onClick={() => setOpen(o => !o)}/>
</Popover>
)
}}
</BrowserOnly>
);
};
export default Share;
\ No newline at end of file
.share-link {
margin: 7px;
display: flex;
align-items: center;
text-decoration: none;
color: var(--ifm-color-content);
}
.share-link:hover {
color: var(--ifm-color-content);
text-decoration: none;
}
.share-popover-container {
padding: 10px;
box-shadow: 0px 4px 6px var(--ifm-color-content-secondary);
background-color: white;
}
.share-link-list {
display: flex;
flex-direction: column;
}
\ No newline at end of file
......@@ -4,7 +4,7 @@ import { MDXProvider } from '@mdx-js/react';
import Seo from '@theme/Seo';
import Link from '@docusaurus/Link';
import MDXComponents from '@theme/MDXComponents';
import useBaseUrl from '@docusaurus/useBaseUrl';
import Share from '@site/src/components/Share';
import styles from './styles.module.css';
const MONTHS = [
'January',
......@@ -44,11 +44,12 @@ function BlogPostItem(props) {
className={clsx('margin-bottom--sm', styles.blogPostTitle)}>
{isBlogPostPage ? title : <Link to={permalink}>{title}</Link>}
</TitleHeading>
<div className="margin-vert--md">
<div className="margin-vert--md" style={{display: "flex", alignItems: "center"}}>
<time dateTime={date} className={styles.blogPostDate}>
{month} {day}, {year}{' '}
{readingTime && <> · {Math.ceil(readingTime)} min read</>}
</time>
{!truncated && <>&nbsp;·&nbsp;<Share title={title} authors={authors} style={{margin: "0 7px"}} /></>}
</div>
......@@ -82,7 +83,7 @@ function BlogPostItem(props) {
<Seo {...{keywords, image}} />
<article className={clsx(!isBlogPostPage && 'margin-bottom--lg', !isBlogPostPage && styles.blogPostPreview)}>
<article className={clsx(!isBlogPostPage && 'margin-bottom--lg', !isBlogPostPage && styles.blogPostPreview)}>
{renderPostHeader()}
<section className="markdown">
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment