Skip to content

Commit

Permalink
Remote cluster - Istio Configs Editable (#6240)
Browse files Browse the repository at this point in the history
* Istio Configs Create/Edit/Deletable

* Service Traffic Wizards support cluster

* Overview page fixes
  • Loading branch information
hhovsepy committed Jun 13, 2023
1 parent 26cfb9e commit dbe2667
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 50 deletions.
5 changes: 0 additions & 5 deletions business/istio_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1405,11 +1405,6 @@ func getPermissionsApi(ctx context.Context, k8s kubernetes.ClientInterface, clus
log.Debug("View only mode configured, skipping RBAC checks")
return canCreate, canPatch, canDelete
}
// Disable writes for remote clusters
if cluster != conf.KubernetesConfig.ClusterName {
log.Debug("Writes disabled for remote clusters")
return canCreate, canPatch, canDelete
}

/*
Kiali only uses create,patch,delete as WRITE permissions
Expand Down
2 changes: 1 addition & 1 deletion business/istio_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func TestCheckMulticlusterPermissions(t *testing.T) {

istioConfigDetailsRemote, err := configService.GetIstioConfigDetails(context.TODO(), "east", "test", "gateways", "gw-1")
assert.Equal("gw-1", istioConfigDetailsRemote.Gateway.Name)
assert.False(istioConfigDetailsRemote.Permissions.Update)
assert.True(istioConfigDetailsRemote.Permissions.Update)
assert.False(istioConfigDetailsRemote.Permissions.Delete)
assert.Nil(err)
}
Expand Down
82 changes: 70 additions & 12 deletions frontend/src/components/IstioWizards/ServiceWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,19 @@ class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWizardSta
const pa = this.state.previews!.pa;
// Gateway is only created when user has explicit selected this option
if (gw) {
promises.push(API.createIstioConfigDetail(this.props.namespace, 'gateways', JSON.stringify(gw)));
promises.push(
API.createIstioConfigDetail(this.props.namespace, 'gateways', JSON.stringify(gw), this.props.cluster)
);
}
if (k8sgateway) {
promises.push(API.createIstioConfigDetail(this.props.namespace, 'k8sgateways', JSON.stringify(k8sgateway)));
promises.push(
API.createIstioConfigDetail(
this.props.namespace,
'k8sgateways',
JSON.stringify(k8sgateway),
this.props.cluster
)
);
}

if (this.props.update) {
Expand All @@ -348,13 +357,20 @@ class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWizardSta
this.props.namespace,
'destinationrules',
dr.metadata.name,
JSON.stringify(dr)
JSON.stringify(dr),
this.props.cluster
)
);
}
if (vs) {
promises.push(
API.updateIstioConfigDetail(this.props.namespace, 'virtualservices', vs.metadata.name, JSON.stringify(vs))
API.updateIstioConfigDetail(
this.props.namespace,
'virtualservices',
vs.metadata.name,
JSON.stringify(vs),
this.props.cluster
)
);
}
if (k8shttproute) {
Expand All @@ -363,7 +379,8 @@ class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWizardSta
this.props.namespace,
'k8shttproutes',
k8shttproute.metadata.name,
JSON.stringify(k8shttproute)
JSON.stringify(k8shttproute),
this.props.cluster
)
);
}
Expand All @@ -374,19 +391,45 @@ class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWizardSta
// Note that Gateways are not updated from the Wizard, only the VS hosts/gateways sections are updated
} else {
if (dr) {
promises.push(API.createIstioConfigDetail(this.props.namespace, 'destinationrules', JSON.stringify(dr)));
promises.push(
API.createIstioConfigDetail(
this.props.namespace,
'destinationrules',
JSON.stringify(dr),
this.props.cluster
)
);
}
if (vs) {
promises.push(API.createIstioConfigDetail(this.props.namespace, 'virtualservices', JSON.stringify(vs)));
promises.push(
API.createIstioConfigDetail(
this.props.namespace,
'virtualservices',
JSON.stringify(vs),
this.props.cluster
)
);
}
if (k8shttproute) {
promises.push(
API.createIstioConfigDetail(this.props.namespace, 'k8shttproutes', JSON.stringify(k8shttproute))
API.createIstioConfigDetail(
this.props.namespace,
'k8shttproutes',
JSON.stringify(k8shttproute),
this.props.cluster
)
);
}

if (pa) {
promises.push(API.createIstioConfigDetail(this.props.namespace, 'peerauthentications', JSON.stringify(pa)));
promises.push(
API.createIstioConfigDetail(
this.props.namespace,
'peerauthentications',
JSON.stringify(pa),
this.props.cluster
)
);
}
}
break;
Expand Down Expand Up @@ -427,14 +470,29 @@ class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWizardSta
): void => {
if (pa) {
if (this.state.trafficPolicy.peerAuthnSelector.addPeerAuthnModified) {
promises.push(API.createIstioConfigDetail(this.props.namespace, 'peerauthentications', JSON.stringify(pa)));
promises.push(
API.createIstioConfigDetail(
this.props.namespace,
'peerauthentications',
JSON.stringify(pa),
this.props.cluster
)
);
} else {
promises.push(
API.updateIstioConfigDetail(this.props.namespace, 'peerauthentications', dr.metadata.name, JSON.stringify(pa))
API.updateIstioConfigDetail(
this.props.namespace,
'peerauthentications',
dr.metadata.name,
JSON.stringify(pa),
this.props.cluster
)
);
}
} else if (this.state.trafficPolicy.peerAuthnSelector.addPeerAuthnModified) {
promises.push(API.deleteIstioConfigDetail(this.props.namespace, 'peerauthentications', dr.metadata.name));
promises.push(
API.deleteIstioConfigDetail(this.props.namespace, 'peerauthentications', dr.metadata.name, this.props.cluster)
);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type ReduxProps = {

type Props = ReduxProps & {
namespace: string;
cluster?: string;
serviceName: string;
show: boolean;
readOnly: boolean;
Expand Down Expand Up @@ -165,7 +166,8 @@ class ServiceWizardDropdownComponent extends React.Component<Props, State> {
deleteServiceTrafficRouting(
this.props.virtualServices,
DestinationRuleC.fromDrArray(this.props.destinationRules),
this.props.k8sHTTPRoutes
this.props.k8sHTTPRoutes,
this.props.cluster
)
.then(_results => {
this.setState({
Expand Down Expand Up @@ -207,7 +209,7 @@ class ServiceWizardDropdownComponent extends React.Component<Props, State> {

onChangeAnnotations = (annotations: { [key: string]: string }) => {
const jsonInjectionPatch = buildAnnotationPatch(annotations);
API.updateService(this.props.namespace, this.props.serviceName, jsonInjectionPatch, 'json')
API.updateService(this.props.namespace, this.props.serviceName, jsonInjectionPatch, 'json', this.props.cluster)
.then(_ => {
AlertUtils.add('Service ' + this.props.serviceName + ' updated', 'default', MessageType.SUCCESS);
this.setState(
Expand Down Expand Up @@ -268,6 +270,7 @@ class ServiceWizardDropdownComponent extends React.Component<Props, State> {
type={this.state.wizardType}
update={this.state.updateWizard}
namespace={this.props.namespace}
cluster={this.props.cluster}
serviceName={this.props.serviceName}
workloads={validWorkloads}
subServices={this.props.subServices}
Expand Down
13 changes: 8 additions & 5 deletions frontend/src/components/IstioWizards/WizardActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export type ServiceWizardProps = {
type: string;
update: boolean;
namespace: string;
cluster?: string;
serviceName: string;
servicePort?: number;
tlsStatus?: TLSStatus;
Expand Down Expand Up @@ -2053,10 +2054,12 @@ export const buildWorkloadInjectionPatch = (workloadType: string, enable: boolea
};

export const buildAnnotationPatch = (annotations: { [key: string]: string }): string => {
const patch = [{
"op": "replace",
"path": "/metadata/annotations",
"value": annotations
}];
const patch = [
{
op: 'replace',
path: '/metadata/annotations',
value: annotations
}
];
return JSON.stringify(patch);
};
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ class IstioConfigDetailsPageComponent extends React.Component<IstioConfigDetails
API.deleteIstioConfigDetail(
this.props.match.params.namespace,
this.props.match.params.objectType,
this.props.match.params.object
this.props.match.params.object,
this.state.cluster
)
.then(() => this.backToList())
.catch(error => {
Expand All @@ -273,7 +274,8 @@ class IstioConfigDetailsPageComponent extends React.Component<IstioConfigDetails
this.props.match.params.namespace,
this.props.match.params.objectType,
this.props.match.params.object,
jsonPatch
jsonPatch,
this.state.cluster
)
.then(() => {
const targetMessage =
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/pages/Overview/OverviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -506,14 +506,15 @@ export class OverviewPage extends React.Component<OverviewProps, State> {
}

fetchValidationResultForCluster(namespaces: NamespaceInfo[], cluster: string) {
return Promise.all([API.getConfigValidations(cluster), API.getAllIstioConfigs([], [], false, '', '')])
return Promise.all([API.getConfigValidations(cluster), API.getAllIstioConfigs([], [], false, '', '', cluster)])
.then(results => {
namespaces.forEach(nsInfo => {
if (nsInfo.cluster && nsInfo.cluster === cluster && results[0].data[nsInfo.cluster]) {
nsInfo.validations = results[0].data[nsInfo.cluster][nsInfo.name];
}
// TODO: cluster param here when remote cluster config creation supported
nsInfo.istioConfig = results[1].data[nsInfo.name];
if (nsInfo.cluster && nsInfo.cluster === cluster) {
nsInfo.istioConfig = results[1].data[nsInfo.name];
}
});
})
.catch(err => this.handleAxiosError('Could not fetch validations status', err));
Expand Down
22 changes: 14 additions & 8 deletions frontend/src/pages/Overview/OverviewTrafficPolicies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export default class OverviewTrafficPolicies extends React.Component<OverviewTra
onAddRemoveTrafficPolicies = (): void => {
const op = this.props.opTarget;
const ns = this.props.nsTarget;
const cluster = this.props.nsInfo?.cluster;
const duration = this.props.duration;
const apsP = this.state.authorizationPolicies;
const sdsP = this.state.sidecars;
Expand All @@ -173,13 +174,13 @@ export default class OverviewTrafficPolicies extends React.Component<OverviewTra
.registerAll(
'trafficPoliciesDelete',
apsP
.map(ap => API.deleteIstioConfigDetail(ns, 'authorizationpolicies', ap.metadata.name))
.concat(sdsP.map(sc => API.deleteIstioConfigDetail(ns, 'sidecars', sc.metadata.name)))
.map(ap => API.deleteIstioConfigDetail(ns, 'authorizationpolicies', ap.metadata.name, cluster))
.concat(sdsP.map(sc => API.deleteIstioConfigDetail(ns, 'sidecars', sc.metadata.name, cluster)))
)
.then(_ => {
//Error here
if (op !== 'delete') {
this.createTrafficPolicies(ns, duration, apsP, sdsP, op);
this.createTrafficPolicies(ns, duration, apsP, sdsP, op, cluster);
} else {
AlertUtils.add('Traffic policies ' + op + 'd for ' + ns + ' namespace.', 'default', MessageType.SUCCESS);
this.props.load();
Expand All @@ -191,7 +192,7 @@ export default class OverviewTrafficPolicies extends React.Component<OverviewTra
}
});
} else {
this.createTrafficPolicies(ns, duration, apsP, sdsP);
this.createTrafficPolicies(ns, duration, apsP, sdsP, op, cluster);
}
};

Expand All @@ -200,16 +201,17 @@ export default class OverviewTrafficPolicies extends React.Component<OverviewTra
duration: DurationInSeconds,
aps: AuthorizationPolicy[],
sds: Sidecar[],
op: string = 'create'
op: string = 'create',
cluster?: string
) => {
const graphDataSource = new GraphDataSource();
graphDataSource.on('fetchSuccess', () => {
this.promises
.registerAll(
'trafficPoliciesCreate',
aps
.map(ap => API.createIstioConfigDetail(ns, 'authorizationpolicies', JSON.stringify(ap)))
.concat(sds.map(sc => API.createIstioConfigDetail(ns, 'sidecars', JSON.stringify(sc))))
.map(ap => API.createIstioConfigDetail(ns, 'authorizationpolicies', JSON.stringify(ap), cluster))
.concat(sds.map(sc => API.createIstioConfigDetail(ns, 'sidecars', JSON.stringify(sc), cluster)))
)
.then(results => {
if (results.length > 0) {
Expand Down Expand Up @@ -250,7 +252,11 @@ export default class OverviewTrafficPolicies extends React.Component<OverviewTra
const aps = items.filter(i => i.type === 'authorizationPolicy')[0];
const sds = items.filter(i => i.type === 'sidecar')[0];
this.setState(
{ authorizationPolicies: aps.items as AuthorizationPolicy[], sidecars: sds.items as Sidecar[], loaded: false },
{
authorizationPolicies: aps ? (aps.items as AuthorizationPolicy[]) : [],
sidecars: sds ? (sds.items as Sidecar[]) : [],
loaded: false
},
() => this.fetchPermission(true, false)
);
};
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/ServiceDetails/ServiceDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ class ServiceDetails extends React.Component<ServiceDetailsProps, ServiceDetails
const actionsToolbar = this.state.serviceDetails ? (
<ServiceWizardDropdown
namespace={this.props.match.params.namespace}
cluster={this.state.cluster ? this.state.cluster : ''}
serviceName={this.state.serviceDetails.service.name}
annotations={this.state.serviceDetails.service.annotations}
show={false}
Expand Down

0 comments on commit dbe2667

Please sign in to comment.