@spotinst/spinnaker-deck
Version:
Spinnaker-Deck service, forked with support to Spotinst
236 lines (203 loc) • 7.91 kB
text/typescript
import { IQResolveReject } from 'angular';
import { chain, intersection, uniq, zipObject } from 'lodash';
import { $log, $q } from 'ngimport';
import { Observable } from 'rxjs';
import { REST } from 'core/api/ApiService';
import { Application } from 'core/application/application.model';
import { SETTINGS } from 'core/config/settings';
import { ILoadBalancer, IServerGroup } from 'core/domain';
import { CloudProviderRegistry } from '../cloudProvider/CloudProviderRegistry';
export interface IRegion {
account?: string;
availabilityZones?: string[];
deprecated?: boolean;
endpoint?: string;
faultDomains?: string[];
name: string;
preferredZones?: string[];
}
export interface IAccount {
accountId: string;
name: string;
requiredGroupMembership: string[];
type: string;
}
export interface IArtifactAccount {
name: string;
types: string[];
}
export interface IAccountDetails extends IAccount {
accountType: string;
authorized: boolean;
awsAccount?: string;
awsVpc?: string;
bastionHost?: string;
challengeDestructiveActions: boolean;
cloudProvider: string;
defaultKeyPair?: string;
environment: string;
primaryAccount: boolean;
regions: IRegion[];
registry?: string;
namespaces?: string[];
spinnakerKindMap?: { [k: string]: string };
}
export interface IAggregatedAccounts {
[credentials: string]: IAccountDetails;
}
export interface IZone {
[key: string]: string[];
}
export interface IAccountZone {
[key: string]: IZone;
}
export class AccountService {
public static accounts$: Observable<IAccountDetails[]>;
public static providers$: Observable<string[]>;
public static initialize(): void {
this.accounts$ = Observable.defer(() => {
const promise = REST('/credentials').useCache().query({ expand: true }).get();
return Observable.fromPromise<IAccountDetails[]>(promise);
})
.publishReplay(1)
.refCount();
this.providers$ = AccountService.accounts$.map((accounts: IAccountDetails[]) => {
const providersFromAccounts: string[] = uniq(accounts.map((account) => account.type));
return intersection(providersFromAccounts, CloudProviderRegistry.listRegisteredProviders());
});
}
public static challengeDestructiveActions(account: string): PromiseLike<boolean> {
return $q((resolve: IQResolveReject<boolean>) => {
if (account) {
this.getAccountDetails(account)
.then((details: IAccountDetails) => {
if (details) {
resolve(details.challengeDestructiveActions);
} else {
resolve(false);
}
})
.catch(() => resolve(false));
} else {
resolve(false);
}
});
}
public static getArtifactAccounts(): PromiseLike<IArtifactAccount[]> {
return REST('/artifacts/credentials').useCache().get();
}
public static getAccountDetails(account: string): PromiseLike<IAccountDetails> {
return this.listAllAccounts().then((accounts) => accounts.find((a) => a.name === account));
}
public static getAllAccountDetailsForProvider(provider: string): PromiseLike<IAccountDetails[]> {
return this.listAccounts(provider).catch((error: any) => {
$log.warn(`Failed to load accounts for provider "${provider}"; exception:`, error);
return [];
});
}
public static getAvailabilityZonesForAccountAndRegion(
provider: string,
account: string,
region: string,
): PromiseLike<string[]> {
return this.getPreferredZonesByAccount(provider).then(
(result: IAccountZone) => (result[account] && result[account][region]) || [],
);
}
public static getCredentialsKeyedByAccount(provider: string = null): PromiseLike<IAggregatedAccounts> {
return this.listAllAccounts(provider).then((accounts: IAccountDetails[]) => {
const names: string[] = accounts.map((account: IAccount) => account.name);
return zipObject<IAccountDetails, IAggregatedAccounts>(names, accounts);
});
}
public static getPreferredZonesByAccount(provider: string): PromiseLike<IAccountZone> {
const preferred: IAccountZone = {};
return this.getAllAccountDetailsForProvider(provider).then((accounts: IAccountDetails[]) => {
accounts.forEach((account: IAccountDetails) => {
preferred[account.name] = {};
account.regions.forEach((region: IRegion) => {
let zones: string[] = region.availabilityZones;
if (region.preferredZones) {
zones = intersection(region.preferredZones, region.availabilityZones);
}
preferred[account.name][region.name] = zones;
});
});
return preferred;
});
}
public static getRegionsForAccount(account: string): PromiseLike<IRegion[]> {
return this.getAccountDetails(account).then((details: IAccountDetails) => (details ? details.regions : []));
}
public static getUniqueAttributeForAllAccounts(provider: string, attribute: string): PromiseLike<string[]> {
return this.getCredentialsKeyedByAccount(provider).then((credentials: IAggregatedAccounts) => {
return chain(credentials)
.map(attribute)
.flatten()
.compact()
.map((region: IRegion) => region.name || region)
.uniq()
.value() as string[];
});
}
public static listAllAccounts(provider: string = null): PromiseLike<IAccountDetails[]> {
return $q
.when(this.accounts$.toPromise())
.then((accounts: IAccountDetails[]) => accounts.filter((account) => !provider || account.type === provider));
}
public static listAccounts(provider: string = null): PromiseLike<IAccountDetails[]> {
return this.listAllAccounts(provider).then((accounts) =>
accounts.filter((account) => account.authorized !== false),
);
}
public static applicationAccounts(application: Application = null): PromiseLike<IAccountDetails[]> {
return $q.all([this.listProviders(application), this.listAccounts()]).then(([providers, accounts]) => {
return providers.reduce((memo, p) => {
return memo.concat(accounts.filter((acc) => acc.cloudProvider === p));
}, [] as IAccountDetails[]);
});
}
public static listProviders$(application: Application = null): Observable<string[]> {
return this.providers$
.map((available: string[]) => {
if (!application) {
return available;
} else if (application.attributes.cloudProviders.length) {
return intersection(available, application.attributes.cloudProviders);
} else if (SETTINGS.defaultProviders) {
return intersection(available, SETTINGS.defaultProviders);
} else {
return available;
}
})
.map((results) => results.sort());
}
public static listProviders(application: Application = null): PromiseLike<string[]> {
return $q.when(this.listProviders$(application).toPromise());
}
public static getAccountForInstance(
cloudProvider: string,
instanceId: string,
app: Application,
): PromiseLike<string> {
return app.ready().then(() => {
const serverGroups = app.getDataSource('serverGroups').data as IServerGroup[];
const loadBalancers = app.getDataSource('loadBalancers').data as ILoadBalancer[];
const loadBalancerServerGroups = loadBalancers
.map((lb) => lb.serverGroups)
.reduce((acc, sg) => acc.concat(sg), []);
const hasInstance = (obj: IServerGroup | ILoadBalancer) => {
return (
obj.cloudProvider === cloudProvider && (obj.instances || []).some((instance) => instance.id === instanceId)
);
};
const all: Array<IServerGroup | ILoadBalancer> = []
.concat(serverGroups)
.concat(loadBalancers)
.concat(loadBalancerServerGroups);
const found = all.find(hasInstance);
return found && found.account;
});
}
}
AccountService.initialize();