1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

feat(config): separate configmaps and secrets [EE-5078] (#9029)

This commit is contained in:
Ali 2023-06-12 09:46:48 +12:00 committed by GitHub
parent 4a331b71e1
commit d7fc2046d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
102 changed files with 2845 additions and 665 deletions

View file

@ -0,0 +1,192 @@
import { Shuffle, Trash2 } from 'lucide-react';
import { useRouter } from '@uirouter/react';
import clsx from 'clsx';
import { Row } from '@tanstack/react-table';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import {
Authorized,
useAuthorizations,
useCurrentUser,
} from '@/react/hooks/useUser';
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
import { pluralize } from '@/portainer/helpers/strings';
import { Datatable, Table, TableSettingsMenu } from '@@/datatables';
import { confirmDelete } from '@@/modals/confirm';
import { Button } from '@@/buttons';
import { Link } from '@@/Link';
import { useTableState } from '@@/datatables/useTableState';
import {
useMutationDeleteServices,
useServicesForCluster,
} from '../../service';
import { Service } from '../../types';
import { DefaultDatatableSettings } from '../../../datatables/DefaultDatatableSettings';
import { isSystemNamespace } from '../../../namespaces/utils';
import { useNamespaces } from '../../../namespaces/queries';
import { SystemResourceDescription } from '../../../datatables/SystemResourceDescription';
import { columns } from './columns';
import { createStore } from './datatable-store';
const storageKey = 'k8sServicesDatatable';
const settingsStore = createStore(storageKey);
export function ServicesDatatable() {
const tableState = useTableState(settingsStore, storageKey);
const environmentId = useEnvironmentId();
const { data: namespaces, ...namespacesQuery } = useNamespaces(environmentId);
const namespaceNames = (namespaces && Object.keys(namespaces)) || [];
const { data: services, ...servicesQuery } = useServicesForCluster(
environmentId,
namespaceNames,
{
autoRefreshRate: tableState.autoRefreshRate * 1000,
}
);
const readOnly = !useAuthorizations(['K8sServiceW']);
const { isAdmin } = useCurrentUser();
const filteredServices = services?.filter(
(service) =>
(isAdmin && tableState.showSystemResources) ||
!isSystemNamespace(service.Namespace)
);
return (
<Datatable
dataset={filteredServices || []}
columns={columns}
settingsManager={tableState}
isLoading={servicesQuery.isLoading || namespacesQuery.isLoading}
emptyContentLabel="No services found"
title="Services"
titleIcon={Shuffle}
getRowId={(row) => row.UID}
isRowSelectable={(row) => !isSystemNamespace(row.original.Namespace)}
disableSelect={readOnly}
renderTableActions={(selectedRows) => (
<TableActions selectedItems={selectedRows} />
)}
renderTableSettings={() => (
<TableSettingsMenu>
<DefaultDatatableSettings
settings={tableState}
hideShowSystemResources={!isAdmin}
/>
</TableSettingsMenu>
)}
description={
<SystemResourceDescription
showSystemResources={tableState.showSystemResources || !isAdmin}
/>
}
renderRow={servicesRenderRow}
/>
);
}
// needed to apply custom styling to the row cells and not globally.
// required in the AC's for this ticket.
function servicesRenderRow(row: Row<Service>, highlightedItemId?: string) {
return (
<Table.Row<Service>
cells={row.getVisibleCells()}
className={clsx('[&>td]:!py-4 [&>td]:!align-top', {
active: highlightedItemId === row.id,
})}
/>
);
}
interface SelectedService {
Namespace: string;
Name: string;
}
type TableActionsProps = {
selectedItems: Service[];
};
function TableActions({ selectedItems }: TableActionsProps) {
const environmentId = useEnvironmentId();
const deleteServicesMutation = useMutationDeleteServices(environmentId);
const router = useRouter();
async function handleRemoveClick(services: SelectedService[]) {
const confirmed = await confirmDelete(
<>
<p>{`Are you sure you want to remove the selected ${pluralize(
services.length,
'service'
)}?`}</p>
<ul className="pl-6">
{services.map((s, index) => (
<li key={index}>
{s.Namespace}/{s.Name}
</li>
))}
</ul>
</>
);
if (!confirmed) {
return null;
}
const payload: Record<string, string[]> = {};
services.forEach((service) => {
payload[service.Namespace] = payload[service.Namespace] || [];
payload[service.Namespace].push(service.Name);
});
deleteServicesMutation.mutate(
{ environmentId, data: payload },
{
onSuccess: () => {
notifySuccess(
'Services successfully removed',
services.map((s) => `${s.Namespace}/${s.Name}`).join(', ')
);
router.stateService.reload();
},
onError: (error) => {
notifyError(
'Unable to delete service(s)',
error as Error,
services.map((s) => `${s.Namespace}/${s.Name}`).join(', ')
);
},
}
);
return services;
}
return (
<div className="servicesDatatable-actions">
<Authorized authorizations="K8sServicesW">
<Button
className="btn-wrapper"
color="dangerlight"
disabled={selectedItems.length === 0}
onClick={() => handleRemoveClick(selectedItems)}
icon={Trash2}
>
Remove
</Button>
<Link
to="kubernetes.deploy"
params={{ referrer: 'kubernetes.services' }}
className="space-left hover:no-decoration"
>
<Button className="btn-wrapper" color="primary" icon="plus">
Create from manifest
</Button>
</Link>
</Authorized>
</div>
);
}

View file

@ -0,0 +1,22 @@
import { ExternalLink } from 'lucide-react';
import { Icon } from '@@/Icon';
interface Props {
to: string;
text: string;
}
export function ExternalIPLink({ to, text }: Props) {
return (
<a
href={to}
target="_blank"
rel="noreferrer"
className="flex items-center gap-1"
>
<Icon icon={ExternalLink} />
<span>{text}</span>
</a>
);
}

View file

@ -0,0 +1,39 @@
import { CellContext } from '@tanstack/react-table';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Link } from '@@/Link';
import { Service } from '../../../types';
import { columnHelper } from './helper';
export const application = columnHelper.accessor(
(row) => (row.Applications ? row.Applications[0].Name : ''),
{
header: 'Application',
id: 'application',
cell: Cell,
}
);
function Cell({ row, getValue }: CellContext<Service, string>) {
const appName = getValue();
const environmentId = useEnvironmentId();
return appName ? (
<Link
to="kubernetes.applications.application"
params={{
endpointId: environmentId,
namespace: row.original.Namespace,
name: appName,
}}
title={appName}
>
{appName}
</Link>
) : (
'-'
);
}

