Skip to content

Commit

Permalink
URL-encode the cluster name (#45)
Browse files Browse the repository at this point in the history
This changes the Weave GitOps linking to correctly encode the name of
the cluster.

Guidance is also provided on linking to namespaced clusters.
  • Loading branch information
bigkevmcd authored Aug 7, 2023
1 parent 4ca8e4f commit d6a8c9f
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 10 deletions.
40 changes: 40 additions & 0 deletions plugins/backstage-plugin-flux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,46 @@ gitops:
baseUrl: https://wego.example.com
```

**NOTE:** The URL generated will include the name of the cluster that is configured in Backstage's cluster location mechanism.

```yaml
kubernetes:
serviceLocatorMethod:
type: 'multiTenant'
clusterLocatorMethods:
- type: 'config'
clusters:
- url: https://192.168.0.1:8000
name: Default
authProvider: 'serviceAccount'
skipTLSVerify: true
skipMetricsLookup: true
serviceAccountToken: ABC123
caData: LS0tLS1CRUdJTiBDRVJUSUZJQ0...
```

For example, this would generate a link to the `Default` cluster.

If you want to link to Weave GitOps Enterprise, you will need to provide the
name and namespace of the cluster you want to query, for example:

```yaml
kubernetes:
serviceLocatorMethod:
type: 'multiTenant'
clusterLocatorMethods:
- type: 'config'
clusters:
- url: https://192.168.0.1:8000
name: test-ns/name
authProvider: 'serviceAccount'
skipTLSVerify: true
skipMetricsLookup: true
serviceAccountToken: ABC123
caData: LS0tLS1CRUdJTiBDRVJUSUZJQ0...
```

## Verification

For the resources where we display a Verification status, if the Flux resource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,30 @@ import { renderHook } from '@testing-library/react-hooks';
import React, { PropsWithChildren } from 'react';
import { useWeaveFluxDeepLink } from './useWeaveFluxDeepLink';
import { GitRepository, HelmRelease, OCIRepository } from '../objects';
import * as unverifiedGitRepository from '../__fixtures__/unverified_git_repository.json';
import * as unverifiedOCIRepository from '../__fixtures__/unverified_oci_repository.json';


const testHelmRelease = new HelmRelease({
clusterName: 'Default',
payload:
'{"apiVersion":"helm.toolkit.fluxcd.io/v2beta1","kind":"HelmRelease","metadata":{"annotations":{"metadata.weave.works/test":"value"},"creationTimestamp":"2023-05-25T14:14:46Z","finalizers":["finalizers.fluxcd.io"],"generation":5,"name":"normal","namespace":"default","resourceVersion":"1","uid":"82231842-2224-4f22-8576-5babf08d746d"}}',
});

const testGitRepository = new GitRepository({
clusterName: 'Default',
payload:
'{"apiVersion":"source.toolkit.fluxcd.io/v1","kind":"GitRepository","metadata":{"creationTimestamp":"2023-06-22T17:58:23Z","finalizers":["finalizers.fluxcd.io"],"generation":1,"name":"podinfo","namespace":"default","resourceVersion":"137468","uid":"068ec137-b2a0-4b35-90ea-4e9a8a2fe5f6"},"spec":{"interval":"1m","ref":{"branch":"master"},"timeout":"60s","url":"https://github.com/stefanprodan/podinfo"},"status":{"artifact":{"digest":"sha256:f1e2d4a8244772c47d5e10b38768acec57dc404d6409464c15d2eb8c84b28b51","lastUpdateTime":"2023-06-22T17:58:24Z","path":"gitrepository/default/podinfo/e06a5517daf5ac8c5ba74a97135499e40624885a.tar.gz","revision":"master@sha1:e06a5517daf5ac8c5ba74a97135499e40624885a","size":80053,"url":"http://source-controller.flux-system.svc.cluster.local./gitrepository/default/podinfo/e06a5517daf5ac8c5ba74a97135499e40624885a.tar.gz"},"conditions":[{"lastTransitionTime":"2023-06-23T06:58:24Z","message":"stored artifact for revision \'master@sha1:e06a5517daf5ac8c5ba74a97135499e40624885a\'","observedGeneration":1,"reason":"Succeeded","status":"True","type":"Ready"},{"lastTransitionTime":"2023-06-22T17:58:24Z","message":"stored artifact for revision \'master@sha1:e06a5517daf5ac8c5ba74a97135499e40624885a\'","observedGeneration":1,"reason":"Succeeded","status":"True","type":"ArtifactInStorage"}],"observedGeneration":1}}',
JSON.stringify(unverifiedGitRepository),
});

const testOCIRepository = new OCIRepository({
payload: `{"apiVersion":"source.toolkit.fluxcd.io/v1beta2","kind":"OCIRepository","metadata":{"creationTimestamp":"2023-06-23T07:50:47Z","finalizers":["finalizers.fluxcd.io"],"generation":1,"name":"podinfo","namespace":"default","resourceVersion":"143955","uid":"1ec54278-ed2d-4f31-9bb0-39dc7163730e"},"spec":{"interval":"5m","provider":"generic","timeout":"60s","url":"oci://ghcr.io/stefanprodan/manifests/podinfo","verify":{"provider":"cosign"}},"status":{"artifact":{"digest":"sha256:62df151eb3714d9dfa943c7d88192d72466bffa268b25595f85530b793f77524","lastUpdateTime":"2023-06-23T07:50:53Z","metadata":{"org.opencontainers.image.created":"2023-05-03T14:30:58Z","org.opencontainers.image.revision":"6.3.6/073f1ec5aff930bd3411d33534e91cbe23302324","org.opencontainers.image.source":"https://github.com/stefanprodan/podinfo"},"path":"ocirepository/default/podinfo/sha256:2982c337af6ba98c0e9224a5d7149a19baa9cbedea09b16ae44253682050b6a4.tar.gz","revision":"latest@sha256:2982c337af6ba98c0e9224a5d7149a19baa9cbedea09b16ae44253682050b6a4","size":1071,"url":"http://source-controller.flux-system.svc.cluster.local./ocirepository/default/podinfo/sha256:2982c337af6ba98c0e9224a5d7149a19baa9cbedea09b16ae44253682050b6a4.tar.gz"},"conditions":[{"lastTransitionTime":"2023-06-23T07:50:53Z","message":"stored artifact for digest 'latest@sha256:2982c337af6ba98c0e9224a5d7149a19baa9cbedea09b16ae44253682050b6a4'","observedGeneration":1,"reason":"Succeeded","status":"True","type":"Ready"},{"lastTransitionTime":"2023-06-23T07:50:53Z","message":"stored artifact for digest 'latest@sha256:2982c337af6ba98c0e9224a5d7149a19baa9cbedea09b16ae44253682050b6a4'","observedGeneration":1,"reason":"Succeeded","status":"True","type":"ArtifactInStorage"}],"observedGeneration":1,"url":"http://source-controller.flux-system.svc.cluster.local./ocirepository/default/podinfo/latest.tar.gz"}}`,
clusterName: 'demo-cluster',
payload:
JSON.stringify(unverifiedOCIRepository),
});

let gitOpsUrl: string | undefined;

const mockConfigApi = {
getOptionalString: jest.fn(() => gitOpsUrl),
} as Partial<ConfigApi>;
Expand Down Expand Up @@ -47,7 +57,7 @@ describe('useWeaveFluxDeepLink', () => {
},
);
expect(result.current).toBe(
'https://example.com/helm_release/details?clusterName=&name=normal&namespace=default',
'https://example.com/helm_release/details?clusterName=Default&name=normal&namespace=default',
);
});

Expand All @@ -61,7 +71,7 @@ describe('useWeaveFluxDeepLink', () => {
},
);
expect(result.current).toBe(
'https://example.com/git_repo/details?clusterName=&name=podinfo&namespace=default',
'https://example.com/git_repo/details?clusterName=Default&name=podinfo&namespace=backstage',
);
});

Expand All @@ -75,7 +85,7 @@ describe('useWeaveFluxDeepLink', () => {
},
);
expect(result.current).toBe(
'https://example.com/oci/details?clusterName=&name=podinfo&namespace=default',
'https://example.com/oci/details?clusterName=demo-cluster&name=testing&namespace=default',
);
});
});
Expand All @@ -91,7 +101,7 @@ describe('useWeaveFluxDeepLink', () => {
},
);
expect(result.current).toBe(
'https://example.com/helm_release/details?clusterName=&name=normal&namespace=default',
'https://example.com/helm_release/details?clusterName=Default&name=normal&namespace=default',
);
});
});
Expand All @@ -107,4 +117,26 @@ describe('useWeaveFluxDeepLink', () => {
expect(result.current).toBeUndefined();
});
});

describe('when the cluster name has a namespace name/test-ns', () => {
it('is correctly URL encoded into the request URL', async () => {
gitOpsUrl = 'https://example.com/';

const helmRelease = new HelmRelease({
clusterName: 'demo-ns/test-cluster',
payload:
'{"apiVersion":"helm.toolkit.fluxcd.io/v2beta1","kind":"HelmRelease","metadata":{"annotations":{"metadata.weave.works/test":"value"},"creationTimestamp":"2023-05-25T14:14:46Z","finalizers":["finalizers.fluxcd.io"],"generation":5,"name":"normal","namespace":"default","resourceVersion":"1","uid":"82231842-2224-4f22-8576-5babf08d746d"}}',
});


const { result } = renderHook(
() => useWeaveFluxDeepLink(helmRelease),
{
wrapper,
},
);
expect(result.current).toBe(
'https://example.com/helm_release/details?clusterName=demo-ns%2Ftest-cluster&name=normal&namespace=default',
); });
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { configApiRef, useApi } from '@backstage/core-plugin-api';

import {
FluxObject,
GitRepository,
Expand All @@ -8,18 +9,17 @@ import {
OCIRepository,
} from '../objects';


const typedUrl = (baseUrl: string, a: FluxObject, type: string): string => {
const queryStringData = {
clusterName: a.clusterName,
name: a.name,
namespace: a.namespace,
};

const queryString = Object.entries(queryStringData)
.map(([key, value]) => `${key}=${value}`)
.join('&');
const searchParams = new URLSearchParams(queryStringData);

return `${baseUrl.replace(/\/$/, '')}/${type}/details?${queryString}`;
return `${baseUrl.replace(/\/$/, '')}/${type}/details?${searchParams.toString()}`;
};

export const useWeaveFluxDeepLink = (
Expand Down

0 comments on commit d6a8c9f

Please sign in to comment.