Skip to content

Commit

Permalink
Merge pull request #48 from weaveworks/ToggleSuspendResume
Browse files Browse the repository at this point in the history
Toggle suspend resume
  • Loading branch information
ahussein3 committed Sep 12, 2023
2 parents 3127e27 + 77e7022 commit 5663471
Show file tree
Hide file tree
Showing 12 changed files with 825 additions and 310 deletions.
21 changes: 12 additions & 9 deletions plugins/backstage-plugin-flux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
The Flux plugin for Backstage provides views of [Flux](https://fluxcd.io/) resources available in Kubernetes clusters.

<!-- This URL is a complete path so that it shows up in the NPM package -->

![EntityFluxSourcesCard](https://raw.githubusercontent.com/weaveworks/weaveworks-backstage/main/plugins/backstage-plugin-flux/sources_card.png)

## Content
Expand Down Expand Up @@ -31,6 +32,7 @@ As with other Backstage plugins, you can compose the UI you need.
The Kubernetes plugins including `@backstage/plugin-kubernetes` and `@backstage/plugin-kubernetes-backend` are to be installed and configured by following the installation and configuration [guides](https://backstage.io/docs/features/kubernetes/installation/#adding-the-kubernetes-frontend-plugin).

After they are installed, make sure to import the frontend plugin by adding the "Kubernetes" tab wherever needed.

```tsx
// In packages/app/src/components/catalog/EntityPage.tsx
import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
Expand All @@ -45,6 +47,7 @@ const serviceEntityPage = (
</EntityLayout>
);
```

If you are using the [`config`](https://backstage.io/docs/features/kubernetes/configuration#config) method for configuring your clusters, and connecting using a `ServiceAccount`, you will need to bind the `ServiceAccount` to the `ClusterRole` `flux-view-flux-system` that is created with these [permissions](https://github.com/fluxcd/flux2/blob/44d69d6fc0c353e79c1bad021a4aca135033bce8/manifests/rbac/view.yaml) by Flux.

```yaml
Expand All @@ -62,13 +65,13 @@ subjects:
namespace: flux-system
```
The "sync" button requires additional permissions, it implements same functionality as [flux reconcile](https://fluxcd.io/flux/cmd/flux_reconcile/) for resources.
The "sync", "suspend/resume" button requires additional permissions, it implements same functionality as [flux reconcile](https://fluxcd.io/flux/cmd/flux_reconcile/) for resources.
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sync-flux-resources
name: patch-flux-resources
rules:
- apiGroups:
- source.toolkit.fluxcd.io
Expand All @@ -80,9 +83,9 @@ rules:
- ocirepositories
verbs:
- patch
- apiGroups:
- apiGroups:
- kustomize.toolkit.fluxcd.io
resources:
resources:
- kustomizations
verbs:
- patch
Expand All @@ -96,11 +99,11 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: backstage-sync-flux-resources-rolebinding
name: backstage-patch-flux-resources-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sync-flux-resources
name: patch-flux-resources
subjects:
- kind: ServiceAccount
name: backstage # replace with the name of the SA that your Backstage runs as
Expand Down Expand Up @@ -271,7 +274,6 @@ kubernetes:
skipMetricsLookup: true
serviceAccountToken: ABC123
caData: LS0tLS1CRUdJTiBDRVJUSUZJQ0...
```

## Verification
Expand All @@ -280,9 +282,10 @@ For the resources where we display a Verification status, if the Flux resource
has no verification configured, the column will be blank.

<!-- This URL is a complete path so that it shows up in the NPM package -->

![Verification status for resources](https://raw.githubusercontent.com/weaveworks/weaveworks-backstage/main/plugins/backstage-plugin-flux/verification.png)

You can configure verification for the following resources:

* [Git Repositories](https://fluxcd.io/flux/components/source/gitrepositories/#verification)
* [OCI Repositories](https://fluxcd.io/flux/components/source/ocirepositories/#verification)
- [Git Repositories](https://fluxcd.io/flux/components/source/gitrepositories/#verification)
- [OCI Repositories](https://fluxcd.io/flux/components/source/ocirepositories/#verification)
92 changes: 67 additions & 25 deletions plugins/backstage-plugin-flux/dev/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,33 @@ const copy = (obj: any): any => {
return JSON.parse(JSON.stringify(obj));
};

const removeVerifiedCondition = (conditions: Condition[]): Condition[] => copy(conditions).filter((cond: Condition) => cond.type !== 'SourceVerified');
const removeVerifiedCondition = (conditions: Condition[]): Condition[] =>
copy(conditions).filter((cond: Condition) => cond.type !== 'SourceVerified');

const applyReadyCondition = (status: boolean, conditions: Condition[]): Condition[] => {
const applyReadyCondition = (
status: boolean,
conditions: Condition[],
): Condition[] => {
const ready = conditions.find(cond => cond.type === 'Ready');
if (ready === undefined) {
return conditions;
}

ready.status = Boolean(status) === true ? 'True' : 'False';
const result = conditions.filter((cond: Condition) => cond.type !== 'Ready')
const result = conditions.filter((cond: Condition) => cond.type !== 'Ready');
result.unshift(ready);

return result;
}
};

const configureFixture = (name: string, url: string, fixture: any, verifiedFixture: any, unverifiedFixture: any, opts?: RepoOpts) => {
const configureFixture = (
name: string,
url: string,
fixture: any,
verifiedFixture: any,
unverifiedFixture: any,
opts?: RepoOpts,
) => {
let result = copy(fixture);

if (opts?.verify) {
Expand All @@ -49,11 +60,16 @@ const configureFixture = (name: string, url: string, fixture: any, verifiedFixtu
}

if (opts?.verify && opts?.pending) {
result.status.conditions = removeVerifiedCondition(result.status.conditions);
result.status.conditions = removeVerifiedCondition(
result.status.conditions,
);
}

if (opts?.ready !== undefined) {
result.status.conditions = applyReadyCondition(opts.ready!, result.status.conditions);
result.status.conditions = applyReadyCondition(
opts.ready!,
result.status.conditions,
);
}

result.spec.url = url;
Expand All @@ -63,60 +79,85 @@ const configureFixture = (name: string, url: string, fixture: any, verifiedFixtu
.toISO()!;

return result;
}
};

export const newTestOCIRepository = (
name: string,
url: string,
opts?: RepoOpts
opts?: RepoOpts,
) => {
return configureFixture(name, url, ociRepository, verifiedOCIRepository, unverifiedOCIRepository, opts);
return configureFixture(
name,
url,
ociRepository,
verifiedOCIRepository,
unverifiedOCIRepository,
opts,
);
};

export const newTestGitRepository = (
name: string,
url: string,
opts?: RepoOpts
opts?: RepoOpts,
) => {
return configureFixture(name, url, gitRepository, verifiedGitRepository, unverifiedGitRepository, opts);
return configureFixture(
name,
url,
gitRepository,
verifiedGitRepository,
unverifiedGitRepository,
opts,
);
};

export const newTestKustomization = (
name: string,
path: string,
ready: boolean,
suspend: boolean,
) => {
const result = copy(kustomization);
const result = copy(kustomization);

result.metadata.name = name;
result.spec.path = path;
result.metadata.name = name;
result.spec.path = path;

result.metadata.name = name;
result.spec.path = path;
result.status.conditions = applyReadyCondition(ready, result.status.conditions);
result.metadata.name = name;
result.spec.path = path;
result.status.conditions = applyReadyCondition(
ready,
result.status.conditions,
);
result.spec.suspend = suspend;

return result;
return result;
};

export const newTestHelmRepository = (
name: string,
url: string,
ready: boolean = true,
suspend: boolean,
) => {
const result = copy(helmRepository);
const result = copy(helmRepository);

result.metadata.name = name;
result.spec.url = url;
result.status.conditions = applyReadyCondition(ready, result.status.conditions);
result.metadata.name = name;
result.spec.url = url;
result.status.conditions = applyReadyCondition(
ready,
result.status.conditions,
);
result.spec.suspend = suspend;

return result;
return result;
};

export const newTestHelmRelease = (
name: string,
chart: string,
version: string,
ready: string = 'True',
suspend: boolean,
) => {
return {
apiVersion: 'helm.toolkit.fluxcd.io/v2beta1',
Expand All @@ -131,6 +172,7 @@ export const newTestHelmRelease = (
namespace: 'default',
},
spec: {
suspend,
interval: '5m',
chart: {
spec: {
Expand Down Expand Up @@ -174,4 +216,4 @@ export const newTestHelmRelease = (
observedGeneration: 12,
},
};
};
};
45 changes: 38 additions & 7 deletions plugins/backstage-plugin-flux/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class StubKubernetesClient implements KubernetesApi {
};
}

// this is only used by sync right now, so it looks a little bit funny
// this is only used by sync and suspend/resume
async proxy({
init,
path,
Expand All @@ -124,14 +124,24 @@ class StubKubernetesClient implements KubernetesApi {
path: string;
init?: RequestInit | undefined;
}): Promise<any> {
// wait 100ms
// wait 100ms so the UI can show a loader or something
await new Promise(resolve => setTimeout(resolve, 100));

// Assumption: The initial request!
// Generates 2 more subsequent requests that can be retrieved in order via GET'ing
// In the case of "sync" it generates 2 more subsequent requests that can be
// retrieved in order via GET'ing. This simulate the polling the UI will do.
//
if (init?.method === 'PATCH') {
const data = JSON.parse(init.body as string);

// We're getting a request to suspend/resume the resource
// just return 200 all good, no polling here.
if (data.spec && 'suspend' in data.spec) {
return {
ok: true,
} as Response;
}

const reconiliationRequestedAt =
data.metadata.annotations[ReconcileRequestAnnotation];
this.mockResponses[path] = [
Expand Down Expand Up @@ -208,21 +218,33 @@ createDevApp()
'prometheus1',
'kube-prometheus-stack',
'6.3.5',
'True',
false,
),
newTestHelmRelease(
'prometheus2',
'kube-prometheus-stack',
'6.3.5',
'True',
false,
),
newTestHelmRelease(
'prometheus3',
'kube-prometheus-stack',
'6.3.5',
'False',
true,
),
newTestHelmRelease('redis1', 'redis', '7.0.1', 'False', false),
newTestHelmRelease('redis2', 'redis', '7.0.1', 'True', true),
newTestHelmRelease('http-api', 'redis', '1.2.5', 'False', false),
newTestHelmRelease(
'queue-runner',
'redis',
'1.0.1',
'True',
false,
),
newTestHelmRelease('redis1', 'redis', '7.0.1', 'False'),
newTestHelmRelease('redis2', 'redis', '7.0.1'),
newTestHelmRelease('http-api', 'redis', '1.2.5', 'False'),
newTestHelmRelease('queue-runner', 'redis', '1.0.1'),
]),
],
[kubernetesAuthProvidersApiRef, new StubKubernetesAuthProvidersApi()],
Expand Down Expand Up @@ -360,11 +382,13 @@ createDevApp()
'flux-system',
'./clusters/my-cluster',
true,
true,
),
newTestKustomization(
'test-kustomization',
'./clusters/my-test-cluster',
true,
false,
),
]),
],
Expand Down Expand Up @@ -398,6 +422,8 @@ createDevApp()
newTestHelmRepository(
'podinfo',
'https://stefanprodan.github.io/podinfo',
true,
false,
),
]),
],
Expand Down Expand Up @@ -432,11 +458,14 @@ createDevApp()
'flux-system',
'./clusters/my-cluster',
true,
false,
),
newTestHelmRelease(
'prometheus1',
'kube-prometheus-stack',
'6.3.5',
'True',
true,
),
]),
],
Expand Down Expand Up @@ -470,6 +499,8 @@ createDevApp()
newTestHelmRepository(
'podinfo',
'https://stefanprodan.github.io/podinfo',
true,
true,
),
newTestOCIRepository(
'podinfo',
Expand Down
Binary file modified plugins/backstage-plugin-flux/sources_card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 5663471

Please sign in to comment.