View file

@ -0,0 +1,41 @@
import { columnHelper } from './helper';
export const clusterIP = columnHelper.accessor(
(row) => row.ClusterIPs?.join(','),
{
header: 'Cluster IP',
id: 'clusterIP',
cell: ({ row }) => {
const clusterIPs = row.original.ClusterIPs;
if (!clusterIPs?.length) {
return '-';
}
return clusterIPs.map((ip) => <div key={ip}>{ip}</div>);
},
sortingFn: (rowA, rowB) => {
const a = rowA.original.ClusterIPs;
const b = rowB.original.ClusterIPs;
const ipA = a?.[0];
const ipB = b?.[0];
// no ip's at top, followed by 'None', then ordered by ip
if (!ipA) return 1;
if (!ipB) return -1;
if (ipA === ipB) return 0;
if (ipA === 'None') return 1;
if (ipB === 'None') return -1;
// natural sort of the ip
return ipA.localeCompare(
ipB,
navigator.languages[0] || navigator.language,
{
numeric: true,
ignorePunctuation: true,
}
);
},
}
);

View file

@ -0,0 +1,23 @@
import { formatDate } from '@/portainer/filters/filters';
import { columnHelper } from './helper';
export const created = columnHelper.accessor(
(row) => {
const owner = row.Labels?.['io.portainer.kubernetes.application.owner'];
const date = formatDate(row.CreationTimestamp);
return owner ? `${date} by ${owner}` : date;
},
{
header: 'Created',
id: 'created',
cell: ({ row }) => {
const date = formatDate(row.original.CreationTimestamp);
const owner =
row.original.Labels?.['io.portainer.kubernetes.application.owner'];
return owner ? `${date} by ${owner}` : date;
},
}
);

