support TN 25.04, env vars in config, improved Dockerfiles

Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
Travis Glenn Hansen 2025-04-05 17:16:52 -06:00
parent d05fe2c4f4
commit 957d7fc6bc
13 changed files with 5955 additions and 181 deletions

View File

@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/*
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG=en_US.utf8
ENV NODE_VERSION=v20.11.1
ENV NODE_VERSION=v20.19.0
ENV NODE_ENV=production
# install build deps
@ -80,18 +80,24 @@ RUN apt-get update && \
apt-get install -y wget netbase zip bzip2 socat e2fsprogs exfatprogs xfsprogs btrfs-progs fatresize dosfstools ntfs-3g nfs-common cifs-utils fdisk gdisk cloud-guest-utils sudo rsync procps util-linux nvme-cli fuse3 && \
rm -rf /var/lib/apt/lists/*
ARG RCLONE_VERSION=1.66.0
# TODO: remove nvme unique files
ARG RCLONE_VERSION=1.69.1
ADD docker/rclone-installer.sh /usr/local/sbin
RUN chmod +x /usr/local/sbin/rclone-installer.sh && rclone-installer.sh
ARG RESTIC_VERSION=0.16.4
ARG RESTIC_VERSION=0.18.0
ADD docker/restic-installer.sh /usr/local/sbin
RUN chmod +x /usr/local/sbin/restic-installer.sh && restic-installer.sh
ARG KOPIA_VERSION=0.16.1
ARG KOPIA_VERSION=0.19.0
ADD docker/kopia-installer.sh /usr/local/sbin
RUN chmod +x /usr/local/sbin/kopia-installer.sh && kopia-installer.sh
ARG YQ_VERSION=v4.45.1
ADD docker/yq-installer.sh /usr/local/sbin
RUN chmod +x /usr/local/sbin/yq-installer.sh && yq-installer.sh
# controller requirements
#RUN apt-get update && \
# apt-get install -y ansible && \

View File

@ -3,9 +3,11 @@
# https://github.com/kubernetes/kubernetes/blob/master/test/images/busybox/Dockerfile_windows
# https://github.com/kubernetes/kubernetes/tree/master/test/images#windows-test-images-considerations
# https://stefanscherer.github.io/find-dependencies-in-windows-containers/
#
# https://stackoverflow.com/questions/65104246/how-to-install-powershell-core-in-aspnet-nanoserver-docker-container
#
# docker build --build-arg NANO_BASE_TAG=1809 --build-arg CORE_BASE_TAG=ltsc2019 -t foobar -f Dockerfile.Windows .
# docker run --rm -ti --entrypoint powershell foobar
# docker run --rm -ti --entrypoint cmd foobar
# docker run --rm foobar
# docker save foobar -o foobar.tar
# buildah pull docker-archive:foobar.tar
@ -16,85 +18,96 @@
ARG NANO_BASE_TAG
ARG CORE_BASE_TAG
FROM mcr.microsoft.com/windows/servercore:${CORE_BASE_TAG} as powershell
# install powershell
ENV PS_VERSION=6.2.7
ADD https://github.com/PowerShell/PowerShell/releases/download/v$PS_VERSION/PowerShell-$PS_VERSION-win-x64.zip /PowerShell/powershell.zip
RUN cd C:\PowerShell &\
tar.exe -xf powershell.zip &\
del powershell.zip &\
mklink powershell.exe pwsh.exe
FROM mcr.microsoft.com/windows/servercore:${CORE_BASE_TAG} as build
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
#ENV GPG_VERSION 4.0.2
ENV GPG_VERSION 2.3.4
ENV POWERSHELL_TELEMETRY_OPTOUT="1"
RUN Invoke-WebRequest $('https://files.gpg4win.org/gpg4win-vanilla-{0}.exe' -f $env:GPG_VERSION) -OutFile 'gpg4win.exe' -UseBasicParsing ; \
Start-Process .\gpg4win.exe -ArgumentList '/S' -NoNewWindow -Wait
ARG PS_VERSION=7.5.0
ADD https://github.com/PowerShell/PowerShell/releases/download/v$PS_VERSION/PowerShell-$PS_VERSION-win-x64.zip /PowerShell/powershell.zip
# https://github.com/nodejs/node#release-keys
RUN @( \
'4ED778F539E3634C779C87C6D7062848A1AB005C', \
'141F07595B7B3FFE74309A937405533BE57C7D57', \
'94AE36675C464D64BAFA68DD7434390BDBE9B9C5', \
'74F12602B6F1C4E913FAA37AD3A89613643B6201', \
'71DCFD284A79C3B38668286BC97EC7A07EDE3FC1', \
'61FC681DFB92A079F1685E77973F295594EC4689', \
'8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600', \
'C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8', \
'C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C', \
'DD8F2338BAE7501E3DD5AC78C273792F7D83545D', \
'A48C2BEE680E841632CD4E44F07496B3EB3C1762', \
'108F52B48DB57BB0CC439B2997B01419BD92F80A', \
'B9E2F5981AA6E0CD28160D9FF13993A75599653C' \
) | foreach { \
gpg --keyserver hkps://keys.openpgp.org --recv-keys $_ ; \
}
ENV NODE_VERSION 16.18.0
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/SHASUMS256.txt.asc' -f $env:NODE_VERSION) -OutFile 'SHASUMS256.txt.asc' -UseBasicParsing ;
#RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/SHASUMS256.txt.asc' -f $env:NODE_VERSION) -OutFile 'SHASUMS256.txt.asc' -UseBasicParsing ; \
# gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc
#gpg --verify SHASUMS256.txt.sig SHASUMS256.txt
RUN \
Expand-Archive '/PowerShell/powershell.zip' -DestinationPath '/PowerShell' ; \
cd C:\PowerShell ; \
del powershell.zip ; \
New-Item -ItemType SymbolicLink -Path "powershell.exe" -Target "pwsh.exe"
ENV NODE_VERSION 20.19.0
ENV NODE_ENV=production
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; \
$sum = $(cat SHASUMS256.txt.asc | sls $(' node-v{0}-win-x64.zip' -f $env:NODE_VERSION)) -Split ' ' ; \
if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $sum[0]) { Write-Error 'SHA256 mismatch' } ; \
Expand-Archive node.zip -DestinationPath C:\ ; \
Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs'
Expand-Archive node.zip -DestinationPath C:\ ; \
Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs'
RUN mkdir \usr\local\bin; mkdir \tmp
ARG RCLONE_VERSION=v1.69.1
RUN Invoke-WebRequest "https://github.com/rclone/rclone/releases/download/${env:RCLONE_VERSION}/rclone-${env:RCLONE_VERSION}-windows-amd64.zip" -OutFile '/tmp/rclone.zip' -UseBasicParsing ; \
Expand-Archive C:\tmp\rclone.zip -DestinationPath C:\tmp ; \
Copy-Item $('C:\tmp\rclone-{0}-windows-amd64\rclone.exe' -f $env:RCLONE_VERSION) -Destination "C:\usr\local\bin"
ARG RESTIC_VERSION=0.18.0
RUN Invoke-WebRequest "https://github.com/restic/restic/releases/download/v${env:RESTIC_VERSION}/restic_${env:RESTIC_VERSION}_windows_amd64.zip" -OutFile '/tmp/restic.zip' -UseBasicParsing ; \
Expand-Archive C:\tmp\restic.zip -DestinationPath C:\tmp ; \
Copy-Item $('C:\tmp\restic_{0}_windows_amd64.exe' -f $env:RESTIC_VERSION) -Destination "C:\usr\local\bin\restic.exe"
ARG KOPIA_VERSION=0.19.0
RUN Invoke-WebRequest "https://github.com/kopia/kopia/releases/download/v${env:KOPIA_VERSION}/kopia-${env:KOPIA_VERSION}-windows-x64.zip" -OutFile '/tmp/kopia.zip' -UseBasicParsing ; \
Expand-Archive C:\tmp\kopia.zip -DestinationPath C:\tmp ; \
Copy-Item $('C:\tmp\kopia-{0}-windows-x64\kopia.exe' -f $env:KOPIA_VERSION) -Destination "C:\usr\local\bin"
ARG YQ_VERSION=v4.45.1
RUN Invoke-WebRequest "https://github.com/mikefarah/yq/releases/download/${env:YQ_VERSION}/yq_windows_amd64.zip" -OutFile '/tmp/yq.zip' -UseBasicParsing ; \
Expand-Archive C:\tmp\yq.zip -DestinationPath C:\tmp ; \
Copy-Item $('C:\tmp\yq_windows_amd64.exe') -Destination "C:\usr\local\bin\yq.exe"
RUN Remove-Item C:\tmp\ -Force -Recurse
# install app
#RUN setx /M PATH "%PATH%;C:\nodejs"
RUN setx /M PATH $(${Env:PATH} + \";C:\nodejs\")
RUN node --version; npm --version;
RUN mkdir /app
WORKDIR /app
COPY package*.json ./
RUN npm install --only=production; ls /
COPY . .
######################
# actual image
######################
FROM mcr.microsoft.com/windows/nanoserver:${NANO_BASE_TAG}
SHELL ["cmd.exe", "/s" , "/c"]
#https://github.com/PowerShell/PowerShell-Docker/issues/236
# NOTE: this works for non-host process containers, but host process containers will have specials PATH requirements
# C:\Windows\System32\WindowsPowerShell\v1.0\
ENV PATH="C:\Windows\system32;C:\Windows;C:\PowerShell;C:\app\bin;"
ENV DEMOCRATIC_CSI_IS_CONTAINER=true
ENV NODE_ENV=production
LABEL org.opencontainers.image.source https://github.com/democratic-csi/democratic-csi
LABEL org.opencontainers.image.url https://github.com/democratic-csi/democratic-csi
LABEL org.opencontainers.image.licenses MIT
# if additional dlls are required can copy like this
#COPY --from=build /Windows/System32/nltest.exe /Windows/System32/nltest.exe
# install powershell
COPY --from=build /PowerShell /PowerShell
# install app
COPY --from=build /app /app
WORKDIR /app
# this works for both host-process and non-host-process container semantics
COPY --from=build /nodejs/node.exe ./bin
COPY --from=build /usr/local/bin/ ./bin
ENTRYPOINT [ "bin/node.exe", "--expose-gc", "bin/democratic-csi" ]
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'Continue'; $verbosePreference='Continue';"]
EXPOSE 50051
# this works for both host-process and non-host-process container semantics
#ENTRYPOINT [ "bin/node.exe", "--expose-gc", "bin/democratic-csi" ]
ADD docker/entrypoint.ps1 ./bin
# NOTE: this powershell.exe could be problematic based on overriding PATH in container vs host etc
ENTRYPOINT [ "powershell.exe", "bin/entrypoint.ps1" ]

View File

@ -10,7 +10,12 @@ require("../src/utils/polyfills");
const yaml = require("js-yaml");
const fs = require("fs");
const { grpc } = require("../src/utils/grpc");
const { stringify, stripWindowsDriveLetter } = require("../src/utils/general");
const {
stringify,
stripWindowsDriveLetter,
expandenv,
} = require("../src/utils/general");
const traverse = require("traverse");
let driverConfigFile;
let options;
@ -67,6 +72,8 @@ const args = require("yargs")
"1.7.0",
"1.8.0",
"1.9.0",
"1.10.0",
"1.11.0",
],
})
.demandOption(["csi-version"], "csi-version is required")
@ -106,6 +113,20 @@ if (!args.serverSocket && !args.serverAddress && !args.serverPort) {
process.exit(1);
}
//console.log(JSON.stringify(options, null, 2));
traverse(options).forEach(function (v) {
if (typeof v === "string" || v instanceof String) {
v = expandenv(v);
try {
v = JSON.parse(v);
} catch (e) {
// ignore
}
this.update(v);
}
});
//console.log(JSON.stringify(options, null, 2));
//process.exit(1);
//console.log(args);
//console.log(process.env);
@ -529,6 +550,9 @@ if (process.env.LOG_GRPC_SESSIONS == "1") {
if (require.main === module) {
(async function () {
try {
//nvme gen-hostnqn > /etc/nvme/hostnqn
//uuidgen > /etc/nvme/hostid
if (bindAddress) {
await new Promise((resolve, reject) => {
csiServer.bindAsync(

2103
csi_proto/csi-v1.10.0.proto Normal file

File diff suppressed because it is too large Load Diff

2078
csi_proto/csi-v1.11.0.proto Normal file

File diff suppressed because it is too large Load Diff

6
docker/entrypoint.ps1 Normal file
View File

@ -0,0 +1,6 @@
write-host "starting democratic-csi via entrypoint.ps1"
$env:Path = "${pwd}\bin;${env:Path}"
.\bin\node.exe --expose-gc .\bin\democratic-csi @args
Exit $LASTEXITCODE

38
docker/yq-installer.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
set -e
set -x
PLATFORM_TYPE=${1}
if [[ "${PLATFORM_TYPE}" == "build" ]]; then
PLATFORM=$BUILDPLATFORM
else
PLATFORM=$TARGETPLATFORM
fi
if [[ "x${PLATFORM}" == "x" ]]; then
PLATFORM="linux/amd64"
fi
# these come from the --platform option of buildx, indirectly from DOCKER_BUILD_PLATFORM in main.yaml
if [ "$PLATFORM" = "linux/amd64" ]; then
export PLATFORM_ARCH="amd64"
elif [ "$PLATFORM" = "linux/arm64" ]; then
export PLATFORM_ARCH="arm64"
elif [ "$PLATFORM" = "linux/arm/v7" ]; then
export PLATFORM_ARCH="arm"
elif [ "$PLATFORM" = "linux/s390x" ]; then
export PLATFORM_ARCH="s390x"
elif [ "$PLATFORM" = "linux/ppc64le" ]; then
export PLATFORM_ARCH="ppc64le"
else
echo "unsupported/unknown yq PLATFORM ${PLATFORM}"
exit 0
fi
echo "I am installing yq $YQ_VERSION"
wget https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_${PLATFORM_ARCH} -O /usr/local/bin/yq
chmod +x /usr/local/bin/yq

View File

@ -4,63 +4,67 @@ Some drivers support different settings for volumes. These can be configured via
classes.
## `synology-iscsi`
The `synology-iscsi` driver supports several storage class parameters. Note however that not all parameters/values are
supported for all backing file systems and LUN type. The following options are available:
### Configure Storage Classes
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: synology-iscsi
parameters:
fsType: ext4
# The following options affect the LUN representing the volume. These options are passed directly to the Synology API.
# The following options are known.
lunTemplate: |
type: BLUN # Btrfs thin provisioning
type: BLUN_THICK # Btrfs thick provisioning
type: THIN # Ext4 thin provisioning
type: ADV # Ext4 thin provisioning with legacy advanced feature set
type: FILE # Ext4 thick provisioning
description: Some Description
# Only for thick provisioned volumes. Known values:
# 0: Buffered Writes
# 3: Direct Write
direct_io_pattern: 0
# Device Attributes. See below for more info
dev_attribs:
- dev_attrib: emulate_tpws
enable: 1
- ...
fsType: ext4
# The following options affect the LUN representing the volume. These options are passed directly to the Synology API.
# The following options are known.
lunTemplate: |
type: BLUN # Btrfs thin provisioning
type: BLUN_THICK # Btrfs thick provisioning
type: THIN # Ext4 thin provisioning
type: ADV # Ext4 thin provisioning with legacy advanced feature set
type: FILE # Ext4 thick provisioning
description: Some Description
# The following options affect the iSCSI target. These options will be passed directly to the Synology API.
# The following options are known.
targetTemplate: |
has_header_checksum: false
has_data_checksum: false
# Note that this option requires a compatible filesystem. Use 0 for unlimited sessions.
max_sessions: 0
multi_sessions: true
max_recv_seg_bytes: 262144
max_send_seg_bytes: 262144
# Only for thick provisioned volumes. Known values:
# 0: Buffered Writes
# 3: Direct Write
direct_io_pattern: 0
# Use this to disable authentication. To configure authentication see below
auth_type: 0
# Device Attributes. See below for more info
dev_attribs:
- dev_attrib: emulate_tpws
enable: 1
- ...
# The following options affect the iSCSI target. These options will be passed directly to the Synology API.
# The following options are known.
targetTemplate: |
has_header_checksum: false
has_data_checksum: false
# Note that this option requires a compatible filesystem. Use 0 for unlimited sessions.
max_sessions: 0
multi_sessions: true
max_recv_seg_bytes: 262144
max_send_seg_bytes: 262144
# Use this to disable authentication. To configure authentication see below
auth_type: 0
```
#### About LUN Types
The availability of the different types of LUNs depends on the filesystem used on your Synology volume. For Btrfs volumes
you can use `BLUN` and `BLUN_THICK` volumes. For Ext4 volumes you can use `THIN`, `ADV` or `FILE` volumes. These
correspond to the options available in the UI.
#### About `dev_attribs`
Most of the LUN options are configured via the `dev_attribs` list. This list can be specified both in the `lunTemplate`
of the global configuration and in the `lunTemplate` of the `StorageClass`. If both lists are present they will be merged
(with the `StorageClass` taking precedence). The following `dev_attribs` are known to work:
(with the `StorageClass` taking precedence). The following `dev_attribs` are known to work:
- `emulate_tpws`: Hardware-assisted zeroing
- `emulate_caw`: Hardware-assisted locking
@ -71,6 +75,7 @@ of the global configuration and in the `lunTemplate` of the `StorageClass`. If b
- `can_snapshot`: Enable snapshots for this volume. Only works for thin provisioned volumes.
### Configure Snapshot Classes
`synology-iscsi` can also configure different parameters on snapshot classes:
```yaml
@ -82,18 +87,18 @@ parameters:
# This inline yaml object will be passed to the Synology API when creating the snapshot.
lunSnapshotTemplate: |
is_locked: true
# https://kb.synology.com/en-me/DSM/tutorial/What_is_file_system_consistent_snapshot
# Note that app consistent snapshots require a working Synology Storage Console. Otherwise both values will have
# equivalent behavior.
is_app_consistent: true
...
```
Note that it is currently not supported by Synology devices to restore a snapshot onto a different volume. You can
create volumes from snapshots, but you should use the same `StorageClass` as the original volume of the snapshot did.
create volumes from snapshots, but you should use the same `StorageClass` as the original volume of the snapshot did.
### Enabling CHAP Authentication
You can enable CHAP Authentication for `StorageClass`es by supplying an appropriate `StorageClass` secret (see the
[documentation](https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html) for more details). You
can use the same password for alle volumes of a `StorageClass` or use different passwords per volume.
@ -123,12 +128,17 @@ kind: Secret
metadata:
name: chap-secret
stringData:
# Client Credentials
user: client
password: MySecretPassword
# Mutual CHAP Credentials. If these are specified mutual CHAP will be enabled.
mutualUser: server
mutualPassword: MyOtherPassword
lunTemplate: |
...
targetTemplate: |
# Client Credentials
user: client
password: MySecretPassword
# Mutual CHAP Credentials. If these are specified mutual CHAP will be enabled.
mutualUser: server
mutualPassword: MyOtherPassword
lunSnapshotTemplate: |
...
```
Note that CHAP authentication will only be enabled if the secret contains a username and password. If e.g. a password is

1551
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@
"reconnecting-websocket": "^4.4.0",
"semver": "^7.3.4",
"ssh2": "^1.1.0",
"traverse": "^0.6.11",
"uri-js": "^4.4.1",
"uuid": "^9.0.0",
"winston": "^3.6.0",

View File

@ -1798,8 +1798,13 @@ class FreeNASApiDriver extends CsiBaseDriver {
async removeSnapshotsFromDatatset(datasetName) {
const httpApiClient = await this.getTrueNASHttpApiClient();
// const httpClient = await this.getHttpClient();
// const major = await httpApiClient.getSystemVersionMajor();
let job_id = await httpApiClient.DatasetDestroySnapshots(datasetName);
await httpApiClient.CoreWaitForJob(job_id, 30);
if (job_id) {
await httpApiClient.CoreWaitForJob(job_id, 30);
}
}
/**
@ -2033,10 +2038,13 @@ class FreeNASApiDriver extends CsiBaseDriver {
}
async getTrueNASHttpApiClient() {
return this.ctx.registry.getAsync(`${__REGISTRY_NS__}:api_client`, async () => {
const httpClient = await this.getHttpClient();
return new TrueNASApiClient(httpClient, this.ctx.cache);
});
return this.ctx.registry.getAsync(
`${__REGISTRY_NS__}:api_client`,
async () => {
const httpClient = await this.getHttpClient();
return new TrueNASApiClient(httpClient, this.ctx.cache);
}
);
}
getAccessModes(capability) {

View File

@ -1,6 +1,7 @@
const _ = require("lodash");
const { sleep, stringify } = require("../../../utils/general");
const { Zetabyte } = require("../../../utils/zfs");
const { Registry } = require("../../../utils/registry");
// used for in-memory cache of the version info
const FREENAS_SYSTEM_VERSION_CACHE_KEY = "freenas:system_version";
@ -11,6 +12,7 @@ class Api {
this.client = client;
this.cache = cache;
this.options = options;
this.registry = new Registry();
}
async getHttpClient() {
@ -22,7 +24,7 @@ class Api {
* @returns
*/
async getZetabyte() {
return this.ctx.registry.get(`${__REGISTRY_NS__}:zb`, () => {
return this.registry.get(`${__REGISTRY_NS__}:zb`, () => {
return new Zetabyte({
executor: {
spawn: function () {
@ -422,13 +424,13 @@ class Api {
* @param {*} properties
* @returns
*/
async DatasetGet(datasetName, properties) {
async DatasetGet(datasetName, properties, queryParams = {}) {
const httpClient = await this.getHttpClient(false);
let response;
let endpoint;
endpoint = `/pool/dataset/id/${encodeURIComponent(datasetName)}`;
response = await httpClient.get(endpoint);
response = await httpClient.get(endpoint, queryParams);
if (response.statusCode == 200) {
return this.normalizeProperties(response.body, properties);
@ -441,28 +443,60 @@ class Api {
throw new Error(JSON.stringify(response.body));
}
/**
* This is meant to destroy all snapshots on the given dataset
*
* @param {*} datasetName
* @param {*} data
* @returns
*/
async DatasetDestroySnapshots(datasetName, data = {}) {
const httpClient = await this.getHttpClient(false);
let response;
let endpoint;
data.name = datasetName;
const major = await this.getSystemVersionMajor();
if (Number(major) >= 25) {
try {
response = await this.DatasetGet(
datasetName,
["id", "type", "name", "pool", "snapshots"],
{
"extra.snapshots": "true",
"extra.retrieve_children": "false",
}
);
endpoint = "/pool/dataset/destroy_snapshots";
response = await httpClient.post(endpoint, data);
for (const snapshot of _.get(response, "snapshots", [])) {
await this.SnapshotDelete(snapshot.name, {
defer: true,
});
}
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
return;
}
throw err;
}
} else {
data.name = datasetName;
if (response.statusCode == 200) {
return response.body;
endpoint = "/pool/dataset/destroy_snapshots";
response = await httpClient.post(endpoint, data);
if (response.statusCode == 200) {
return response.body;
}
if (
response.statusCode == 422 &&
JSON.stringify(response.body).includes("already exists")
) {
return;
}
throw new Error(JSON.stringify(response.body));
}
if (
response.statusCode == 422 &&
JSON.stringify(response.body).includes("already exists")
) {
return;
}
throw new Error(JSON.stringify(response.body));
}
async SnapshotSet(snapshotName, properties) {

View File

@ -272,6 +272,19 @@ async function hostname_lookup(hostname) {
});
}
function expandenv(string, env) {
if (!(typeof string === "string" || string instanceof String)) {
throw new Error("Please pass a string into expandenv");
}
env = env ? env : process.env;
return string.replace(/\$\{?[a-zA-Z_]+[a-zA-Z0-9_]*\}?/g, function (match) {
match = match.replace(/[^A-Za-z0-9_]/g, "");
return env[match] || "";
});
}
module.exports.sleep = sleep;
module.exports.md5 = md5;
module.exports.crc32 = crc32;
@ -292,3 +305,4 @@ module.exports.default_supported_file_filesystems =
module.exports.retry = retry;
module.exports.trimchar = trimchar;
module.exports.hostname_lookup = hostname_lookup;
module.exports.expandenv = expandenv;