@grucloud/provider-azure
Version:
The GruCloud Azure provider
864 lines (857 loc) • 26.9 kB
JavaScript
const assert = require("assert");
const {
pipe,
eq,
tryCatch,
get,
tap,
pick,
map,
assign,
omit,
filter,
not,
and,
switchCase,
} = require("rubico");
const {
defaultsDeep,
pluck,
isEmpty,
find,
includes,
first,
when,
unless,
flatten,
callProp,
} = require("rubico/x");
const fs = require("fs").promises;
const path = require("path");
const { getField } = require("@grucloud/core/ProviderCommon");
const { omitIfEmpty, replaceWithName } = require("@grucloud/core/Common");
const {
findDependenciesResourceGroup,
findDependenciesUserAssignedIdentity,
configDefaultGeneric,
assignDependenciesId,
} = require("../AzureCommon");
const group = "Compute";
const virtualMachineDependenciesCommon = {
resourceGroup: {
type: "ResourceGroup",
group: "Resources",
name: "resourceGroupName",
parent: true,
},
disks: {
type: "Disk",
group: "Compute",
list: true,
excludeDefaultDependencies: true,
createOnly: true,
},
managedIdentities: {
type: "UserAssignedIdentity",
group: "ManagedIdentity",
createOnly: true,
list: true,
},
sshPublicKeys: {
type: "SshPublicKey",
group: "Compute",
list: true,
createOnly: true,
},
galleryImage: {
type: "GalleryImage",
group: "Compute",
createOnly: true,
},
networkSecurityGroups: {
type: "NetworkSecurityGroup",
group: "Network",
createOnly: true,
list: true,
},
proximityPlacementGroup: {
type: "ProximityPlacementGroup",
group: "Compute",
createOnly: true,
},
dedicatedHostGroup: {
type: "DedicatedHostGroup",
group: "Compute",
createOnly: true,
},
capacityReservationGroup: {
type: "CapacityReservationGroup",
group: "Compute",
createOnly: true,
},
};
const filterVirtualMachineProperties =
({ config }) =>
({ lives, providerConfig }) =>
pipe([
tap(() => {
assert(lives);
assert(config);
}),
pick([
"hardwareProfile",
"osProfile",
"storageProfile",
"diagnosticsProfile",
"networkProfile",
]),
tap((params) => {
assert(true);
}),
omit([
"storageProfile.imageReference.exactVersion",
"osProfile.requireGuestProvisionSignal",
]),
assign({
storageProfile: pipe([
get("storageProfile"),
when(
get("imageReference"),
assign({
imageReference: pipe([
get("imageReference"),
tap((params) => {
assert(true);
}),
when(
get("id"),
assign({
id: pipe([
get("id"),
callProp(
"replace",
config.subscriptionId,
"${config.subscriptionId}"
),
(id) => () => "`" + id + "`",
]),
})
),
]),
})
),
assign({
osDisk: pipe([
get("osDisk"),
assign({
managedDisk: pipe([
get("managedDisk"),
omit(["id"]),
when(
get("diskEncryptionSet"),
assign({
diskEncryptionSet: pipe([
get("diskEncryptionSet"),
assignDependenciesId({
group: "Compute",
type: "DiskEncryptionSet",
providerConfig,
lives,
}),
]),
})
),
]),
}),
]),
dataDisks: pipe([
get("dataDisks", []),
map(
assign({
managedDisk: pipe([
get("managedDisk"),
assignDependenciesId({
group: "Compute",
type: "Disk",
providerConfig,
lives,
}),
]),
})
),
]),
}),
]),
}),
assign({
networkProfile: pipe([
get("networkProfile"),
when(
get("networkInterfaces"),
assign({
networkInterfaces: pipe([
get("networkInterfaces"),
map(
assignDependenciesId({
group: "Network",
type: "NetworkInterface",
providerConfig,
lives,
})
),
]),
})
),
when(
get("networkInterfaceConfigurations"),
assign({
networkInterfaceConfigurations: pipe([
get("networkInterfaceConfigurations"),
map(
assign({
properties: pipe([
get("properties"),
when(
get("networkSecurityGroup"),
assign({
networkSecurityGroup: pipe([
get("networkSecurityGroup"),
assignDependenciesId({
group: "Network",
type: "NetworkSecurityGroup",
providerConfig,
lives,
}),
]),
})
),
assign({
ipConfigurations: pipe([
get("ipConfigurations"),
map(
pipe([
assign({
properties: pipe([
get("properties"),
when(
get("loadBalancerBackendAddressPools"),
assign({
loadBalancerBackendAddressPools: pipe([
get("loadBalancerBackendAddressPools"),
map(
assign({
id: pipe([
get("id"),
replaceWithName({
groupType:
"Network::LoadBalancer",
providerConfig,
lives,
path: "id",
withSuffix: true,
}),
]),
})
),
]),
})
),
when(
get(
"applicationGatewayBackendAddressPools"
),
assign({
applicationGatewayBackendAddressPools:
pipe([
get(
"applicationGatewayBackendAddressPools"
),
map(
assignDependenciesId({
group: "Network",
type: "ApplicationGateway",
providerConfig,
lives,
withSuffix: true,
})
),
]),
})
),
when(
get("loadBalancerInboundNatPools"),
assign({
loadBalancerInboundNatPools: pipe([
get("loadBalancerInboundNatPools"),
map(
assign({
id: pipe([
get("id"),
replaceWithName({
groupType:
"Network::LoadBalancer",
providerConfig,
lives,
path: "id",
withSuffix: true,
}),
]),
})
),
]),
})
),
assign({
subnet: pipe([
get("subnet"),
assignDependenciesId({
group: "Network",
type: "Subnet",
providerConfig,
lives,
}),
]),
}),
]),
}),
])
),
]),
}),
]),
})
),
]),
})
),
]),
osProfile: pipe([
get("osProfile"),
when(
get("linuxConfiguration.ssh.publicKeys"),
assign({
linuxConfiguration: pipe([
get("linuxConfiguration"),
assign({
ssh: pipe([
get("ssh"),
assign({
publicKeys: pipe([
get("publicKeys", []),
map(
assign({
keyData: ({ keyData }) =>
pipe([
() => lives,
find(
and([
eq(
get("groupType"),
"Compute::SshPublicKey"
),
eq(
get("live.properties.publicKey"),
keyData
),
])
),
get("id"),
switchCase([
isEmpty,
() => keyData,
replaceWithName({
groupType: "Compute::SshPublicKey",
path: "live.properties.publicKey",
providerConfig,
lives,
}),
]),
])(),
})
),
]),
}),
]),
}),
]),
})
),
]),
}),
omitIfEmpty(["networkProfile.networkInterfaces"]),
omitIfEmpty([
"osProfile.secrets",
"storageProfile.dataDisks",
"networkProfile",
]),
]);
const VirtualMachineDependencySshPublicKey = ({
publicKeysPath,
live,
lives,
config,
}) => ({
type: "SshPublicKey",
group: "Compute",
ids: pipe([
() => live,
get(publicKeysPath),
map(({ keyData }) =>
pipe([
lives.getByType({
providerName: config.providerName,
type: "SshPublicKey",
group: "Compute",
}),
find(eq(get("live.properties.publicKey"), keyData)),
get("id"),
])()
),
])(),
});
exports.fnSpecs = ({ config }) =>
pipe([
() => [
{
type: "SshPublicKey",
configDefault: ({
properties,
dependencies,
config,
spec,
programOptions,
}) =>
pipe([
() => properties,
when(get("publicKeyFile"), (props) =>
pipe([
() => properties,
get("publicKeyFile"),
(publicKeyFile) =>
path.resolve(programOptions.workingDirectory, publicKeyFile),
tryCatch(
(fileName) => fs.readFile(fileName, "utf-8"),
pipe([() => undefined])
),
(publicKey) =>
pipe([
() => props,
defaultsDeep({
properties: {
publicKey,
},
}),
])(),
])()
),
defaultsDeep(
configDefaultGeneric({
properties,
dependencies,
config,
spec,
})
),
omit(["publicKeyFile"]),
tap((params) => {
assert(true);
}),
])(),
},
{
type: "Disk",
managedByOther:
({ lives, config }) =>
(live) =>
pipe([
() => live.managedBy,
lives.getById({
type: "VirtualMachine",
group: "Compute",
providerName: config.providerName,
}),
get("live.properties.storageProfile.osDisk.managedDisk.id", ""),
eq(
callProp("toUpperCase"),
pipe([() => live, get("id"), callProp("toUpperCase")])()
),
])(),
},
{
type: "DiskEncryptionSet",
//TODO remove
pickPropertiesCreate: [
"name",
"properties.encryptionType",
"properties.rotationToLatestKeyVersionEnabled",
],
findDependencies: ({ live, lives }) => [
findDependenciesResourceGroup({ live, lives, config }),
{
type: "Vault",
group: "KeyVault",
ids: [
pipe([() => live, get("properties.activeKey.sourceVault.id")])(),
],
},
{
type: "Key",
group: "KeyVault",
ids: [
pipe([
() => live,
get("properties.activeKey.keyUrl"),
(keyUrl) =>
pipe([
lives.getByType({
type: "Key",
group: "KeyVault",
providerName: config.providerName,
}),
find(
pipe([
get("live.properties.keyUri"),
(liveKeyUri) =>
pipe([() => keyUrl, includes(liveKeyUri)])(),
])
),
get("id"),
])(),
])(),
],
},
],
configDefault: ({ properties, dependencies, config, spec }) =>
pipe([
tap(() => {
assert(dependencies.vault);
assert(dependencies.key);
}),
() => properties,
defaultsDeep({
identity: {
type: "SystemAssigned",
},
properties: {
activeKey: {
sourceVault: {
id: getField(dependencies.vault, "id"),
},
keyUrl: pipe([
() => getField(dependencies.key, "versions"),
first,
get("kid", "<< Not available yet >>"),
])(),
},
},
}),
defaultsDeep(
configDefaultGeneric({
properties,
dependencies,
config,
spec,
})
),
])(),
},
{
type: "VirtualMachineScaleSetVM",
ignoreResource: () => () => true,
managedByOther: () => () => true,
},
{
type: "VirtualMachineScaleSet",
dependencies: {
...virtualMachineDependenciesCommon,
subnets: {
type: "Subnet",
group: "Network",
createOnly: true,
list: true,
},
loadBalancer: {
type: "LoadBalancer",
group: "Network",
createOnly: true,
},
applicationGateways: {
type: "ApplicationGateway",
group: "Network",
createOnly: true,
list: true,
},
},
omitPropertiesExtra: [
"properties.virtualMachineProfile.osProfile.requireGuestProvisionSignal",
],
environmentVariables: [
{
path: "properties.virtualMachineProfile.osProfile.adminPassword",
suffix: "ADMIN_PASSWORD",
},
],
findDependencies: ({ live, lives }) => [
findDependenciesResourceGroup({ live, lives }),
findDependenciesUserAssignedIdentity({ live }),
{
type: "NetworkSecurityGroup",
group: "Network",
ids: pipe([
() => live,
get(
"properties.virtualMachineProfile.networkProfile.networkInterfaceConfigurations"
),
pluck("properties"),
pluck("networkSecurityGroup"),
pluck("id"),
])(),
},
{
type: "Subnet",
group: "Network",
ids: pipe([
() => live,
get(
"properties.virtualMachineProfile.networkProfile.networkInterfaceConfigurations"
),
pluck("properties"),
pluck("ipConfigurations"),
flatten,
pluck("properties"),
pluck("subnet"),
pluck("id"),
])(),
},
VirtualMachineDependencySshPublicKey({
live,
lives,
config,
publicKeysPath:
"properties.virtualMachineProfile.osProfile.linuxConfiguration.ssh.publicKeys",
}),
{
type: "LoadBalancer",
group: "Network",
ids: pipe([
() => live,
get(
"properties.virtualMachineProfile.networkProfile.networkInterfaceConfigurations"
),
pluck("properties"),
pluck("ipConfigurations"),
flatten,
pluck("properties"),
pluck("loadBalancerBackendAddressPools"),
flatten,
pluck("id"),
filter(not(isEmpty)),
map(
pipe([
callProp("split", "/"),
callProp("slice", 0, -2),
callProp("join", "/"),
])
),
])(),
},
{
type: "ApplicationGateway",
group: "Network",
ids: pipe([
() => live,
get(
"properties.virtualMachineProfile.networkProfile.networkInterfaceConfigurations"
),
pluck("properties"),
pluck("ipConfigurations"),
flatten,
pluck("properties"),
pluck("applicationGatewayBackendAddressPools"),
flatten,
pluck("id"),
filter(not(isEmpty)),
map(
pipe([
callProp("split", "/"),
callProp("slice", 0, -2),
callProp("join", "/"),
])
),
])(),
},
//TODO common
{
type: "Disk",
group: "Compute",
ids: [
pipe([
() => live,
get(
"properties.virtualMachineProfile.storageProfile.osDisk.managedDisk.id"
),
])(),
],
},
],
filterLive: (context) =>
pipe([
pick(["name", "sku", "identity.type", "properties", "tags"]),
assign({
properties: pipe([
get("properties"),
omit(["provisioningState", "uniqueId", "timeCreated"]),
assign({
virtualMachineProfile: pipe([
get("virtualMachineProfile"),
filterVirtualMachineProperties({ config })(context),
]),
}),
,
]),
}),
]),
configDefault: ({ properties, dependencies, config, spec }) =>
pipe([
tap(() => {
assert(true);
}),
() => properties,
defaultsDeep(
configDefaultGeneric({ properties, dependencies, config, spec })
),
tap((params) => {
assert(true);
}),
])(),
},
{
// https://docs.microsoft.com/en-us/rest/api/compute/virtual-machines
type: "VirtualMachine",
dependencies: {
...virtualMachineDependenciesCommon,
networkInterfaces: {
type: "NetworkInterface",
group: "Network",
list: true,
createOnly: true,
},
availabilitySet: {
type: "AvailabilitySet",
group: "Compute",
createOnly: true,
},
virtualMachineScaleSet: {
type: "VirtualMachineScaleSet",
group: "Compute",
createOnly: true,
},
virtualMachineScaleSetVm: {
type: "VirtualMachineScaleSetVM",
group: "Compute",
createOnly: true,
},
},
environmentVariables: [
{
path: "properties.osProfile.adminPassword",
suffix: "ADMIN_PASSWORD",
},
],
propertiesDefault: {
properties: {
osProfile: {
linuxConfiguration: {
disablePasswordAuthentication: false,
provisionVMAgent: true,
patchSettings: {
patchMode: "ImageDefault",
assessmentMode: "ImageDefault",
},
},
allowExtensionOperations: true,
},
},
},
omitPropertiesExtra: [
"properties.osProfile.requireGuestProvisionSignal",
],
filterLive: (context) =>
pipe([
pick(["name", "tags", "properties", "identity.type"]),
assign({
properties: pipe([
get("properties"),
filterVirtualMachineProperties({ config })(context),
]),
}),
]),
findDependencies: ({ live, lives }) => [
findDependenciesResourceGroup({ live, lives, config }),
findDependenciesUserAssignedIdentity({ live }),
{
type: "NetworkInterface",
group: "Network",
ids: pipe([
() => live,
get("properties.networkProfile.networkInterfaces"),
pluck("id"),
])(),
},
VirtualMachineDependencySshPublicKey({
lives,
live,
config,
publicKeysPath:
"properties.osProfile.linuxConfiguration.ssh.publicKeys",
}),
{
type: "Disk",
group: "Compute",
ids: [
pipe([
() => live,
get("properties.storageProfile.osDisk.managedDisk.id"),
])(),
...pipe([
() => live,
get("properties.storageProfile.dataDisks"),
pluck("managedDisk"),
pluck("id"),
])(),
],
},
],
configDefault: ({ properties, dependencies, config, spec }) =>
pipe([
tap(() => {
assert(
dependencies.networkInterfaces,
"networkInterfaces is missing VirtualMachine"
);
}),
() => properties,
unless(
() => isEmpty(dependencies.networkInterfaces),
defaultsDeep({
properties: {
networkProfile: {
networkInterfaces: pipe([
() => dependencies,
get("networkInterfaces", []),
map((networkInterface) => ({
id: getField(networkInterface, "id"),
})),
])(),
},
},
})
),
defaultsDeep(
configDefaultGeneric({ properties, dependencies, config, spec })
),
])(),
},
],
map(defaultsDeep({ group })),
])();