View file

@ -0,0 +1,139 @@
import { CellContext } from '@tanstack/react-table';
import { Service } from '../../../types';
import { ExternalIPLink } from './ExternalIPLink';
import { columnHelper } from './helper';
export const externalIP = columnHelper.accessor(
(row) => {
if (row.Type === 'ExternalName') {
return row.ExternalName || '';
}
if (row.ExternalIPs?.length) {
return row.ExternalIPs?.join(',') || '';
}
return row.IngressStatus?.map((status) => status.IP).join(',') || '';
},
{
header: 'External IP',
id: 'externalIP',
cell: Cell,
sortingFn: (rowA, rowB) => {
const a = rowA.original.IngressStatus;
const b = rowB.original.IngressStatus;
const aExternal = rowA.original.ExternalIPs;
const bExternal = rowB.original.ExternalIPs;
const ipA = a?.[0].IP || aExternal?.[0] || rowA.original.ExternalName;
const ipB = b?.[0].IP || bExternal?.[0] || rowA.original.ExternalName;
if (!ipA) return 1;
if (!ipB) return -1;
// use a nat sort order for ip addresses
return ipA.localeCompare(
ipB,
navigator.languages[0] || navigator.language,
{
numeric: true,
ignorePunctuation: true,
}
);
},
}
);
function Cell({ row }: CellContext<Service, string>) {
if (row.original.Type === 'ExternalName') {
if (row.original.ExternalName) {
const linkTo = `http://${row.original.ExternalName}`;
return <ExternalIPLink to={linkTo} text={row.original.ExternalName} />;
}
return '-';
}
const [scheme, port] = getSchemeAndPort(row.original);
if (row.original.ExternalIPs?.length) {
return row.original.ExternalIPs?.map((ip, index) => {
// some ips come through blank
if (ip.length === 0) {
return '-';
}
if (scheme) {
let linkTo = `${scheme}://${ip}`;
if (port !== 80 && port !== 443) {
linkTo = `${linkTo}:${port}`;
}
return (
<div key={index}>
<ExternalIPLink to={linkTo} text={ip} />
</div>
);
}
return <div key={index}>{ip}</div>;
});
}
const status = row.original.IngressStatus;
if (status) {
return status?.map((status, index) => {
// some ips come through blank
if (status.IP.length === 0) {
return '-';
}
if (scheme) {
let linkTo = `${scheme}://${status.IP}`;
if (port !== 80 && port !== 443) {
linkTo = `${linkTo}:${port}`;
}
return (
<div key={index}>
<ExternalIPLink to={linkTo} text={status.IP} />
</div>
);
}
return <div key={index}>{status.IP}</div>;
});
}
return '-';
}
// calculate the scheme based on the ports of the service
// favour https over http.
function getSchemeAndPort(svc: Service): [string, number] {
let scheme = '';
let servicePort = 0;
svc.Ports?.forEach((port) => {
if (port.Protocol === 'TCP') {
switch (port.TargetPort) {
case '443':
case '8443':
case 'https':
scheme = 'https';
servicePort = port.Port;
break;
case '80':
case '8080':
case 'http':
if (scheme !== 'https') {
scheme = 'http';
servicePort = port.Port;
}
break;
default:
break;
}
}
});
return [scheme, servicePort];
}

View file

@ -0,0 +1,5 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Service } from '../../../types';
export const columnHelper = createColumnHelper<Service>();

View file

@ -0,0 +1,21 @@
import { name } from './name';
import { type } from './type';
import { namespace } from './namespace';
import { ports } from './ports';
import { clusterIP } from './clusterIP';
import { externalIP } from './externalIP';
import { targetPorts } from './targetPorts';
import { application } from './application';
import { created } from './created';
export const columns = [
name,
application,
namespace,
type,
ports,
targetPorts,
clusterIP,
externalIP,
created,
];

View file

