diff --git a/src/common/constants.ts b/src/common/constants.ts index 63ed3609..d8a77c6d 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -104,6 +104,9 @@ export class Constants { public static inputNamePattern = "input1,input2,input3"; public static moduleSchemaVersion = "$schema-version"; public static groupId = "groupId"; + + public static TwinValueMaxSize = 512; + public static TwinValueMaxChunks = 8; } export enum ContainerState { diff --git a/src/common/utility.ts b/src/common/utility.ts index cfd8b1f5..b4980b68 100644 --- a/src/common/utility.ts +++ b/src/common/utility.ts @@ -449,6 +449,22 @@ export class Utility { }); } + public static convertCreateOptions(deployment: any): any { + if (deployment) { + const moduleProperties = deployment.modulesContent.$edgeAgent["properties.desired"]; + const systemModules = moduleProperties.systemModules; + if (systemModules) { + moduleProperties.systemModules = Utility.serializeCreateOptionsForEachModule(systemModules); + } + const modules = moduleProperties.modules; + if (modules) { + moduleProperties.modules = Utility.serializeCreateOptionsForEachModule(modules); + } + } + + return deployment; + } + // Temp utility to sovle the compatibale issue because of the schema change in IoT Hub Service. // moduleContent -> modulesContent public static updateSchema(deployment: any): any { @@ -459,6 +475,42 @@ export class Utility { return deployment; } + public static serializeCreateOptions(settings: any, createOptions: any): any { + let optionStr: string; + if (typeof createOptions === "string") { + optionStr = createOptions; + } else { + optionStr = JSON.stringify(createOptions); + } + const re = new RegExp(`.{1,${Constants.TwinValueMaxSize}}`, "g"); + const options = optionStr.match(re); + if (options.length > Constants.TwinValueMaxChunks) { + throw new Error("Size of createOptions too big. The maxium size of createOptions is 4K"); + } + options.map((value, index) => { + if (index === 0) { + settings.createOptions = value; + } else { + const formattedNumber = (`0${index}`).slice(-2); + settings[`createOptions${formattedNumber}`] = value; + } + }); + + return settings; + } + + private static serializeCreateOptionsForEachModule(modules: any): any { + for (const key in modules) { + if (modules.hasOwnProperty(key)) { + const moduleVar = modules[key]; + if (moduleVar.settings && moduleVar.settings.createOptions) { + moduleVar.settings = Utility.serializeCreateOptions(moduleVar.settings, moduleVar.settings.createOptions); + } + } + } + return modules; + } + private static getLocalRegistryState(): ContainerState { try { const isRunning = Executor.execSync("docker inspect registry --format='{{.State.Running}}'"); diff --git a/src/container/containerManager.ts b/src/container/containerManager.ts index 25f4bd16..e87728b1 100644 --- a/src/container/containerManager.ts +++ b/src/container/containerManager.ts @@ -119,7 +119,7 @@ export class ContainerManager { const moduleExpanded: string = Utility.expandModules(data, moduleToImageMap); const exceptStr = ["$edgeHub", "$edgeAgent", "$upstream"]; const generatedDeployFile: string = Utility.expandEnv(moduleExpanded, ...exceptStr); - const dpManifest = Utility.updateSchema(JSON.parse(generatedDeployFile)); + const dpManifest = Utility.convertCreateOptions(Utility.updateSchema(JSON.parse(generatedDeployFile))); // generate config file await fse.ensureDir(configPath); await fse.writeFile(deployFile, JSON.stringify(dpManifest, null, 2), { encoding: "utf8" }); diff --git a/test/utility.test.ts b/test/utility.test.ts index 0e3a25bd..37738759 100644 --- a/test/utility.test.ts +++ b/test/utility.test.ts @@ -32,6 +32,92 @@ suite("utility tests", () => { .modules.samplemodule.settings.image, "test.az.io/filter:0.0.1-amd64"); }).timeout(60 * 1000); + test("convertCreateOptions", async () => { + const input: string = await fse.readFile(path.resolve(__dirname, "../../testResources/deployment.template.json"), "utf8"); + let deployment = JSON.parse(input); + const oldOptionObj = deployment.modulesContent.$edgeAgent["properties.desired"].modules.tempSensor.settings.createOptions; + deployment = Utility.convertCreateOptions(deployment); + const depStr = JSON.stringify(deployment, null, 2); + assert.equal( + deployment.modulesContent.$edgeAgent["properties.desired"].systemModules.edgeAgent.settings.createOptions, + deployment.modulesContent.$edgeAgent["properties.desired"].systemModules.edgeHub.settings.createOptions, + ); + + const settings = deployment.modulesContent.$edgeAgent["properties.desired"].modules.tempSensor.settings; + assert.equal(settings.hasOwnProperty("createOptions"), true); + assert.equal(settings.hasOwnProperty("createOptions01"), true); + assert.equal(settings.hasOwnProperty("createOptions02"), true); + + const optionString = settings.createOptions + settings.createOptions01 + settings.createOptions02; + const optionObj = JSON.parse(optionString); + for (const key in oldOptionObj) { + if (oldOptionObj.hasOwnProperty(key)) { + assert.equal(optionObj.hasOwnProperty(key), true); + } + } + assert.equal(optionObj.Env.length, oldOptionObj.Env.length); + assert.equal(JSON.stringify(optionObj.Env), JSON.stringify(oldOptionObj.Env)); + }).timeout(60 * 1000); + + test("serializeCreateOptions", async () => { + const hostPort: any = { + HostPort: "43", + }; + + const hostPorts50: any[] = Array(50).fill(hostPort); + const PortBindingsVal = { + "43/udp": hostPorts50, + "42/tcp": [{ + HostPort: "42", + }], + }; + + const createOptionsVal = { + Env: ["k1=v1", "k2=v2", "k3=v3"], + HostConfig: { + PortBindings: PortBindingsVal, + }, + }; + + let settings = { + image: "test", + createOptions: createOptionsVal, + }; + + settings = Utility.serializeCreateOptions(settings, createOptionsVal); + const outStr = JSON.stringify(settings); + + const expected = "{\"image\":\"test\",\"createOptions\":\"{\\\"Env\\\":[\\\"k1=v1\\\",\\\"k2=v2\\\",\\\"k3=v3\\\"]," + + "\\\"HostConfig\\\":{\\\"PortBindings\\\":{\\\"43/udp\\\":[{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostP\",\"createOptions01\":\"ort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}," + + "{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}],\\\"42/tcp\\\":[{\\\"HostPort\\\":\\\"42\\\"}]}}}\"}"; + assert.equal(outStr, expected); + }).timeout(60 * 1000); + + test("serializeCreateOptionsShort", async () => { + const createOptionsVal = { + Env: ["k1=v1", "k2=v2", "k3=v3"], + }; + + let settings = { + image: "test", + createOptions: createOptionsVal, + }; + + settings = Utility.serializeCreateOptions(settings, createOptionsVal); + const outStr = JSON.stringify(settings); + const expected = "{\"image\":\"test\",\"createOptions\":\"{\\\"Env\\\":[\\\"k1=v1\\\",\\\"k2=v2\\\",\\\"k3=v3\\\"]}\"}"; + assert.equal(outStr, expected); + }).timeout(60 * 1000); + test("getValidModuleName", () => { let valid: string = Utility.getValidModuleName("test Space"); assert.equal(valid, "test_Space"); diff --git a/testResources/deployment.template.json b/testResources/deployment.template.json index e1bd3888..77a3b796 100644 --- a/testResources/deployment.template.json +++ b/testResources/deployment.template.json @@ -15,7 +15,15 @@ "type": "docker", "settings": { "image": "$AGENT", - "createOptions": "{}" + "createOptions": { + "HostConfig":{ + "PortBindings":{ + "5671/tcp":[{"HostPort":"5671"}], + "8883/tcp":[{"HostPort":"8883"}], + "443/tcp":[{"HostPort":"443"}] + } + } + } } }, "edgeHub": { @@ -24,7 +32,7 @@ "restartPolicy": "always", "settings": { "image": "microsoft/azureiotedge-hub:1.0-preview", - "createOptions": "{}" + "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}" } } }, @@ -36,7 +44,70 @@ "restartPolicy": "always", "settings": { "image": "$IMAGE", - "createOptions": "{}" + "createOptions": { + "Env": [ + "abcdefghij0=00", + "abcdefghij1=01", + "abcdefghij2=02", + "abcdefghij3=03", + "abcdefghij4=04", + "abcdefghij5=05", + "abcdefghij6=06", + "abcdefghij7=07", + "abcdefghij8=08", + "abcdefghij9=09", + "abcdefghij10=10", + "abcdefghij11=11", + "abcdefghij12=12", + "abcdefghij13=13", + "abcdefghij14=14", + "abcdefghij15=15", + "abcdefghij16=16", + "abcdefghij17=17", + "abcdefghij18=18", + "abcdefghij19=19", + "abcdefghij20=20", + "abcdefghij22=21", + "abcdefghij22=22", + "abcdefghij23=23", + "abcdefghij24=24", + "abcdefghij25=25", + "abcdefghij26=26", + "abcdefghij27=27", + "abcdefghij28=28", + "abcdefghij29=29", + "abcdefghij30=30", + "abcdefghij31=31", + "abcdefghij32=32", + "abcdefghij33=33", + "abcdefghij34=34", + "abcdefghij35=35", + "abcdefghij36=36", + "abcdefghij37=37", + "abcdefghij38=38", + "abcdefghij39=39", + "abcdefghij40=40", + "abcdefghij41=41", + "abcdefghij42=42", + "abcdefghij43=43", + "abcdefghij44=44", + "abcdefghij45=45", + "abcdefghij46=46", + "abcdefghij47=47", + "abcdefghij48=48", + "abcdefghij49=49", + "abcdefghij50=50", + "abcdefghij51=51", + "abcdefghij52=52", + "abcdefghij53=53", + "abcdefghij54=54", + "abcdefghij55=55", + "abcdefghij56=56", + "abcdefghij57=57", + "abcdefghij58=58", + "abcdefghij59=59" + ] + } } }, "samplemodule": {