Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {MatTableDataSource} from '@angular/material/table';
import {KyvernoService} from '@app/core/services/kyverno';
import {NotificationService} from '@app/core/services/notification';
import {UserService} from '@app/core/services/user';
import {PolicyTemplate} from '@app/shared/entity/kyverno';
import {PolicyBinding, PolicyTemplate} from '@app/shared/entity/kyverno';
import {Group} from '@app/shared/utils/member';
import {filter, Subject, switchMap, take, takeUntil} from 'rxjs';
import _ from 'lodash';
Expand All @@ -39,10 +39,6 @@ import {
ConfirmationDialogConfig,
} from '@app/shared/components/confirmation-dialog/component';

interface templatesBinding {
bindingName: string;
namespace: string;
}
@Component({
selector: 'km-kyverno-cluster-policies-list',
templateUrl: './template.html',
Expand All @@ -56,14 +52,13 @@ export class KyvernoClusterPoliciesListComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, {static: true}) sort: MatSort;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
private readonly _unsubscribe = new Subject<void>();
dataSource = new MatTableDataSource<PolicyTemplate>();
dataSource = new MatTableDataSource<PolicyBinding>();
policyTemplates: PolicyTemplate[] = [];
policiesWithBinding: PolicyTemplate[] = [];
policyBindings: PolicyBinding[] = [];
columns = ['name', 'category', 'namespace', 'view'];
loadingTemplates = false;
hasOwnerRole = false;
nameSpaces: string[] = [];
policyBindings: Record<string, templatesBinding> = {};

constructor(
private readonly _kyvernoService: KyvernoService,
Expand Down Expand Up @@ -103,12 +98,13 @@ export class KyvernoClusterPoliciesListComponent implements OnInit, OnDestroy {
}

openAddPolicyDialog(): void {
const noneBindingPolicies = this.policyTemplates.filter(template => !this.policyBindings[template.name]);
const templatesToExclude = new Set(this.policyBindings.map(binding => binding.spec.policyTemplateRef.name));
const templatesWithNoBinding = this.policyTemplates.filter(template => !templatesToExclude.has(template.name));
const config: MatDialogConfig = {
data: {
projectID: this.projectID,
clusterID: this.cluster.id,
templates: noneBindingPolicies,
templates: templatesWithNoBinding,
namespaces: this.nameSpaces,
} as AddPolicyDialogConfig,
};
Expand All @@ -120,8 +116,7 @@ export class KyvernoClusterPoliciesListComponent implements OnInit, OnDestroy {
.subscribe(_ => this._getPolicyBindings());
}

deletePolicyBinding(templateName: string): void {
const bindingName = this.policyBindings[templateName].bindingName;
deletePolicyBinding(bindingName: string): void {
const config: MatDialogConfig = {
data: {
title: 'Delete Policy',
Expand All @@ -137,13 +132,26 @@ export class KyvernoClusterPoliciesListComponent implements OnInit, OnDestroy {
.pipe(switchMap(_ => this._kyvernoService.deletePolicyBinding(bindingName, this.projectID, this.cluster.id)))
.subscribe(_ => {
this._notificationService.success(`Deleting the ${bindingName} policy`);
delete this.policyBindings[templateName];
this.policiesWithBinding = this.policiesWithBinding.filter(template => template.name !== templateName);
this.dataSource.data = this.policiesWithBinding;
this.policyBindings = this.policyBindings.filter(binding => binding.name !== bindingName);
this.dataSource.data = this.policyBindings;
});
}

viewTemplateSpec(template: PolicyTemplate): void {
canDeletePolicy(templateName: string): boolean {
const template = this.policyTemplates.find(template => template.name === templateName);

if (!template) {
return this.hasOwnerRole && this.isClusterRunning;
}
return this.hasOwnerRole && !template?.spec.enforced && this.isClusterRunning;
}

viewTemplateSpec(templateName: string): void {
const template = this.policyTemplates.find(template => template.name === templateName);
if (!template) {
return;
}

const config: MatDialogConfig = {
data: {
template: template,
Expand All @@ -152,21 +160,23 @@ export class KyvernoClusterPoliciesListComponent implements OnInit, OnDestroy {
this._matDialog.open(ViewTemplateDialogComponent, config);
}

canDeletePolicy(enforced: boolean): boolean {
return this.hasOwnerRole && !enforced && this.isClusterRunning;
canViewTemplate(templateName: string): boolean {
const template = this.policyTemplates.find(template => template.name === templateName);
return !!template;
}

getCategory(policyTemplateName: string): string {
const template = this.policyTemplates.find(template => template.name === policyTemplateName);
return template ? template.spec.category : '-';
}

private _getPolicyBindings(): void {
this._kyvernoService
.listPolicyBindings(this.projectID, this.cluster.id)
.pipe(takeUntil(this._unsubscribe))
.subscribe(bindings => {
bindings.forEach(binding => {
this.policyBindings[binding.spec.policyTemplateRef.name] = {
bindingName: binding?.name,
namespace: binding?.spec?.kyvernoPolicyNamespace?.name,
};
});
this.policyBindings = bindings;
this.dataSource.data = bindings;
this._getPolicyTemplates();
});
}
Expand All @@ -180,14 +190,15 @@ export class KyvernoClusterPoliciesListComponent implements OnInit, OnDestroy {
if (_.isEmpty(template.spec?.target?.clusterSelector)) {
return true;
}
if (this.policyBindings.some(binding => binding.spec.policyTemplateRef.name === template.name)) {
return true;
}
const labelKeys: string[] = Object.keys(template.spec.target.clusterSelector.matchLabels);
const hasMatchedLabels = !!labelKeys.find(key =>
this._isMatchedLabel(key, template.spec.target.clusterSelector.matchLabels[key])
);
return hasMatchedLabels;
});
this.policiesWithBinding = this.policyTemplates.filter(template => !!this.policyBindings[template.name]);
this.dataSource.data = this.policiesWithBinding;
this.loadingTemplates = false;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
class="km-header-cell">Category</th>
<td mat-cell
*matCellDef="let element">
<span>{{element.spec.category}}</span>
<span>{{getCategory(element.spec.policyTemplateRef.name)}}</span>
</td>
</ng-container>
<ng-container matColumnDef="namespace">
Expand All @@ -66,8 +66,7 @@
</div>
<td mat-cell
*matCellDef="let element">
<span *ngIf="element.spec.namespacedPolicy">{{policyBindings[element.name]?.namespace}}</span>
<span *ngIf="!element.spec.namespacedPolicy">Not Namespaced Policy</span>
<span>{{element.spec.kyvernoPolicyNamespace?.name ?? 'Not Namespaced Policy'}}</span>
</td>
</ng-container>
<ng-container matColumnDef="view">
Expand All @@ -78,13 +77,16 @@
*matCellDef="let element">
<div fxLayoutAlign="end"
class="km-table-actions">
<button mat-icon-button
(click)="viewTemplateSpec(element)">
<i class="km-icon-mask km-icon-show"></i>
</button>
<span [matTooltip]="canViewTemplate(element.spec.policyTemplateRef.name) ? 'View Template' : 'Template not available'">
<button mat-icon-button
(click)="viewTemplateSpec(element.spec.policyTemplateRef.name)"
[disabled]="!canViewTemplate(element.spec.policyTemplateRef.name)">
<i class="km-icon-mask km-icon-show"></i>
</button>
</span>
<button mat-icon-button
(click)="deletePolicyBinding(element.name)"
[disabled]="!canDeletePolicy(element.spec.enforced)">
[disabled]="!canDeletePolicy(element.spec.policyTemplateRef.name)">
<i class="km-icon-mask km-icon-delete"></i>
</button>
</div>
Expand All @@ -96,10 +98,10 @@
*matRowDef="let row; columns: columns;"></tr>
</table>
<div class="km-row km-empty-list-msg"
*ngIf="!policiesWithBinding.length && !loadingTemplates">No policy templates are available</div>
*ngIf="!policyBindings.length && !loadingTemplates">No policy templates are available</div>

<div class="km-row"
*ngIf="loadingTemplates && policiesWithBinding.length">
*ngIf="loadingTemplates && policyBindings.length">
<mat-spinner color="accent"
class="km-spinner km-with-spacing"
[diameter]="25"></mat-spinner>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
import {Project} from '@app/shared/entity/project';
import {KUBERNETES_RESOURCE_NAME_PATTERN_VALIDATOR} from '@app/shared/validators/others';
import * as y from 'js-yaml';
import {Observable, Subject, take} from 'rxjs';
import {Observable, Subject, take, takeUntil} from 'rxjs';

export interface AddPolicyTemplateDialogConfig {
mode: PolicyTemplateDialogMode;
Expand Down Expand Up @@ -149,6 +149,18 @@ export class AddPolicyTemplateDialogComponent implements OnInit, OnDestroy {
this.form.get(Controls.Project).disable();
this.form.get(Controls.Scope).disable();
}

this.form
.get(Controls.Enforced)
.valueChanges.pipe(takeUntil(this._unsubscribe))
.subscribe(value => {
if (value) {
this.form.get(Controls.NamespacedPolicy).setValue(false);
this.form.get(Controls.NamespacedPolicy).disable();
} else {
this.form.get(Controls.NamespacedPolicy).enable();
}
});
}

ngOnDestroy(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,13 @@
<i class="km-icon-info km-pointer"
matTooltip="Enforced policies will be applied to all targeted clusters. Users can't delete them."></i>
</mat-checkbox>
<mat-checkbox [formControlName]="controls.NamespacedPolicy">
Namespaced Policy
<span>
<mat-checkbox [formControlName]="controls.NamespacedPolicy">
Namespaced Policy
</mat-checkbox>
<i class="km-icon-info km-pointer"
matTooltip="Enable to scope the policy down to Namespace instead of the default i.e. Cluster scope."></i>
</mat-checkbox>
</span>
</div>
<km-label-form *ngIf="form.get(controls.Scope).value === scopes.Global"
title="Projects Labels Selector"
Expand Down