Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match Harvester community repo check via regex #13206

Merged
merged 1 commit into from
Jan 27, 2025
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
2 changes: 1 addition & 1 deletion pkg/harvester-manager/l10n/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ harvesterManager:
prompt-standard-user: Please contact your system administrator to install the latest Harvester UI Extension, if any
missingVersion:
warning: "Could not find a compatible version"
prompt: "Please update Rancher to get the latest compatible version of the Harvester UI extension"
prompt: "Please update Rancher to get the latest compatible version of the Harvester UI extension or try to install it manually"
prompt-standard-user: Please contact your system administrator
error:
warning: "Warning, Harvester UI extension automatic installation failed"
Expand Down
49 changes: 37 additions & 12 deletions pkg/harvester-manager/list/harvesterhci.io.management.cluster.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import { allHash } from '@shell/utils/promise';
import { NAME as APP_PRODUCT } from '@shell/config/product/apps';
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
import { UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
import { HARVESTER_CHART, HARVESTER_COMMUNITY_REPO, HARVESTER_RANCHER_REPO } from '../types';
import { HARVESTER_CHART, HARVESTER_COMMUNITY_REPO, HARVESTER_RANCHER_REPO, communityRepoRegexes } from '../types';
import {
getLatestExtensionVersion,
getHelmRepository,
ensureHelmRepository,
getHelmRepositoryExact,
getHelmRepositoryMatch,
createHelmRepository,
refreshHelmRepository,
installHelmChart,
waitForUIExtension,
Expand Down Expand Up @@ -102,9 +103,9 @@ export default {
},

watch: {
async harvesterRepository(value) {
if (value) {
await refreshHelmRepository(this.$store, HARVESTER_REPO.spec.gitRepo, HARVESTER_REPO.spec.gitBranch);
async harvesterRepository(neu) {
if (neu) {
await refreshHelmRepository(this.$store, neu.spec.gitRepo || neu.spec.url);

if (this.harvester.extension) {
await this.setHarvesterUpdateVersion();
Expand Down Expand Up @@ -212,7 +213,11 @@ export default {
methods: {
async getHarvesterRepository() {
try {
return await getHelmRepository(this.$store, HARVESTER_REPO.spec.gitRepo, HARVESTER_REPO.spec.gitBranch);
if (isRancherPrime()) {
return await getHelmRepositoryExact(this.$store, HARVESTER_REPO.gitRepo);
} else {
return await getHelmRepositoryMatch(this.$store, communityRepoRegexes);
Copy link
Member Author

@torchiaf torchiaf Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-Prime users can manually add Harvester repository. The Harvester repo should match one of urls below:

Git repo url:

  • https://github.com/harvester/harvester-ui-extension
  • https://github.com/harvester/harvester-ui-extension/
  • https://github.com/{forker-repository}/harvester-ui-extension
  • https://github.com/{forker-repository}/harvester-ui-extension/

GitHub deployments url:

  • https://harvester.github.io/harvester-ui-extension

}
} catch (error) {
this.harvesterRepositoryError = true;
}
Expand All @@ -234,13 +239,15 @@ export default {
let installed = false;

try {
const harvesterRepo = await ensureHelmRepository(this.$store, HARVESTER_REPO.spec.gitRepo, HARVESTER_REPO.metadata.name, HARVESTER_REPO.spec.gitBranch);
if (!this.harvesterRepository) {
this.harvesterRepository = await createHelmRepository(this.$store, HARVESTER_REPO.metadata.name, HARVESTER_REPO.gitRepo, HARVESTER_REPO.gitBranch);
}

/**
* Server issue
* It needs to refresh the HelmRepository because the server can have a previous one in the cache.
*/
await refreshHelmRepository(this.$store, HARVESTER_REPO.spec.gitRepo, HARVESTER_REPO.spec.gitBranch);
await refreshHelmRepository(this.$store, this.harvesterRepository.spec.gitRepo || this.harvesterRepository.spec.url);

this.harvesterInstallVersion = await getLatestExtensionVersion(this.$store, HARVESTER_CHART.name, this.rancherVersion, this.kubeVersion);

Expand All @@ -250,7 +257,16 @@ export default {
return;
}

await installHelmChart(harvesterRepo, { ...HARVESTER_CHART, version: this.harvesterInstallVersion }, {}, UI_PLUGIN_NAMESPACE, 'install');
await installHelmChart(
this.harvesterRepository,
{
...HARVESTER_CHART,
version: this.harvesterInstallVersion
},
{},
UI_PLUGIN_NAMESPACE,
'install'
);

const extension = await waitForUIExtension(this.$store, HARVESTER_CHART.name);

Expand All @@ -272,7 +288,7 @@ export default {

try {
if (this.harvester.missingRepository) {
this.harvesterRepository = await ensureHelmRepository(this.$store, HARVESTER_REPO.spec.gitRepo, HARVESTER_REPO.metadata.name, HARVESTER_REPO.spec.gitBranch);
this.harvesterRepository = await createHelmRepository(this.$store, HARVESTER_REPO.metadata.name, HARVESTER_REPO.gitRepo, HARVESTER_REPO.gitBranch);

await this.setHarvesterUpdateVersion();
}
Expand All @@ -283,7 +299,16 @@ export default {
return;
}

await installHelmChart(this.harvesterRepository, { ...HARVESTER_CHART, version: this.harvesterUpdateVersion }, {}, UI_PLUGIN_NAMESPACE, 'upgrade');
await installHelmChart(
this.harvesterRepository,
{
...HARVESTER_CHART,
version: this.harvesterUpdateVersion
},
{},
UI_PLUGIN_NAMESPACE,
'upgrade'
);

const extension = await waitForUIExtension(this.$store, HARVESTER_CHART.name);

Expand Down
25 changes: 11 additions & 14 deletions pkg/harvester-manager/types.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { UI_PLUGINS_REPOS } from '@shell/config/uiplugins';

export const communityRepoRegexes = [
/^https:\/\/github\.com\/.*\/harvester-ui-extension+/g,
/^https:\/\/.*\.github\.io\/harvester-ui-extension+/g,
];

export const HARVESTER_CHART = {
name: 'harvester',
version: '',
Expand All @@ -8,21 +13,13 @@ export const HARVESTER_CHART = {
};

export const HARVESTER_COMMUNITY_REPO = {
type: 'catalog.cattle.io.clusterrepo',
metadata: { name: 'harvester' },
spec: {
clientSecret: null,
gitRepo: 'https://github.com/harvester/harvester-ui-extension',
gitBranch: 'gh-pages'
}
metadata: { name: 'harvester' },
gitRepo: 'https://github.com/harvester/harvester-ui-extension',
gitBranch: 'gh-pages',
};

export const HARVESTER_RANCHER_REPO = {
type: 'catalog.cattle.io.clusterrepo',
metadata: { name: 'rancher' },
spec: {
clientSecret: null,
gitRepo: UI_PLUGINS_REPOS.OFFICIAL.URL,
gitBranch: UI_PLUGINS_REPOS.OFFICIAL.BRANCH,
}
metadata: { name: 'rancher' },
gitRepo: UI_PLUGINS_REPOS.OFFICIAL.URL,
gitBranch: UI_PLUGINS_REPOS.OFFICIAL.BRANCH,
};
100 changes: 56 additions & 44 deletions shell/utils/uiplugins.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { matchesSomeRegex } from '@shell/utils/string';
import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
import { CATALOG } from '@shell/config/types';
import { UI_PLUGIN_BASE_URL, isSupportedChartVersion } from '@shell/config/uiplugins';
Expand Down Expand Up @@ -158,37 +159,56 @@ export async function installHelmChart(repo: any, chart: any, values: any = {},
}

/**
* Get the Helm repository object
*
* @param store Vue store
* @param url The url of the Helm repository
* @param branch The branch of the Helm repository
* @param url Repository Url
* @returns HelmRepository
*/
export async function getHelmRepository(store: any, url: string, branch?: string): Promise<HelmRepository> {
export async function getHelmRepositoryExact(store: any, url: string): Promise<HelmRepository> {
return getHelmRepository(store, (repository: any) => {
const target = repository.spec?.gitRepo || repository.spec?.url;

return target === url;
});
}

/**
*
* @param store Vue store
* @param urlRegexes Regex to match a community repository
* @returns HelmRepository
*/
export async function getHelmRepositoryMatch(store: any, urlRegexes: string[]): Promise<HelmRepository> {
return getHelmRepository(store, (repository: any) => {
const target = repository.spec?.gitBranch ? repository.spec?.gitRepo : repository.spec?.url;

return matchesSomeRegex(target, urlRegexes);
});
}

/**
*
* @param store Vue store
* @param matchFn Match function for repository's urls
* @returns HelmRepository
*/
async function getHelmRepository(store: any, matchFn: (repository: any) => boolean): Promise<HelmRepository> {
if (store.getters['management/schemaFor'](CATALOG.CLUSTER_REPO)) {
const repos = await store.dispatch('management/findAll', { type: CATALOG.CLUSTER_REPO, opt: { force: true, watch: false } });

return repos.find((r: any) => {
const target = branch ? r.spec?.gitRepo : r.spec?.url ;

return target === url;
});
return repos.find(matchFn);
} else {
throw new Error('No permissions');
}
}

/**
* Refresh the Helm repository
* Ensures that we find the latest extension versions
*
* @param store Vue store
* @param gitRepo Extension Repository url
* @param gitBranch Extension Repository branch
* @param url Repository Url
*/
export async function refreshHelmRepository(store: any, gitRepo: string, gitBranch: string): Promise<void> {
const repository = await getHelmRepository(store, gitRepo, gitBranch);
export async function refreshHelmRepository(store: any, url: string): Promise<void> {
const repository = await getHelmRepositoryExact(store, url);

const now = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');

Expand All @@ -202,40 +222,32 @@ export async function refreshHelmRepository(store: any, gitRepo: string, gitBran
}

/**
* Ensure the required Helm Repository exits, if it does not, add it with the specified name
*
* Wait until the newly added repository has been downloaded
*
* @param store Vue store
* @param url The url of the Helm repository
* @param name The name of the cluster repository
* @param branch The branch of the Helm repository
* @returns HelmRepository object
* @param name Repository name
* @param url Repository Url
* @param branch Repository Branch
* @returns HelmRepository
*/
export async function ensureHelmRepository(store: any, url: string, name: string, branch?: string): Promise<HelmRepository> {
let helmRepo = await getHelmRepository(store, url, branch);

// Add the Helm repository, if it is not there
if (!helmRepo) {
const data = {
type: CATALOG.CLUSTER_REPO,
metadata: { name },
spec: {} as any
};

if (branch) {
data.spec.gitBranch = branch;
data.spec.gitRepo = url;
} else {
data.spec.url = url;
}

// Create a model for the new repository and save it
const repo = await store.dispatch('management/create', data);
export async function createHelmRepository(store: any, name: string, url: string, branch?: string): Promise<HelmRepository> {
const data = {
type: CATALOG.CLUSTER_REPO,
metadata: { name },
spec: {} as any
};

helmRepo = await repo.save();
if (branch) {
data.spec.gitBranch = branch;
data.spec.gitRepo = url;
} else {
data.spec.url = url;
}

// Create a model for the new repository and save it
const repo = await store.dispatch('management/create', data);

const helmRepo = await repo.save();

// Poll the repository until it says it has been downloaded
let fetched = false;
let tries = 0;
Expand Down Expand Up @@ -278,7 +290,7 @@ export async function ensureHelmRepository(store: any, url: string, name: string
* Get the given Helm Chart from the specified Helm Repository
*
* @param store Vue store
* @param repository Helm Repository
* @param repository Repository Url
* @param chartName Helm Chart name
* @returns Helm Chart
*/
Expand Down
Loading