@grucloud/provider-google
Version:
The GruCloud Google Cloud Platform provider
379 lines (362 loc) • 10.1 kB
JavaScript
const assert = require("assert");
const {
tap,
map,
pipe,
get,
flatten,
tryCatch,
assign,
omit,
filter,
fork,
or,
switchCase,
} = require("rubico");
const { defaultsDeep, isEmpty, find } = require("rubico/x");
const { differenceWith, isEqual } = require("lodash/fp");
const { GCP_DNS_BASE_URL } = require("./GcpDnsCommon");
const { createAxiosMakerGoogle } = require("../../GoogleCommon");
const {
logError,
axiosErrorToJSON,
getByNameCore,
isDownByIdCore,
} = require("@grucloud/core/Common");
const { retryCallOnError } = require("@grucloud/core/Retry");
const logger = require("@grucloud/core/logger")({
prefix: "GcpDnsManagedZone",
});
const { tos } = require("@grucloud/core/tos");
exports.compareDnsManagedZone = async ({ target, live }) =>
pipe([
tap(() => {
logger.debug(`compareDnsManagedZone ${tos({ target, live })}`);
assert(target.recordSet, "target.recordSet");
assert(live.recordSet, "live.recordSet");
}),
() =>
filterNonDeletableRecords({
targetRecordSet: target.recordSet,
liveRecordSet: live.recordSet,
}),
fork({
additions: (liveRecordSet) =>
differenceWith(isEqual, target.recordSet)(liveRecordSet),
deletions: (liveRecordSet) =>
differenceWith(isEqual, liveRecordSet)(target.recordSet),
}),
assign({
needUpdateRecordSet: or([
(diff) => !isEmpty(diff.additions),
(diff) => !isEmpty(diff.deletions),
]),
needUpdateManagedZone: () => target.dnsName !== live.dnsName,
}),
assign({
needUpdate: or([
(diff) => diff.needUpdateRecordSet,
(diff) => diff.needUpdateManagedZone,
]),
}),
tap((diff) => {
logger.debug(`compareDnsManagedZone ${tos(diff)}`);
}),
])();
const filterNonDeletableRecords = ({ targetRecordSet, liveRecordSet }) =>
pipe([
() =>
filter(
(type) =>
!find((targetRecord) => targetRecord.type === type)(targetRecordSet)
)(["SOA", "NS"]),
tap((types) => {
logger.debug(`filterNonDeletableRecords: types: ${types}`);
}),
(types) => filter((record) => !types.includes(record.type))(liveRecordSet),
map(omit(["kind", "signatureRrdatas"])),
tap((liveRecordSet) => {
logger.debug(`filterNonDeletableRecords: ${tos(liveRecordSet)}`);
}),
])();
// https://cloud.google.com/dns/docs/reference/v1/managedZones
exports.GcpDnsManagedZone = ({ spec, config }) => {
const { projectId, managedByDescription } = config;
const configDefault = ({ name, properties }) =>
defaultsDeep({
name,
description: managedByDescription,
recordSet: [],
})(properties);
const findName = () => get("name");
const findId = findName;
const axios = createAxiosMakerGoogle({
baseURL: GCP_DNS_BASE_URL,
url: `/projects/${projectId}/managedZones`,
config,
});
const getList = () =>
tryCatch(
pipe([
() =>
retryCallOnError({
name: `getList`,
fn: () => axios.get("/"),
config,
}),
get("data.managedZones"),
map(
assign({
recordSet: pipe([
(managedZone) =>
retryCallOnError({
name: `getList`,
fn: () => axios.get(`/${managedZone.name}/rrsets`),
config,
}),
get("data.rrsets"),
]),
})
),
tap((xxx) => {
logger.debug(`getList`);
}),
]),
(error) => {
logError(`list`, error);
throw axiosErrorToJSON(error);
}
)();
const getById = async ({ id }) =>
tryCatch(
pipe([
tap(() => {
logger.debug(`getById id: ${id}`);
}),
() =>
retryCallOnError({
name: `getById ${id}`,
fn: async () =>
await axios.request(`/${id}`, {
method: "GET",
}),
config,
}),
get("data"),
tap((result) => {
logger.debug(`getById ${tos(result)}`);
}),
]),
(error) => {
if (error.response?.status !== 404) {
logError(`getById ${id}`, error);
throw axiosErrorToJSON(error);
}
logger.debug(`getById ${id} not found`);
}
)();
const create = ({ name, payload }) =>
pipe([
tap(() => {
logger.debug(`create ${name}, payload: ${tos(payload)}`);
assert(name, "name");
assert(payload.name, "missing name");
assert(payload.recordSet, "missing recordSet");
}),
tryCatch(
pipe([
() =>
retryCallOnError({
name: `create ${name}`,
fn: () =>
axios.request(`/`, {
method: "POST",
data: omit(["recordSet"])(payload),
}),
config,
}),
get("data"),
tap((result) => {
logger.debug(`create ${result}`);
}),
() =>
retryCallOnError({
name: `list change ${name}`,
fn: () =>
axios.request(`/${name}/rrsets`, {
method: "GET",
}),
config,
}),
get("data.rrsets"),
tap((rrsets) => {
logger.debug(`create changes ${tos(rrsets)}`);
}),
(rrsets) => ({
additions: payload.recordSet,
deletions: filter(({ type }) =>
find((record) => record.type === type)(payload.recordSet)
)(rrsets),
}),
tap((data) => {
logger.debug(`create changes data ${tos(data)}`);
}),
(data) =>
retryCallOnError({
name: `create recordSet ${name}`,
fn: () =>
axios.request(`/${name}/changes`, {
method: "POST",
data,
}),
config,
}),
get("data"),
tap((result) => {
logger.debug(`create recordset ${result}`);
}),
]),
(error) => {
logError(`create`, error);
throw axiosErrorToJSON(error);
}
),
])();
// https://cloud.google.com/dns/docs/reference/v1beta2/changes/create
const update = async ({ name, payload, live, diff }) =>
pipe([
tap(() => {
logger.info(`update ${name}, payload: ${tos(payload)}`);
assert(name, "name");
assert(live, "live");
assert(diff, "diff");
assert(payload.recordSet, "missing recordSet");
assert(diff.needUpdate, "diff.needUpate");
}),
switchCase([
() => diff.needUpdateManagedZone,
tryCatch(
pipe([
() =>
retryCallOnError({
name: `update dns managed zone ${name}`,
fn: () =>
axios.request(`/${name}`, {
method: "PATCH",
data: omit(["recordSet"])(payload),
}),
config,
}),
get("data"),
tap((result) => {
logger.debug(`update ${result}`);
}),
]),
(error) => {
logError(`update`, error);
throw axiosErrorToJSON(error);
}
),
() => diff.needUpdateRecordSet,
tryCatch(
pipe([
() =>
retryCallOnError({
name: `update dns changes ${name}`,
fn: () =>
axios.request(`/${name}/changes`, {
method: "POST",
data: {
additions: diff.additions,
deletions: diff.deletions,
},
}),
config,
}),
get("data"),
tap((result) => {
logger.info(`update ${result}`);
}),
]),
(error) => {
logError(`update`, error);
throw axiosErrorToJSON(error);
}
),
]),
])();
const destroy = async ({ id, name }) =>
tryCatch(
pipe([
tap(() => {
logger.info(`destroy ${name}`);
}),
() =>
retryCallOnError({
name: `destroy ${name}`,
fn: async () =>
await axios.request(`/${name}/rrsets`, {
method: "GET",
}),
config,
}),
get("data.rrsets"),
tap((rrsets) => {
logger.debug(`destroy ${name}`);
}),
(rrsets) =>
filterNonDeletableRecords({
targetRecordSet: [],
liveRecordSet: rrsets,
}),
tap((rrsets) => {
logger.debug(`destroy ${name}`);
}),
(rrsets) =>
retryCallOnError({
name: `destroy change ${name}`,
fn: async () =>
await axios.request(`/${name}/changes`, {
method: "POST",
data: { deletions: rrsets },
}),
config,
}),
tap((rrsets) => {
logger.debug(`destroy ${name}`);
}),
() =>
retryCallOnError({
name: `destroy ${name}`,
fn: async () =>
await axios.request(`/${name}`, {
method: "DELETE",
}),
config,
}),
get("data"),
tap((result) => {
logger.info(`destroyed`);
}),
]),
(error) => {
logError(`delete ${name}`, error);
throw axiosErrorToJSON(error);
}
)();
const getByName = getByNameCore({ getList, findName });
const isDownById = isDownByIdCore({ getById, findId });
return {
spec,
configDefault,
getList,
create,
update,
destroy,
findName,
getByName,
getById,
isDownById,
findId,
};
};