@ -0,0 +1,53 @@
import { Authorized } from '@/react/hooks/useUser';
import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils';
import { columnHelper } from './helper';
export const name = columnHelper.accessor(
(row) => {
let name = row.Name;
const isExternal =
!row.Labels || !row.Labels['io.portainer.kubernetes.application.owner'];
const isSystem = isSystemNamespace(row.Namespace);
if (isExternal && !isSystem) {
name = `${name} external`;
}
if (isSystem) {
name = `${name} system`;
}
return name;
},
{
header: 'Name',
id: 'name',
cell: ({ row }) => {
const name = row.original.Name;
const isSystem = isSystemNamespace(row.original.Namespace);
const isExternal =
!row.original.Labels ||
!row.original.Labels['io.portainer.kubernetes.application.owner'];
return (
<Authorized authorizations="K8sServiceW" childrenUnauthorized={name}>
{name}
{isSystem && (
<span className="label label-info image-tag label-margins">
system
</span>
)}
{isExternal && !isSystem && (
<span className="label label-primary image-tag label-margins">
external
</span>
)}
</Authorized>
);
},
}
);

View file

@ -0,0 +1,35 @@
import { Row } from '@tanstack/react-table';
import { filterHOC } from '@/react/components/datatables/Filter';
import { Link } from '@@/Link';
import { Service } from '../../../types';
import { columnHelper } from './helper';
export const namespace = columnHelper.accessor('Namespace', {
header: 'Namespace',
id: 'namespace',
cell: ({ getValue }) => {
const namespace = getValue();
return (
<Link
to="kubernetes.resourcePools.resourcePool"
params={{
id: namespace,
}}
title={namespace}
>
{namespace}
</Link>
);
},
meta: {
filter: filterHOC('Filter by namespace'),
},
enableColumnFilter: true,
filterFn: (row: Row<Service>, columnId: string, filterValue: string[]) =>
filterValue.length === 0 || filterValue.includes(row.original.Namespace),
});

View file

@ -0,0 +1,73 @@
import { Tooltip } from '@@/Tip/Tooltip';
import { columnHelper } from './helper';
export const ports = columnHelper.accessor(
(row) =>
row.Ports.map(
(port) =>
`${port.Port}${port.NodePort !== 0 ? `:${port.NodePort}` : ''}/${
port.Protocol
}`
).join(',') || '-',
{
header: () => (
<>
Ports
<Tooltip message="The format of Ports is port[:nodePort]/protocol. Protocol is either TCP, UDP or SCTP." />
</>
),
id: 'ports',
cell: ({ row }) => {
if (!row.original.Ports.length) {
return '-';
}
return (
<>
{row.original.Ports.map((port, index) => {
if (port.NodePort !== 0) {
return (
<div key={index}>
{port.Port}:{port.NodePort}/{port.Protocol}
</div>
);
}
return (
<div key={index}>
{port.Port}/{port.Protocol}
</div>
);
})}
</>
);
},
sortingFn: (rowA, rowB) => {
const a = rowA.original.Ports;
const b = rowB.original.Ports;
if (!a.length && !b.length) return 0;
if (!a.length) return 1;
if (!b.length) return -1;
// sort order based on first port
const portA = a[0].Port;
const portB = b[0].Port;
if (portA === portB) {
// longer list of ports is considered "greater"
if (a.length < b.length) return -1;
if (a.length > b.length) return 1;
return 0;
}
// now do a regular number sort
if (portA < portB) return -1;
if (portA > portB) return 1;
return 0;
},
}
);

View file

@ -0,0 +1,45 @@
import { columnHelper } from './helper';
export const targetPorts = columnHelper.accessor(
(row) => row.Ports.map((port) => port.TargetPort).join(','),
{
header: 'Target Ports',
id: 'targetPorts',
cell: ({ row }) => {
const ports = row.original.Ports.map((port) => port.TargetPort);
if (!ports.length) {
return '-';
}
return ports.map((port, index) => <div key={index}>{port}</div>);
},
sortingFn: (rowA, rowB) => {
const a = rowA.original.Ports;
const b = rowB.original.Ports;
if (!a.length && !b.length) return 0;
if (!a.length) return 1;
if (!b.length) return -1;
const portA = a[0].TargetPort;
const portB = b[0].TargetPort;
if (portA === portB) {
if (a.length < b.length) return -1;
if (a.length > b.length) return 1;
return 0;
}
// natural sort of the port
return portA.localeCompare(
portB,
navigator.languages[0] || navigator.language,
{
numeric: true,
ignorePunctuation: true,
}
);
},
}
);

