feat(hostedapp): add hosted Cloudly parent upgrade controls

This commit is contained in:
2026-05-26 21:50:17 +00:00
parent c7a307c9d3
commit 26256c92bd
7 changed files with 434 additions and 7 deletions
@@ -6,6 +6,12 @@ type IHostedAppLifecycleState = plugins.servezoneInterfaces.data.IHostedAppLifec
type IHostedAppUpgradeState = plugins.servezoneInterfaces.data.IHostedAppUpgradeState;
type IHostedAppRuntimeIdentity = plugins.servezoneInterfaces.data.IHostedAppRuntimeIdentity;
interface IHostedAppParentUpgradeResponse {
isHosted: boolean;
unavailableReason?: string;
upgradeState: IHostedAppUpgradeState;
}
type TExtendedServiceData = plugins.servezoneInterfaces.data.IService['data'] & {
hostedAppLifecycle?: IHostedAppLifecycleState;
};
@@ -45,6 +51,89 @@ export class CloudlyHostedAppManager {
);
}
private getParentRuntimeUnavailableReason(): string | undefined {
if (!process.env.SERVEZONE_RUNTIME_URL) {
return 'SERVEZONE_RUNTIME_URL is not configured.';
}
if (!process.env.SERVEZONE_APP_INSTANCE_ID || !process.env.SERVEZONE_APP_CONTROL_TOKEN) {
return 'Hosted app runtime identity is not configured.';
}
return undefined;
}
private getErrorMessage(errorArg: unknown): string {
return errorArg instanceof Error ? errorArg.message : String(errorArg);
}
public async getParentUpgradeStatus(): Promise<IHostedAppParentUpgradeResponse> {
const unavailableReason = this.getParentRuntimeUnavailableReason();
const identity = this.getParentRuntimeIdentity();
const request = this.createParentRuntimeTypedRequest<plugins.servezoneInterfaces.requests.hostedapp.IReq_HostedApp_GetManagedUpgradeStatus>(
'hostedAppGetManagedUpgradeStatus',
);
if (unavailableReason || !identity || !request) {
return {
isHosted: false,
unavailableReason,
upgradeState: { status: 'unknown' },
};
}
try {
const response = await request.fire({ identity });
return {
isHosted: true,
upgradeState: response.upgradeState,
};
} catch (error) {
const message = this.getErrorMessage(error);
return {
isHosted: true,
unavailableReason: message,
upgradeState: {
status: 'unknown',
error: message,
},
};
}
}
public async startParentUpgrade(targetVersionArg?: string): Promise<IHostedAppParentUpgradeResponse> {
const unavailableReason = this.getParentRuntimeUnavailableReason();
const identity = this.getParentRuntimeIdentity();
const request = this.createParentRuntimeTypedRequest<plugins.servezoneInterfaces.requests.hostedapp.IReq_HostedApp_StartManagedUpgrade>(
'hostedAppStartManagedUpgrade',
);
if (unavailableReason || !identity || !request) {
return {
isHosted: false,
unavailableReason,
upgradeState: { status: 'unknown' },
};
}
try {
const response = await request.fire({
identity,
targetVersion: targetVersionArg,
});
return {
isHosted: true,
upgradeState: response.upgradeState,
};
} catch (error) {
const message = this.getErrorMessage(error);
return {
isHosted: true,
unavailableReason: message,
upgradeState: {
status: 'failed',
error: message,
},
};
}
}
public async requestParentInitialAdminBootstrap(): Promise<{
username: string;
password: string;
@@ -332,5 +421,31 @@ export class CloudlyHostedAppManager {
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.hostedapp.IReq_Admin_GetHostedAppParentUpgradeStatus>(
'getHostedAppParentUpgradeStatus',
async (dataArg) => {
await this.passAdminIdentity(dataArg);
return await this.getParentUpgradeStatus();
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.hostedapp.IReq_Admin_StartHostedAppParentUpgrade>(
'startHostedAppParentUpgrade',
async (dataArg) => {
await this.passAdminIdentity(dataArg);
return await this.startParentUpgrade(dataArg.targetVersion);
},
),
);
}
private async passAdminIdentity(dataArg: { identity: plugins.servezoneInterfaces.data.IIdentity }) {
await plugins.smartguard.passGuardsOrReject(dataArg, [
this.cloudlyRef.authManager.adminIdentityGuard,
]);
}
}