diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index bf5cf4e34..c6bc745bb 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -16,6 +16,62 @@ "application/json" ], "paths": { + "/v1/orgs": { + "get": { + "summary": "Lists all Organizations", + "operationId": "OrganizationService_ListOrganizations", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.ListOrganizationsResponse" + } + } + }, + "parameters": [ + { + "name": "view", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.OrganizationService" + ] + } + }, + "/v1/orgs/{id}": { + "get": { + "summary": "Gets a single Organization", + "operationId": "OrganizationService_GetOrganization", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.scopes.v1.Organization" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "view", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.OrganizationService" + ] + } + }, "/v1/orgs/{org_id}/groups": { "get": { "summary": "Lists all Groups", @@ -2080,6 +2136,37 @@ }, "title": "HostSet is a collection of Hosts created and managed by a HostCatalog" }, + "controller.api.resources.scopes.v1.Organization": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the Organization\nOutput only.", + "readOnly": true + }, + "name": { + "type": "string", + "title": "Optional name for identification purposes" + }, + "description": { + "type": "string", + "title": "Optional user-set descripton for identification purposes" + }, + "created_time": { + "type": "string", + "format": "date-time", + "description": "The time this resource was created\nOutput only.", + "readOnly": true + }, + "updated_time": { + "type": "string", + "format": "date-time", + "description": "The time this resource was last updated.\nOutput only.", + "readOnly": true + } + }, + "title": "Organization contains all fields related to an Organization resource" + }, "controller.api.resources.scopes.v1.Project": { "type": "object", "properties": { @@ -2307,6 +2394,14 @@ } } }, + "controller.api.services.v1.GetOrganizationResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.scopes.v1.Organization" + } + } + }, "controller.api.services.v1.GetProjectResponse": { "type": "object", "properties": { @@ -2367,6 +2462,17 @@ } } }, + "controller.api.services.v1.ListOrganizationsResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.resources.scopes.v1.Organization" + } + } + } + }, "controller.api.services.v1.ListProjectsResponse": { "type": "object", "properties": { diff --git a/internal/gen/controller/api/services/organization_service.pb.go b/internal/gen/controller/api/services/organization_service.pb.go new file mode 100644 index 000000000..d05bf04bf --- /dev/null +++ b/internal/gen/controller/api/services/organization_service.pb.go @@ -0,0 +1,530 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.24.0 +// protoc v3.12.3 +// source: controller/api/services/v1/organization_service.proto + +package services + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options" + scopes "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/scopes" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type GetOrganizationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + View string `protobuf:"bytes,2,opt,name=view,proto3" json:"view,omitempty"` +} + +func (x *GetOrganizationRequest) Reset() { + *x = GetOrganizationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrganizationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrganizationRequest) ProtoMessage() {} + +func (x *GetOrganizationRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrganizationRequest.ProtoReflect.Descriptor instead. +func (*GetOrganizationRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_organization_service_proto_rawDescGZIP(), []int{0} +} + +func (x *GetOrganizationRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GetOrganizationRequest) GetView() string { + if x != nil { + return x.View + } + return "" +} + +type GetOrganizationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *scopes.Organization `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *GetOrganizationResponse) Reset() { + *x = GetOrganizationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrganizationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrganizationResponse) ProtoMessage() {} + +func (x *GetOrganizationResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrganizationResponse.ProtoReflect.Descriptor instead. +func (*GetOrganizationResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_organization_service_proto_rawDescGZIP(), []int{1} +} + +func (x *GetOrganizationResponse) GetItem() *scopes.Organization { + if x != nil { + return x.Item + } + return nil +} + +type ListOrganizationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + View string `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` +} + +func (x *ListOrganizationsRequest) Reset() { + *x = ListOrganizationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListOrganizationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOrganizationsRequest) ProtoMessage() {} + +func (x *ListOrganizationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOrganizationsRequest.ProtoReflect.Descriptor instead. +func (*ListOrganizationsRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_organization_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ListOrganizationsRequest) GetView() string { + if x != nil { + return x.View + } + return "" +} + +type ListOrganizationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*scopes.Organization `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ListOrganizationsResponse) Reset() { + *x = ListOrganizationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListOrganizationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOrganizationsResponse) ProtoMessage() {} + +func (x *ListOrganizationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_organization_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOrganizationsResponse.ProtoReflect.Descriptor instead. +func (*ListOrganizationsResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_organization_service_proto_rawDescGZIP(), []int{3} +} + +func (x *ListOrganizationsResponse) GetItems() []*scopes.Organization { + if x != nil { + return x.Items + } + return nil +} + +var File_controller_api_services_v1_organization_service_proto protoreflect.FileDescriptor + +var file_controller_api_services_v1_organization_service_proto_rawDesc = []byte{ + 0x0a, 0x35, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x1a, 0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, + 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x35, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, + 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3c, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x76, 0x69, 0x65, 0x77, 0x22, 0x5f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x44, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2e, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x76, 0x69, 0x65, 0x77, 0x22, 0x63, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x46, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x32, 0xff, 0x02, 0x0a, 0x13, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0xb6, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x3a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x72, 0x67, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x1c, 0x12, + 0x1a, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0xae, 0x01, 0x0a, 0x11, + 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x72, 0x67, 0x73, + 0x92, 0x41, 0x19, 0x12, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x4f, 0x5a, 0x4d, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_services_v1_organization_service_proto_rawDescOnce sync.Once + file_controller_api_services_v1_organization_service_proto_rawDescData = file_controller_api_services_v1_organization_service_proto_rawDesc +) + +func file_controller_api_services_v1_organization_service_proto_rawDescGZIP() []byte { + file_controller_api_services_v1_organization_service_proto_rawDescOnce.Do(func() { + file_controller_api_services_v1_organization_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_services_v1_organization_service_proto_rawDescData) + }) + return file_controller_api_services_v1_organization_service_proto_rawDescData +} + +var file_controller_api_services_v1_organization_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_controller_api_services_v1_organization_service_proto_goTypes = []interface{}{ + (*GetOrganizationRequest)(nil), // 0: controller.api.services.v1.GetOrganizationRequest + (*GetOrganizationResponse)(nil), // 1: controller.api.services.v1.GetOrganizationResponse + (*ListOrganizationsRequest)(nil), // 2: controller.api.services.v1.ListOrganizationsRequest + (*ListOrganizationsResponse)(nil), // 3: controller.api.services.v1.ListOrganizationsResponse + (*scopes.Organization)(nil), // 4: controller.api.resources.scopes.v1.Organization +} +var file_controller_api_services_v1_organization_service_proto_depIdxs = []int32{ + 4, // 0: controller.api.services.v1.GetOrganizationResponse.item:type_name -> controller.api.resources.scopes.v1.Organization + 4, // 1: controller.api.services.v1.ListOrganizationsResponse.items:type_name -> controller.api.resources.scopes.v1.Organization + 0, // 2: controller.api.services.v1.OrganizationService.GetOrganization:input_type -> controller.api.services.v1.GetOrganizationRequest + 2, // 3: controller.api.services.v1.OrganizationService.ListOrganizations:input_type -> controller.api.services.v1.ListOrganizationsRequest + 1, // 4: controller.api.services.v1.OrganizationService.GetOrganization:output_type -> controller.api.services.v1.GetOrganizationResponse + 3, // 5: controller.api.services.v1.OrganizationService.ListOrganizations:output_type -> controller.api.services.v1.ListOrganizationsResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_controller_api_services_v1_organization_service_proto_init() } +func file_controller_api_services_v1_organization_service_proto_init() { + if File_controller_api_services_v1_organization_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_services_v1_organization_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrganizationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_organization_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrganizationResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_organization_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListOrganizationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_organization_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListOrganizationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_services_v1_organization_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_controller_api_services_v1_organization_service_proto_goTypes, + DependencyIndexes: file_controller_api_services_v1_organization_service_proto_depIdxs, + MessageInfos: file_controller_api_services_v1_organization_service_proto_msgTypes, + }.Build() + File_controller_api_services_v1_organization_service_proto = out.File + file_controller_api_services_v1_organization_service_proto_rawDesc = nil + file_controller_api_services_v1_organization_service_proto_goTypes = nil + file_controller_api_services_v1_organization_service_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// OrganizationServiceClient is the client API for OrganizationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type OrganizationServiceClient interface { + // GetOrganization returns a stored Organization if present. The provided request + // must include the org id for the organization being retrieved. If + // that id is missing, malformed or reference a non existing resource an error is returned. + GetOrganization(ctx context.Context, in *GetOrganizationRequest, opts ...grpc.CallOption) (*GetOrganizationResponse, error) + // ListOrganizations returns a list of stored organizations which exist in watchtower. + ListOrganizations(ctx context.Context, in *ListOrganizationsRequest, opts ...grpc.CallOption) (*ListOrganizationsResponse, error) +} + +type organizationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewOrganizationServiceClient(cc grpc.ClientConnInterface) OrganizationServiceClient { + return &organizationServiceClient{cc} +} + +func (c *organizationServiceClient) GetOrganization(ctx context.Context, in *GetOrganizationRequest, opts ...grpc.CallOption) (*GetOrganizationResponse, error) { + out := new(GetOrganizationResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.OrganizationService/GetOrganization", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *organizationServiceClient) ListOrganizations(ctx context.Context, in *ListOrganizationsRequest, opts ...grpc.CallOption) (*ListOrganizationsResponse, error) { + out := new(ListOrganizationsResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.OrganizationService/ListOrganizations", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// OrganizationServiceServer is the server API for OrganizationService service. +type OrganizationServiceServer interface { + // GetOrganization returns a stored Organization if present. The provided request + // must include the org id for the organization being retrieved. If + // that id is missing, malformed or reference a non existing resource an error is returned. + GetOrganization(context.Context, *GetOrganizationRequest) (*GetOrganizationResponse, error) + // ListOrganizations returns a list of stored organizations which exist in watchtower. + ListOrganizations(context.Context, *ListOrganizationsRequest) (*ListOrganizationsResponse, error) +} + +// UnimplementedOrganizationServiceServer can be embedded to have forward compatible implementations. +type UnimplementedOrganizationServiceServer struct { +} + +func (*UnimplementedOrganizationServiceServer) GetOrganization(context.Context, *GetOrganizationRequest) (*GetOrganizationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrganization not implemented") +} +func (*UnimplementedOrganizationServiceServer) ListOrganizations(context.Context, *ListOrganizationsRequest) (*ListOrganizationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListOrganizations not implemented") +} + +func RegisterOrganizationServiceServer(s *grpc.Server, srv OrganizationServiceServer) { + s.RegisterService(&_OrganizationService_serviceDesc, srv) +} + +func _OrganizationService_GetOrganization_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrganizationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrganizationServiceServer).GetOrganization(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.OrganizationService/GetOrganization", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrganizationServiceServer).GetOrganization(ctx, req.(*GetOrganizationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrganizationService_ListOrganizations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListOrganizationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrganizationServiceServer).ListOrganizations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.OrganizationService/ListOrganizations", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrganizationServiceServer).ListOrganizations(ctx, req.(*ListOrganizationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _OrganizationService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "controller.api.services.v1.OrganizationService", + HandlerType: (*OrganizationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetOrganization", + Handler: _OrganizationService_GetOrganization_Handler, + }, + { + MethodName: "ListOrganizations", + Handler: _OrganizationService_ListOrganizations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "controller/api/services/v1/organization_service.proto", +} diff --git a/internal/gen/controller/api/services/organization_service.pb.gw.go b/internal/gen/controller/api/services/organization_service.pb.gw.go new file mode 100644 index 000000000..ec5cd43f0 --- /dev/null +++ b/internal/gen/controller/api/services/organization_service.pb.gw.go @@ -0,0 +1,290 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: controller/api/services/v1/organization_service.proto + +/* +Package services is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package services + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +var ( + filter_OrganizationService_GetOrganization_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_OrganizationService_GetOrganization_0(ctx context.Context, marshaler runtime.Marshaler, client OrganizationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrganizationRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_OrganizationService_GetOrganization_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetOrganization(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_OrganizationService_GetOrganization_0(ctx context.Context, marshaler runtime.Marshaler, server OrganizationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetOrganizationRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_OrganizationService_GetOrganization_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetOrganization(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_OrganizationService_ListOrganizations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_OrganizationService_ListOrganizations_0(ctx context.Context, marshaler runtime.Marshaler, client OrganizationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListOrganizationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_OrganizationService_ListOrganizations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListOrganizations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_OrganizationService_ListOrganizations_0(ctx context.Context, marshaler runtime.Marshaler, server OrganizationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListOrganizationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_OrganizationService_ListOrganizations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListOrganizations(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterOrganizationServiceHandlerServer registers the http handlers for service OrganizationService to "mux". +// UnaryRPC :call OrganizationServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterOrganizationServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server OrganizationServiceServer) error { + + mux.Handle("GET", pattern_OrganizationService_GetOrganization_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.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OrganizationService_GetOrganization_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_OrganizationService_GetOrganization_0(ctx, mux, outboundMarshaler, w, req, response_OrganizationService_GetOrganization_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_OrganizationService_ListOrganizations_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.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OrganizationService_ListOrganizations_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_OrganizationService_ListOrganizations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterOrganizationServiceHandlerFromEndpoint is same as RegisterOrganizationServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterOrganizationServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterOrganizationServiceHandler(ctx, mux, conn) +} + +// RegisterOrganizationServiceHandler registers the http handlers for service OrganizationService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterOrganizationServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterOrganizationServiceHandlerClient(ctx, mux, NewOrganizationServiceClient(conn)) +} + +// RegisterOrganizationServiceHandlerClient registers the http handlers for service OrganizationService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "OrganizationServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "OrganizationServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "OrganizationServiceClient" to call the correct interceptors. +func RegisterOrganizationServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client OrganizationServiceClient) error { + + mux.Handle("GET", pattern_OrganizationService_GetOrganization_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) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OrganizationService_GetOrganization_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_OrganizationService_GetOrganization_0(ctx, mux, outboundMarshaler, w, req, response_OrganizationService_GetOrganization_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_OrganizationService_ListOrganizations_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) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OrganizationService_ListOrganizations_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_OrganizationService_ListOrganizations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +type response_OrganizationService_GetOrganization_0 struct { + proto.Message +} + +func (m response_OrganizationService_GetOrganization_0) XXX_ResponseBody() interface{} { + response := m.Message.(*GetOrganizationResponse) + return response.Item +} + +var ( + pattern_OrganizationService_GetOrganization_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "orgs", "id"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_OrganizationService_ListOrganizations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "orgs"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_OrganizationService_GetOrganization_0 = runtime.ForwardResponseMessage + + forward_OrganizationService_ListOrganizations_0 = runtime.ForwardResponseMessage +) diff --git a/internal/iam/repository_scope.go b/internal/iam/repository_scope.go index af4198c0f..647c86cdf 100644 --- a/internal/iam/repository_scope.go +++ b/internal/iam/repository_scope.go @@ -136,10 +136,10 @@ func (r *Repository) ListProjects(ctx context.Context, withOrganizationId string // ListOrganizations and supports the WithLimit option. func (r *Repository) ListOrganizations(ctx context.Context, opt ...Option) ([]*Scope, error) { - var projects []*Scope - err := r.list(ctx, &projects, "type = ?", []interface{}{OrganizationScope.String()}, opt...) + var organizations []*Scope + err := r.list(ctx, &organizations, "type = ?", []interface{}{OrganizationScope.String()}, opt...) if err != nil { return nil, fmt.Errorf("list organizations: %w", err) } - return projects, nil + return organizations, nil } diff --git a/internal/proto/local/controller/api/services/v1/organization_service.proto b/internal/proto/local/controller/api/services/v1/organization_service.proto new file mode 100644 index 000000000..74d277246 --- /dev/null +++ b/internal/proto/local/controller/api/services/v1/organization_service.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +package controller.api.services.v1; + +option go_package = "github.com/hashicorp/watchtower/internal/gen/controller/api/services;services"; + +import "protoc-gen-swagger/options/annotations.proto"; +import "google/api/annotations.proto"; +import "controller/api/resources/scopes/v1/organization.proto"; + +service OrganizationService { + + // GetOrganization returns a stored Organization if present. The provided request + // must include the org id for the organization being retrieved. If + // that id is missing, malformed or reference a non existing resource an error is returned. + rpc GetOrganization(GetOrganizationRequest) returns (GetOrganizationResponse) { + option (google.api.http) = { + get: "/v1/orgs/{id}" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = { + summary: "Gets a single Organization" + }; + } + + // ListOrganizations returns a list of stored organizations which exist in watchtower. + rpc ListOrganizations(ListOrganizationsRequest) returns (ListOrganizationsResponse) { + option (google.api.http) = { + get: "/v1/orgs" + }; + option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = { + summary: "Lists all Organizations" + }; + } +} + +message GetOrganizationRequest { + string id = 1; + string view = 2; +} + +message GetOrganizationResponse { + resources.scopes.v1.Organization item = 1; +} + +message ListOrganizationsRequest { + string view = 1; +} + +message ListOrganizationsResponse { + repeated resources.scopes.v1.Organization items = 1; +} diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 2e91a248c..712fbd2cb 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/watchtower/internal/servers/controller/handlers/host_catalogs" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/host_sets" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/hosts" + "github.com/hashicorp/watchtower/internal/servers/controller/handlers/organizations" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/projects" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/users" ) @@ -79,6 +80,13 @@ func handleGrpcGateway(c *Controller) (http.Handler, error) { if err := services.RegisterHostServiceHandlerServer(ctx, mux, &hosts.Service{}); err != nil { return nil, fmt.Errorf("failed to register host service handler: %w", err) } + os, err := organizations.NewService(c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create organization handler service: %w", err) + } + if err := services.RegisterOrganizationServiceHandlerServer(ctx, mux, os); err != nil { + return nil, fmt.Errorf("failed to register organization service handler: %w", err) + } ps, err := projects.NewService(c.IamRepoFn) if err != nil { return nil, fmt.Errorf("failed to create project handler service: %w", err) diff --git a/internal/servers/controller/handlers/organizations/organization_service.go b/internal/servers/controller/handlers/organizations/organization_service.go new file mode 100644 index 000000000..246ff4ddb --- /dev/null +++ b/internal/servers/controller/handlers/organizations/organization_service.go @@ -0,0 +1,127 @@ +package organizations + +import ( + "context" + "fmt" + "regexp" + "strings" + + pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/scopes" + pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services" + "github.com/hashicorp/watchtower/internal/iam" + "github.com/hashicorp/watchtower/internal/servers/controller/common" + "github.com/hashicorp/watchtower/internal/servers/controller/handlers" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +const orgIdFieldName = "org_id" + +var ( + reInvalidID = regexp.MustCompile("[^A-Za-z0-9]") +) + +// Service handles request as described by the pbs.OrganizationServiceServer interface. +type Service struct { + repo common.IamRepoFactory +} + +// NewService returns an organization service which handles organization related requests to watchtower. +func NewService(repo common.IamRepoFactory) (Service, error) { + if repo == nil { + return Service{}, fmt.Errorf("nil iam repository provided") + } + return Service{repo: repo}, nil +} + +var _ pbs.OrganizationServiceServer = Service{} + +// ListOrganizations is not yet implemented but will implement the interface pbs.OrganizationServiceServer. +func (s Service) ListOrganizations(ctx context.Context, req *pbs.ListOrganizationsRequest) (*pbs.ListOrganizationsResponse, error) { + ol, err := s.listFromRepo(ctx) + if err != nil { + return nil, err + } + return &pbs.ListOrganizationsResponse{Items: ol}, nil +} + +// GetOrganizations implements the interface pbs.OrganizationServiceServer. +func (s Service) GetOrganization(ctx context.Context, req *pbs.GetOrganizationRequest) (*pbs.GetOrganizationResponse, error) { + if err := validateGetRequest(req); err != nil { + return nil, err + } + o, err := s.getFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + return &pbs.GetOrganizationResponse{Item: o}, nil +} + +func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Organization, error) { + repo, err := s.repo() + if err != nil { + return nil, err + } + p, err := repo.LookupScope(ctx, id) + if err != nil { + return nil, err + } + if p == nil { + return nil, handlers.NotFoundErrorf("Organization %q doesn't exist.", id) + } + return toProto(p), nil +} + +func (s Service) listFromRepo(ctx context.Context) ([]*pb.Organization, error) { + repo, err := s.repo() + if err != nil { + return nil, err + } + ol, err := repo.ListOrganizations(ctx) + if err != nil { + return nil, err + } + var outOl []*pb.Organization + for _, o := range ol { + outOl = append(outOl, toProto(o)) + } + return outOl, nil +} + +func toProto(in *iam.Scope) *pb.Organization { + out := pb.Organization{ + Id: in.GetPublicId(), + CreatedTime: in.GetCreateTime().GetTimestamp(), + UpdatedTime: in.GetUpdateTime().GetTimestamp(), + } + if in.GetDescription() != "" { + out.Description = &wrapperspb.StringValue{Value: in.GetDescription()} + } + if in.GetName() != "" { + out.Name = &wrapperspb.StringValue{Value: in.GetName()} + } + return &out +} + +// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce +// requirements on the structure of the request. They verify that: +// * The path passed in is correctly formatted +// * All required parameters are set +// * There are no conflicting parameters provided +func validateGetRequest(req *pbs.GetOrganizationRequest) error { + badFields := make(map[string]string) + if !validId(req.GetId(), iam.OrganizationScope.Prefix()+"_") { + badFields["id"] = "Invalid formatted organization id." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) + } + return nil +} + +func validId(id, prefix string) bool { + if !strings.HasPrefix(id, prefix) { + return false + } + id = strings.TrimPrefix(id, prefix) + return !reInvalidID.Match([]byte(id)) +} diff --git a/internal/servers/controller/handlers/organizations/organization_service_test.go b/internal/servers/controller/handlers/organizations/organization_service_test.go new file mode 100644 index 000000000..9c9b7e43d --- /dev/null +++ b/internal/servers/controller/handlers/organizations/organization_service_test.go @@ -0,0 +1,144 @@ +package organizations_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/watchtower/internal/db" + pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/scopes" + pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services" + "github.com/hashicorp/watchtower/internal/iam" + "github.com/hashicorp/watchtower/internal/servers/controller/handlers/organizations" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func createDefaultOrganizationAndRepo(t *testing.T) (*iam.Scope, func() (*iam.Repository, error)) { + t.Helper() + cleanup, conn, _ := db.TestSetup(t, "postgres") + t.Cleanup(func() { + if err := conn.Close(); err != nil { + t.Errorf("Error when closing gorm DB: %v", err) + } + if err := cleanup(); err != nil { + t.Errorf("Error when cleaning up TestSetup: %v", err) + } + }) + rw := db.New(conn) + wrap := db.TestWrapper(t) + repoFn := func() (*iam.Repository, error) { + return iam.NewRepository(rw, rw, wrap) + } + + oRes, _ := iam.TestScopes(t, conn) + oRes.Name = "default" + oRes.Description = "default" + repo, err := repoFn() + require.NoError(t, err) + oRes, _, err = repo.UpdateScope(context.Background(), oRes, []string{"Name", "Description"}) + require.NoError(t, err) + return oRes, repoFn +} + +func TestGet(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + org, repo := createDefaultOrganizationAndRepo(t) + toMerge := &pbs.GetOrganizationRequest{ + Id: org.GetPublicId(), + } + + pOrganization := &pb.Organization{ + Id: org.GetPublicId(), + Name: &wrapperspb.StringValue{Value: org.GetName()}, + Description: &wrapperspb.StringValue{Value: org.GetDescription()}, + CreatedTime: org.CreateTime.GetTimestamp(), + UpdatedTime: org.UpdateTime.GetTimestamp(), + } + + cases := []struct { + name string + req *pbs.GetOrganizationRequest + res *pbs.GetOrganizationResponse + errCode codes.Code + }{ + { + name: "Get an Existing organization", + req: &pbs.GetOrganizationRequest{Id: org.GetPublicId()}, + res: &pbs.GetOrganizationResponse{Item: pOrganization}, + errCode: codes.OK, + }, + { + name: "Get a non existing organization", + req: &pbs.GetOrganizationRequest{Id: iam.OrganizationScope.Prefix() + "_DoesntExis"}, + res: nil, + errCode: codes.NotFound, + }, + { + name: "Wrong id prefix", + req: &pbs.GetOrganizationRequest{Id: "j_1234567890"}, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "space in id", + req: &pbs.GetOrganizationRequest{Id: iam.OrganizationScope.Prefix() + "_1 23456789"}, + res: nil, + errCode: codes.InvalidArgument, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := proto.Clone(toMerge).(*pbs.GetOrganizationRequest) + proto.Merge(req, tc.req) + + s, err := organizations.NewService(repo) + require.NoError(err, "Couldn't create new project service.") + + got, gErr := s.GetOrganization(context.Background(), req) + assert.Equal(tc.errCode, status.Code(gErr), "GetOrganization(%+v) got error %v, wanted %v", req, gErr, tc.errCode) + assert.True(proto.Equal(got, tc.res), "GetOrganization(%q) got response %q, wanted %q", req, got, tc.res) + }) + } +} + +func TestList(t *testing.T) { + assert, require := assert.New(t), require.New(t) + cleanup, conn, _ := db.TestSetup(t, "postgres") + t.Cleanup(func() { + if err := conn.Close(); err != nil { + t.Errorf("Error when closing gorm DB: %v", err) + } + if err := cleanup(); err != nil { + t.Errorf("Error when cleaning up TestSetup: %v", err) + } + }) + rw := db.New(conn) + wrap := db.TestWrapper(t) + repoFn := func() (*iam.Repository, error) { + return iam.NewRepository(rw, rw, wrap) + } + + s, err := organizations.NewService(repoFn) + require.NoError(err) + ctx := context.Background() + resp, err := s.ListOrganizations(ctx, &pbs.ListOrganizationsRequest{}) + assert.NoError(err) + assert.Equal(&pbs.ListOrganizationsResponse{}, resp) + + var orgs []*pb.Organization + for i := 0; i < 10; i++ { + o, _ := iam.TestScopes(t, conn) + orgs = append(orgs, &pb.Organization{Id: o.PublicId, CreatedTime: o.GetCreateTime().GetTimestamp(), UpdatedTime: o.GetUpdateTime().GetTimestamp()}) + } + resp, err = s.ListOrganizations(ctx, &pbs.ListOrganizationsRequest{}) + assert.NoError(err) + assert.Empty(cmp.Diff(resp, &pbs.ListOrganizationsResponse{Items: orgs}, protocmp.Transform(), protocmp.SortRepeatedFields(&pbs.ListOrganizationsResponse{}, "items"))) +}