diff --git a/pkg/toolsets/core/create_project.go b/pkg/toolsets/core/create_project.go index 9ae57fa..f75fa60 100644 --- a/pkg/toolsets/core/create_project.go +++ b/pkg/toolsets/core/create_project.go @@ -3,6 +3,7 @@ package core import ( "context" "fmt" + "strconv" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/rancher/rancher-ai-mcp/internal/middleware" @@ -13,15 +14,38 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +type resourceQuota struct { + Pods int `json:"pods,omitempty" jsonschema:"the maximum number of pods that can be created in the project"` + Services int `json:"services,omitempty" jsonschema:"the maximum number of services that can be created in the project"` + ReplicationControllers int `json:"replicationControllers,omitempty" jsonschema:"the maximum number of replication controllers that can be created in the project"` + Secrets int `json:"secrets,omitempty" jsonschema:"the maximum number of secrets that can be created in the project"` + ConfigMaps int `json:"configMaps,omitempty" jsonschema:"the maximum number of config maps that can be created in the project"` + PersistentVolumeClaims int `json:"persistentVolumeClaims,omitempty" jsonschema:"the maximum number of persistent volume claims that can be created in the project"` + ServicesNodePorts int `json:"servicesNodePorts,omitempty" jsonschema:"the maximum number of services with node ports that can be created in the project"` + ServicesLoadBalancers int `json:"servicesLoadBalancers,omitempty" jsonschema:"the maximum number of services with load balancers that can be created in the project"` + RequestsCPU int `json:"requestsCpu,omitempty" jsonschema:"the amount of CPU resources (mCPUs) reserved for containers in the project"` + RequestsMemory int `json:"requestsMemory,omitempty" jsonschema:"the amount of memory resources (MiB) reserved for containers in the project"` + RequestsStorage int `json:"requestsStorage,omitempty" jsonschema:"the amount of storage resources (MiB) reserved for containers in the project"` + LimitsCPU int `json:"limitsCpu,omitempty" jsonschema:"the maximum amount of CPU resources (mCPUs) that can be used by containers in the project"` + LimitsMemory int `json:"limitsMemory,omitempty" jsonschema:"the maximum amount of memory resources (MiB) that can be used by containers in the project"` + Extended map[string]any `json:"extended,omitempty" jsonschema:"a map of any additional resource quotas to be applied to the project, where the key is the name of the resource quota and the value is the quantity (e.g., '10Gi' for storage)"` +} + +type containerDefaultResourceLimit struct { + CPULimit int `json:"cpuLimit,omitempty" jsonschema:"the maximum amount of CPU resources (mCPUs) that can be used by containers in the project"` + CPUReservation int `json:"cpuReservation,omitempty" jsonschema:"the amount of CPU resources (mCPUs) reserved for containers in the project"` + MemoryLimit int `json:"memoryLimit,omitempty" jsonschema:"the maximum amount of memory resources (MiB) that can be used by containers in the project"` + MemoryReservation int `json:"memoryReservation,omitempty" jsonschema:"the amount of memory resources (MiB) reserved for containers in the project"` +} + type createProjectParams struct { - Cluster string `json:"cluster" jsonschema:"the cluster that the project belongs to"` - Name string `json:"name" jsonschema:"the name of the project to be created"` - Description string `json:"description,omitempty" jsonschema:"an optional description for the project"` - DisplayName string `json:"displayName,omitempty" jsonschema:"an optional display name for the project"` - CPULimit int `json:"cpuLimit,omitempty" jsonschema:"the maximum amount of CPU resources (mCPUs) that can be used by containers in the project"` - CPUReservation int `json:"cpuReservation,omitempty" jsonschema:"the amount of CPU resources (mCPUs) reserved for containers in the project"` - MemoryLimit int `json:"memoryLimit,omitempty" jsonschema:"the maximum amount of memory resources (MiB) that can be used by containers in the project"` - MemoryReservation int `json:"memoryReservation,omitempty" jsonschema:"the amount of memory resources (MiB) reserved for containers in the project"` + Cluster string `json:"cluster" jsonschema:"the cluster that the project belongs to"` + Name string `json:"name" jsonschema:"the name of the project to be created"` + Description string `json:"description,omitempty" jsonschema:"an optional description for the project"` + DisplayName string `json:"displayName,omitempty" jsonschema:"an optional display name for the project"` + ResourceQuota resourceQuota `json:"resourceQuota,omitempty" jsonschema:"optional resource quotas to be applied to the project"` + NamespaceDefaultResourceQuota resourceQuota `json:"namespaceDefaultResourceQuota,omitempty" jsonschema:"optional default resource quotas to be applied to namespaces created within the project"` + ContainerDefaultResourceLimit containerDefaultResourceLimit `json:"containerDefaultResourceLimit,omitempty" jsonschema:"optional default resource limits to be applied to containers created within the project"` } func (t *Tools) createProject(ctx context.Context, toolReq *mcp.CallToolRequest, params createProjectParams) (*mcp.CallToolResult, any, error) { @@ -85,22 +109,87 @@ func (t *Tools) createProjectObj(params createProjectParams) (*unstructured.Unst // Create any container resource quotas if specified with their respective units containerResourceQuotas := make(map[string]any) - if params.CPULimit != 0 { - containerResourceQuotas["limitsCpu"] = fmt.Sprintf("%dm", params.CPULimit) + if params.ContainerDefaultResourceLimit.CPULimit != 0 { + containerResourceQuotas["limitsCpu"] = fmt.Sprintf("%dm", params.ContainerDefaultResourceLimit.CPULimit) } - if params.CPUReservation != 0 { - containerResourceQuotas["requestsCpu"] = fmt.Sprintf("%dm", params.CPUReservation) + if params.ContainerDefaultResourceLimit.CPUReservation != 0 { + containerResourceQuotas["requestsCpu"] = fmt.Sprintf("%dm", params.ContainerDefaultResourceLimit.CPUReservation) } - if params.MemoryLimit != 0 { - containerResourceQuotas["limitsMemory"] = fmt.Sprintf("%dMi", params.MemoryLimit) + if params.ContainerDefaultResourceLimit.MemoryLimit != 0 { + containerResourceQuotas["limitsMemory"] = fmt.Sprintf("%dMi", params.ContainerDefaultResourceLimit.MemoryLimit) } - if params.MemoryReservation != 0 { - containerResourceQuotas["requestsMemory"] = fmt.Sprintf("%dMi", params.MemoryReservation) + if params.ContainerDefaultResourceLimit.MemoryReservation != 0 { + containerResourceQuotas["requestsMemory"] = fmt.Sprintf("%dMi", params.ContainerDefaultResourceLimit.MemoryReservation) } if err := unstructured.SetNestedField(project.Object, containerResourceQuotas, "spec", "containerDefaultResourceLimit"); err != nil { return nil, fmt.Errorf("failed to set project container resource quotas: %w", err) } + // Create the resource quota map if any quotas were specified + quotaMap := buildResourceQuotaMap(params.ResourceQuota) + if quotaMap["limit"] != nil { + if err := unstructured.SetNestedField(project.Object, quotaMap, "spec", "resourceQuota"); err != nil { + return nil, fmt.Errorf("failed to set project resource quotas: %w", err) + } + } + + // Create the namespaces default resource quota map if any quotas were specified + namespaceResourceQuota := buildResourceQuotaMap(params.NamespaceDefaultResourceQuota) + if namespaceResourceQuota["limit"] != nil { + if err := unstructured.SetNestedField(project.Object, namespaceResourceQuota, "spec", "namespaceDefaultResourceQuota"); err != nil { + return nil, fmt.Errorf("failed to set project namespace default resource quotas: %w", err) + } + } + return project, nil } + +func buildResourceQuotaMap(quota resourceQuota) map[string]any { + limitMap := make(map[string]any) + if quota.Pods != 0 { + limitMap["pods"] = strconv.Itoa(quota.Pods) + } + if quota.Services != 0 { + limitMap["services"] = strconv.Itoa(quota.Services) + } + if quota.ReplicationControllers != 0 { + limitMap["replicationControllers"] = strconv.Itoa(quota.ReplicationControllers) + } + if quota.Secrets != 0 { + limitMap["secrets"] = strconv.Itoa(quota.Secrets) + } + if quota.ConfigMaps != 0 { + limitMap["configMaps"] = strconv.Itoa(quota.ConfigMaps) + } + if quota.PersistentVolumeClaims != 0 { + limitMap["persistentVolumeClaims"] = strconv.Itoa(quota.PersistentVolumeClaims) + } + if quota.ServicesNodePorts != 0 { + limitMap["servicesNodePorts"] = strconv.Itoa(quota.ServicesNodePorts) + } + if quota.ServicesLoadBalancers != 0 { + limitMap["servicesLoadBalancers"] = strconv.Itoa(quota.ServicesLoadBalancers) + } + if quota.RequestsCPU != 0 { + limitMap["requestsCpu"] = fmt.Sprintf("%dm", quota.RequestsCPU) + } + if quota.RequestsMemory != 0 { + limitMap["requestsMemory"] = fmt.Sprintf("%dMi", quota.RequestsMemory) + } + if quota.RequestsStorage != 0 { + limitMap["requestsStorage"] = fmt.Sprintf("%dMi", quota.RequestsStorage) + } + if quota.LimitsCPU != 0 { + limitMap["limitsCpu"] = fmt.Sprintf("%dm", quota.LimitsCPU) + } + if quota.LimitsMemory != 0 { + limitMap["limitsMemory"] = fmt.Sprintf("%dMi", quota.LimitsMemory) + } + if quota.Extended != nil { + limitMap["extended"] = quota.Extended + } + quotaMap := make(map[string]any) + quotaMap["limit"] = limitMap + return quotaMap +} diff --git a/pkg/toolsets/core/create_project_plan_test.go b/pkg/toolsets/core/create_project_plan_test.go index 9f1a985..f8606a3 100644 --- a/pkg/toolsets/core/create_project_plan_test.go +++ b/pkg/toolsets/core/create_project_plan_test.go @@ -62,13 +62,15 @@ func TestCreateProjectPlan(t *testing.T) { }, "create project plan with resource limits": { params: createProjectParams{ - Cluster: "local", - Name: "project-with-limits", - DisplayName: "Project with Limits", - CPULimit: 2000, - CPUReservation: 1000, - MemoryLimit: 4096, - MemoryReservation: 2048, + Cluster: "local", + Name: "project-with-limits", + DisplayName: "Project with Limits", + ContainerDefaultResourceLimit: containerDefaultResourceLimit{ + CPULimit: 2000, + CPUReservation: 1000, + MemoryLimit: 4096, + MemoryReservation: 2048, + }, }, validateResult: func(t *testing.T, result string) { var planResources []response.PlanResource @@ -118,6 +120,145 @@ func TestCreateProjectPlan(t *testing.T) { assert.True(t, ok) }, }, + "create project plan with namespace default resource quotas": { + params: createProjectParams{ + Cluster: "local", + Name: "project-with-ns-quotas", + DisplayName: "Project with Namespace Quotas", + NamespaceDefaultResourceQuota: resourceQuota{ + Pods: 100, + Services: 50, + Secrets: 100, + ConfigMaps: 50, + PersistentVolumeClaims: 10, + RequestsCPU: 5000, + RequestsMemory: 10240, + }, + }, + validateResult: func(t *testing.T, result string) { + var planResources []response.PlanResource + err := json.Unmarshal([]byte(result), &planResources) + require.NoError(t, err) + require.Len(t, planResources, 1) + + planResource := planResources[0] + payload, ok := planResource.Payload.(map[string]any) + require.True(t, ok) + + spec, ok := payload["spec"].(map[string]any) + require.True(t, ok) + + nsQuota, ok := spec["namespaceDefaultResourceQuota"].(map[string]any) + require.True(t, ok) + limit, ok := nsQuota["limit"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "100", limit["pods"]) + assert.Equal(t, "50", limit["services"]) + assert.Equal(t, "100", limit["secrets"]) + assert.Equal(t, "50", limit["configMaps"]) + assert.Equal(t, "10", limit["persistentVolumeClaims"]) + assert.Equal(t, "5000m", limit["requestsCpu"]) + assert.Equal(t, "10240Mi", limit["requestsMemory"]) + }, + }, + "create project plan with resource quotas": { + params: createProjectParams{ + Cluster: "local", + Name: "project-with-quotas", + DisplayName: "Project with Resource Quotas", + ResourceQuota: resourceQuota{ + ReplicationControllers: 20, + ServicesNodePorts: 5, + ServicesLoadBalancers: 3, + LimitsCPU: 10000, + LimitsMemory: 20480, + RequestsStorage: 51200, + }, + }, + validateResult: func(t *testing.T, result string) { + var planResources []response.PlanResource + err := json.Unmarshal([]byte(result), &planResources) + require.NoError(t, err) + require.Len(t, planResources, 1) + + planResource := planResources[0] + payload, ok := planResource.Payload.(map[string]any) + require.True(t, ok) + + spec, ok := payload["spec"].(map[string]any) + require.True(t, ok) + + resQuota, ok := spec["resourceQuota"].(map[string]any) + require.True(t, ok) + limit, ok := resQuota["limit"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "20", limit["replicationControllers"]) + assert.Equal(t, "5", limit["servicesNodePorts"]) + assert.Equal(t, "3", limit["servicesLoadBalancers"]) + assert.Equal(t, "10000m", limit["limitsCpu"]) + assert.Equal(t, "20480Mi", limit["limitsMemory"]) + assert.Equal(t, "51200Mi", limit["requestsStorage"]) + }, + }, + "create project plan with all fields": { + params: createProjectParams{ + Cluster: "local", + Name: "comprehensive-project", + DisplayName: "Comprehensive Project", + Description: "A project with all fields set", + ContainerDefaultResourceLimit: containerDefaultResourceLimit{ + CPULimit: 1500, + CPUReservation: 500, + MemoryLimit: 2048, + MemoryReservation: 1024, + }, + NamespaceDefaultResourceQuota: resourceQuota{ + Pods: 50, + Services: 25, + }, + ResourceQuota: resourceQuota{ + ServicesNodePorts: 10, + LimitsCPU: 8000, + }, + }, + validateResult: func(t *testing.T, result string) { + var planResources []response.PlanResource + err := json.Unmarshal([]byte(result), &planResources) + require.NoError(t, err) + require.Len(t, planResources, 1) + + planResource := planResources[0] + payload, ok := planResource.Payload.(map[string]any) + require.True(t, ok) + + spec, ok := payload["spec"].(map[string]any) + require.True(t, ok) + + // Validate container default resource limits + containerLimits, ok := spec["containerDefaultResourceLimit"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "1500m", containerLimits["limitsCpu"]) + assert.Equal(t, "500m", containerLimits["requestsCpu"]) + assert.Equal(t, "2048Mi", containerLimits["limitsMemory"]) + assert.Equal(t, "1024Mi", containerLimits["requestsMemory"]) + + // Validate namespace default resource quotas + nsQuota, ok := spec["namespaceDefaultResourceQuota"].(map[string]any) + require.True(t, ok) + nsLimit, ok := nsQuota["limit"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "50", nsLimit["pods"]) + assert.Equal(t, "25", nsLimit["services"]) + + // Validate resource quotas + resQuota, ok := spec["resourceQuota"].(map[string]any) + require.True(t, ok) + resLimit, ok := resQuota["limit"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "10", resLimit["servicesNodePorts"]) + assert.Equal(t, "8000m", resLimit["limitsCpu"]) + }, + }, } for name, tt := range tests { diff --git a/pkg/toolsets/core/create_project_test.go b/pkg/toolsets/core/create_project_test.go index 15aebc6..b038680 100644 --- a/pkg/toolsets/core/create_project_test.go +++ b/pkg/toolsets/core/create_project_test.go @@ -57,7 +57,9 @@ func TestCreateProject(t *testing.T) { "clusterName": "local", "displayName": "Test Project", "description": "A test project", - "containerDefaultResourceLimit": {} + "containerDefaultResourceLimit": {}, + "resourceQuota": {"limit": {}}, + "namespaceDefaultResourceQuota": {"limit": {}} } } ], @@ -68,13 +70,15 @@ func TestCreateProject(t *testing.T) { }, "create project with resource limits": { params: createProjectParams{ - Cluster: "local", - Name: "project-with-limits", - DisplayName: "Project with Limits", - CPULimit: 2000, - CPUReservation: 1000, - MemoryLimit: 4096, - MemoryReservation: 2048, + Cluster: "local", + Name: "project-with-limits", + DisplayName: "Project with Limits", + ContainerDefaultResourceLimit: containerDefaultResourceLimit{ + CPULimit: 2000, + CPUReservation: 1000, + MemoryLimit: 4096, + MemoryReservation: 2048, + }, }, requestURL: fakeUrl, fakeDynClient: dynamicfake.NewSimpleDynamicClientWithCustomListKinds(createProjectScheme(), map[schema.GroupVersionResource]string{ @@ -94,7 +98,9 @@ func TestCreateProject(t *testing.T) { "requestsCpu": "1000m", "limitsMemory": "4096Mi", "requestsMemory": "2048Mi" - } + }, + "resourceQuota": {"limit": {}}, + "namespaceDefaultResourceQuota": {"limit": {}} } } ], @@ -120,7 +126,9 @@ func TestCreateProject(t *testing.T) { "metadata": {"name": "configured-url-project", "namespace": "local"}, "spec": { "clusterName": "local", - "containerDefaultResourceLimit": {} + "containerDefaultResourceLimit": {}, + "resourceQuota": {"limit": {}}, + "namespaceDefaultResourceQuota": {"limit": {}} } } ], @@ -129,6 +137,159 @@ func TestCreateProject(t *testing.T) { ] }`, }, + "create project with namespace default resource quotas": { + params: createProjectParams{ + Cluster: "local", + Name: "project-with-ns-quotas", + DisplayName: "Project with Namespace Quotas", + NamespaceDefaultResourceQuota: resourceQuota{ + Pods: 100, + Services: 50, + Secrets: 100, + ConfigMaps: 50, + PersistentVolumeClaims: 10, + RequestsCPU: 5000, + RequestsMemory: 10240, + }, + }, + requestURL: fakeUrl, + fakeDynClient: dynamicfake.NewSimpleDynamicClientWithCustomListKinds(createProjectScheme(), map[schema.GroupVersionResource]string{ + {Group: "management.cattle.io", Version: "v3", Resource: "projects"}: "ProjectList", + }), + expectedResult: `{ + "llm": [ + { + "apiVersion": "management.cattle.io/v3", + "kind": "Project", + "metadata": {"name": "project-with-ns-quotas", "namespace": "local"}, + "spec": { + "clusterName": "local", + "displayName": "Project with Namespace Quotas", + "containerDefaultResourceLimit": {}, "resourceQuota": {"limit": {}}, "namespaceDefaultResourceQuota": { + "limit": { + "pods": "100", + "services": "50", + "secrets": "100", + "configMaps": "50", + "persistentVolumeClaims": "10", + "requestsCpu": "5000m", + "requestsMemory": "10240Mi" + } + } + } + } + ], + "uiContext": [ + {"namespace": "local", "kind": "Project", "cluster": "local", "name": "project-with-ns-quotas", "type": "project"} + ] + }`, + }, + "create project with resource quotas": { + params: createProjectParams{ + Cluster: "local", + Name: "project-with-quotas", + DisplayName: "Project with Resource Quotas", + ResourceQuota: resourceQuota{ + ReplicationControllers: 20, + ServicesNodePorts: 5, + ServicesLoadBalancers: 3, + LimitsCPU: 10000, + LimitsMemory: 20480, + RequestsStorage: 51200, + }, + }, + requestURL: fakeUrl, + fakeDynClient: dynamicfake.NewSimpleDynamicClientWithCustomListKinds(createProjectScheme(), map[schema.GroupVersionResource]string{ + {Group: "management.cattle.io", Version: "v3", Resource: "projects"}: "ProjectList", + }), + expectedResult: `{ + "llm": [ + { + "apiVersion": "management.cattle.io/v3", + "kind": "Project", + "metadata": {"name": "project-with-quotas", "namespace": "local"}, + "spec": { + "clusterName": "local", + "displayName": "Project with Resource Quotas", + "containerDefaultResourceLimit": {}, "namespaceDefaultResourceQuota": {"limit": {}}, "resourceQuota": { + "limit": { + "replicationControllers": "20", + "servicesNodePorts": "5", + "servicesLoadBalancers": "3", + "requestsStorage": "51200Mi", + "limitsCpu": "10000m", + "limitsMemory": "20480Mi" + } + } + } + } + ], + "uiContext": [ + {"namespace": "local", "kind": "Project", "cluster": "local", "name": "project-with-quotas", "type": "project"} + ] + }`, + }, + "create project with all fields": { + params: createProjectParams{ + Cluster: "local", + Name: "comprehensive-project", + DisplayName: "Comprehensive Project", + Description: "A project with all fields set", + ContainerDefaultResourceLimit: containerDefaultResourceLimit{ + CPULimit: 1500, + CPUReservation: 500, + MemoryLimit: 2048, + MemoryReservation: 1024, + }, + NamespaceDefaultResourceQuota: resourceQuota{ + Pods: 50, + Services: 25, + }, + ResourceQuota: resourceQuota{ + ServicesNodePorts: 10, + LimitsCPU: 8000, + }, + }, + requestURL: fakeUrl, + fakeDynClient: dynamicfake.NewSimpleDynamicClientWithCustomListKinds(createProjectScheme(), map[schema.GroupVersionResource]string{ + {Group: "management.cattle.io", Version: "v3", Resource: "projects"}: "ProjectList", + }), + expectedResult: `{ + "llm": [ + { + "apiVersion": "management.cattle.io/v3", + "kind": "Project", + "metadata": {"name": "comprehensive-project", "namespace": "local"}, + "spec": { + "clusterName": "local", + "displayName": "Comprehensive Project", + "description": "A project with all fields set", + "containerDefaultResourceLimit": { + "limitsCpu": "1500m", + "requestsCpu": "500m", + "limitsMemory": "2048Mi", + "requestsMemory": "1024Mi" + }, + "namespaceDefaultResourceQuota": { + "limit": { + "pods": "50", + "services": "25" + } + }, + "resourceQuota": { + "limit": { + "servicesNodePorts": "10", + "limitsCpu": "8000m" + } + } + } + } + ], + "uiContext": [ + {"namespace": "local", "kind": "Project", "cluster": "local", "name": "comprehensive-project", "type": "project"} + ] + }`, + }, "create project - no rancherURL or request URL": { // fails because requestURL and rancherURL are not configured (no // R_Url or configured Rancher URL. diff --git a/pkg/toolsets/core/tools.go b/pkg/toolsets/core/tools.go index 8d6385f..010faab 100644 --- a/pkg/toolsets/core/tools.go +++ b/pkg/toolsets/core/tools.go @@ -275,9 +275,28 @@ func (t *Tools) AddTools(mcpServer *mcp.Server) { name (string): The name of the project to be created. description (string, optional): An optional description for the project. displayName (string, optional): An optional display name for the project. - cpuLimit (string, optional): The maximum amount of CPU resources (in mCPUs) that can be used by containers in the project. - cpuReservation (string, optional): The amount of CPU resources (in mCPUs) reserved for containers in the project. - memoryLimit (string, optional): The maximum amount of memory resources (in MiB) that can be used by containers in the project. - memoryReservation (string, optional): The amount of memory resources (in MiB) reserved for containers in the project.`}, + containerResourceQuota (object, optional): An optional object specifying resource quotas for containers in the project. The JSON object can include the following fields: + cpuLimit (string, optional): The maximum amount of CPU resources (in mCPUs) that can be used by containers in the project. + cpuReservation (string, optional): The amount of CPU resources (in mCPUs) reserved for containers in the project. + memoryLimit (string, optional): The maximum amount of memory resources (in MiB) that can be used by containers in the project. + memoryReservation (string, optional): The amount of memory resources (in MiB) reserved for containers in the project. + resourceQuota (object, optional): An optional object specifying resource quotas for the project. + namespaceDefaultResourceQuota (object, optional): An optional object specifying default resource quotas for namespaces in the project. + Parameters for resourceQuota and namespaceDefaultResourceQuota JSON objects: + pods (int, optional): The maximum number of pods that can be created in the project or in each namespace within the project. + services (int, optional): The maximum number of services that can be created in the project or in each namespace within the project. + replicationControllers (int, optional): The maximum number of replication controllers that can be created in the project or in each namespace within the project. + secrets (int, optional): The maximum number of secrets that can be created in the project or in each namespace within the project. + configMaps (int, optional): The maximum number of config maps that can be created in the project or in each namespace within the project. + persistentVolumeClaims (int, optional): The maximum number of persistent volume claims that can be created in the project or in each namespace within the project. + servicesNodePorts (int, optional): The maximum number of service node ports that can be created in the project or in each namespace within the project. + servicesLoadBalancers (int, optional): The maximum number of service load balancers that can be created in the project or in each namespace within the project. + requestsCpu (int, optional): The amount of CPU resources (in mCPUs) that is requested by default for each namespace within the project. + requestsMemory (int, optional): The amount of memory resources (in MiB) that is requested by default for each namespace within the project. + requestsStorage (int, optional): The amount of storage resources (in MiB) that is requested by default for each namespace within the project. + limitsCPU (int, optional): The maximum amount of CPU resources (in mCPUs) that can be used by the project or by each namespace within the project. + limitsMemory (int, optional): The maximum amount of memory resources (in MiB) that can be used by the project or by each namespace within the project. + extended (object, optional): An optional field that can be used to specify any additional resource quotas in the format required by the Rancher API. This field is useful for specifying quotas that are not covered by the other fields, such as quotas for custom resources or for specific Kubernetes features. The value of this field should be a JSON object that represents the additional quotas to be applied to the project or namespaces within the project. The key should be the name of the quota field and the value should be the quota limit in the appropriate unit.`, + }, t.createProjectPlan) }