View file

@ -0,0 +1,18 @@
import { Row } from '@tanstack/react-table';
import { filterHOC } from '@@/datatables/Filter';
import { Service } from '../../../types';
import { columnHelper } from './helper';
export const type = columnHelper.accessor('Type', {
header: 'Type',
id: 'type',
meta: {
filter: filterHOC('Filter by type'),
},
enableColumnFilter: true,
filterFn: (row: Row<Service>, columnId: string, filterValue: string[]) =>
filterValue.length === 0 || filterValue.includes(row.original.Type),
});

View file

@ -0,0 +1,13 @@
import { refreshableSettings, createPersistedStore } from '@@/datatables/types';
import {
systemResourcesSettings,
TableSettings,
} from '../../../datatables/DefaultDatatableSettings';
export function createStore(storageKey: string) {
return createPersistedStore<TableSettings>(storageKey, 'name', (set) => ({
...refreshableSettings(set),
...systemResourcesSettings(set),
}));
}

View file

@ -0,0 +1 @@
export { ServicesDatatable } from './ServicesDatatable';

View file

@ -0,0 +1,12 @@
import { PageHeader } from '@@/PageHeader';
import { ServicesDatatable } from './ServicesDatatable';
export function ServicesView() {
return (
<>
<PageHeader title="Service list" breadcrumbs="Services" reload />
<ServicesDatatable />
</>
);
}

View file

@ -0,0 +1 @@
export { ServicesView } from './ServicesView';

View file

@ -1,23 +0,0 @@
import { saveAs } from 'file-saver';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
const baseUrl = 'kubernetes';
export async function downloadKubeconfigFile(environmentIds: EnvironmentId[]) {
try {
const { headers, data } = await axios.get<Blob>(`${baseUrl}/config`, {
params: { ids: JSON.stringify(environmentIds) },
responseType: 'blob',
headers: {
Accept: 'text/yaml',
},
});
const contentDispositionHeader = headers['content-disposition'];
const filename = contentDispositionHeader.replace('attachment;', '').trim();
saveAs(data, filename);
} catch (e) {
throw parseAxiosError(e as Error, '');
}
}

View file

@ -1,5 +0,0 @@
## Common Services
This folder contains rest api services that are shared by different features within kubernetes.
This includes api requests to the portainer backend, and also requests to the kubernetes api.

View file

@ -0,0 +1,107 @@
import { useMutation, useQuery, useQueryClient } from 'react-query';
import { compact } from 'lodash';
import { ServiceList } from 'kubernetes-types/core/v1';
import { withError } from '@/react-tools/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { isFulfilled } from '@/portainer/helpers/promise-utils';
import { Service } from './types';
export const queryKeys = {
clusterServices: (environmentId: EnvironmentId) =>
['environments', environmentId, 'kubernetes', 'services'] as const,
};
export function useServicesForCluster(
environmentId: EnvironmentId,
namespaceNames?: string[],
options?: { autoRefreshRate?: number }
) {
return useQuery(
queryKeys.clusterServices(environmentId),
async () => {
if (!namespaceNames?.length) {
return [];
}
const settledServicesPromise = await Promise.allSettled(
namespaceNames.map((namespace) =>
getServices(environmentId, namespace, true)
)
);
return compact(
settledServicesPromise.filter(isFulfilled).flatMap((i) => i.value)
);
},
{
...withError('Unable to get services.'),
refetchInterval() {
return options?.autoRefreshRate ?? false;
},
enabled: !!namespaceNames?.length,
}
);
}
export function useMutationDeleteServices(environmentId: EnvironmentId) {
const queryClient = useQueryClient();
return useMutation(deleteServices, {
onSuccess: () =>
queryClient.invalidateQueries(queryKeys.clusterServices(environmentId)),
...withError('Unable to delete service(s)'),
});
}
// get a list of services for a specific namespace from the Portainer API
async function getServices(
environmentId: EnvironmentId,
namespace: string,
lookupApps: boolean
) {
try {
const { data: services } = await axios.get<Array<Service>>(
`kubernetes/${environmentId}/namespaces/${namespace}/services`,
{
params: {
lookupapplications: lookupApps,
},
}
);
return services;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve services');
}
}
// getNamespaceServices is used to get a list of services for a specific namespace directly from the Kubernetes API
export async function getNamespaceServices(
environmentId: EnvironmentId,
namespace: string,
queryParams?: Record<string, string>
) {
const { data: services } = await axios.get<ServiceList>(
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/services`,
{
params: queryParams,
}
);
return services.items;
}
export async function deleteServices({
environmentId,
data,
}: {
environmentId: EnvironmentId;
data: Record<string, string[]>;
}) {
try {
return await axios.post(
`kubernetes/${environmentId}/services/delete`,
data
);
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to delete service(s)');
}
}

View file

@ -0,0 +1,41 @@
type ServicePort = {
Name: string;
NodePort: number;
Port: number;
Protocol: string;
TargetPort: string;
};
type IngressStatus = {
Hostname: string;
IP: string;
};
type Application = {
UID: string;
Name: string;
Type: string;
};
export type ServiceType =
| 'ClusterIP'
| 'ExternalName'
| 'NodePort'
| 'LoadBalancer';
export type Service = {
Name: string;
UID: string;
Namespace: string;
Annotations?: Record<string, string>;
Labels?: Record<string, string>;
Type: ServiceType;
Ports: Array<ServicePort>;
Selector?: Record<string, string>;
ClusterIPs?: Array<string>;
IngressStatus?: Array<IngressStatus>;
ExternalName?: string;
ExternalIPs?: Array<string>;
CreationTimestamp: string;
Applications?: Application[];
};

View file

@ -1,11 +0,0 @@
export * from './v1IngressClass';
export * from './v1ObjectMeta';
export type KubernetesApiListResponse<T> = {
apiVersion: string;
kind: string;
items: T;
metadata: {
resourceVersion?: string;
};
};

View file

@ -1,48 +0,0 @@
import { V1ObjectMeta } from './v1ObjectMeta';
/**
* IngressClassParametersReference identifies an API object. This can be used to specify a cluster or namespace-scoped resource.
*/
type V1IngressClassParametersReference = {
/**
* APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required.
*/
apiGroup?: string;
/**
* Kind is the type of resource being referenced.
*/
kind: string;
/**
* Name is the name of resource being referenced.
*/
name: string;
/**
* Namespace is the namespace of the resource being referenced. This field is required when scope is set to \"Namespace\" and must be unset when scope is set to \"Cluster\".
*/
namespace?: string;
/**
* Scope represents if this refers to a cluster or namespace scoped resource. This may be set to \"Cluster\" (default) or \"Namespace\".
*/
scope?: string;
};
type V1IngressClassSpec = {
controller?: string;
parameters?: V1IngressClassParametersReference;
};
/**
* IngressClass represents the class of the Ingress, referenced by the Ingress Spec. The `ingressclass.kubernetes.io/is-default-class` annotation can be used to indicate that an IngressClass should be considered default. When a single IngressClass resource has this annotation set to true, new Ingress resources without a class specified will be assigned this default class.
*/
export type V1IngressClass = {
/**
* APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
*/
apiVersion?: string;
/**
* Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
*/
kind?: string;
metadata?: V1ObjectMeta;
spec?: V1IngressClassSpec;
};

View file

@ -1,33 +0,0 @@
// type definitions taken from https://github.com/kubernetes-client/javascript/blob/master/src/gen/model/v1ObjectMeta.ts
// and simplified to only include the types we need
export type V1ObjectMeta = {
/**
* Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations
*/
annotations?: { [key: string]: string };
/**
* Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels
*/
labels?: { [key: string]: string };
/**
* Deprecated: ClusterName is a legacy field that was always cleared by the system and never used; it will be removed completely in 1.25. The name in the go struct is changed to help clients detect accidental use.
*/
clusterName?: string;
/**
* Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names
*/
name?: string;
/**
* Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces
*/
namespace?: string;
/**
* An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
*/
resourceVersion?: string;
/**
* UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids
*/
uid?: string;
};