deploy-azure-cdn
Advanced tools
Comparing version 0.1.2 to 1.0.1
241
index.js
@@ -1,239 +0,4 @@ | ||
var through = require('through2'); | ||
var gutil = require('gulp-util'); | ||
var PluginError = gutil.PluginError; | ||
var Q = require('q'); | ||
var azure = require('azure'); | ||
var mime = require('mime'); | ||
var path = require('path'); | ||
var zlib = require('zlib'); | ||
var fs = require('fs'); | ||
var extend = require('node.extend'); | ||
var async = require('async'); | ||
"use strict"; | ||
var deploy = require('./src/deploy-task'); | ||
function createAzureCdnContainer(blobService, options) { | ||
var deferred = Q.defer(); | ||
blobService.createContainerIfNotExists(options.containerName, options.containerOptions, function (err) { | ||
if (err) { | ||
if (err.code === 'ContainerBeingDeleted') { | ||
deferred.reject("Container being deleted, retry in 10 seconds") | ||
} | ||
deferred.reject(err); | ||
} | ||
deferred.resolve(); | ||
}); | ||
return deferred.promise; | ||
} | ||
function noop() { | ||
var success = arguments[arguments.length - 1]; | ||
success(); | ||
} | ||
function emptyAzureCdnTargetFolder(blobService, options, logger) { | ||
var deferred = Q.defer(); | ||
if(!options.deleteExistingBlobs){ | ||
deferred.resolve(); | ||
} else { | ||
// removing all blobs in destination structure | ||
blobService.listBlobs(options.containerName, {prefix: options.folder}, function (err, blobs) { | ||
if (err) { | ||
deferred.reject(err); | ||
} | ||
var count = blobs.length; | ||
if(count === 0){ | ||
deferred.resolve(); | ||
} | ||
blobs.forEach(function (blob, next) { | ||
logger("deleting file", blob.name); | ||
var exec = options.testRun ? noop : blobService.deleteBlob; | ||
exec.call(blobService, options.containerName, blob.name, function (err, success) { | ||
if (err) { | ||
logger("Error while deleting blob", blob.name); | ||
deferred.reject(err); | ||
} | ||
logger("deleted", blob.url); | ||
if(--count == 0){ | ||
deferred.resolve(); | ||
} | ||
}); | ||
}); | ||
}); | ||
} | ||
return deferred.promise; | ||
} | ||
function uploadFileToAzureCdn(blobService, options, logger, destFileName, sourceFile, metadata) { | ||
var deferred = Q.defer(); | ||
var exec = options.testRun ? noop : blobService.createBlockBlobFromFile; | ||
logger("Uploading", destFileName, "encoding", metadata.contentEncoding); | ||
exec.call(blobService, options.containerName, destFileName, sourceFile, metadata, function(err) { | ||
if (err) { | ||
deferred.reject(err); | ||
} | ||
logger("Uploaded", destFileName, "to", options.containerName); | ||
deferred.resolve(); | ||
}); | ||
return deferred.promise; | ||
} | ||
function chooseSmallerFileAndModifyContentType(compressedFile, originalFile, metadata) { | ||
var deferred = Q.defer(); | ||
fs.stat(compressedFile, function (err, compressedStats) { | ||
if(err){ | ||
deferred.reject(err); | ||
return; | ||
} | ||
fs.stat(originalFile, function (err, originalStats) { | ||
if(err){ | ||
deferred.reject(err); | ||
return; | ||
} | ||
if(originalStats.size < compressedStats.size){ | ||
// don't upload compressed if it becomes bigger | ||
deferred.resolve({ | ||
zippedTmpFile: compressedFile, | ||
fileToUpload: originalFile, | ||
updatedMetadata: metadata | ||
}); | ||
} else { | ||
metadata.contentEncoding = 'gzip'; | ||
deferred.resolve({ | ||
zippedTmpFile: compressedFile, | ||
fileToUpload: compressedFile, | ||
updatedMetadata: metadata | ||
}); | ||
} | ||
}); | ||
}); | ||
return deferred.promise; | ||
} | ||
function gzipFile(source){ | ||
var tempFile; | ||
var deferred = Q.defer(), | ||
gzip = zlib.createGzip({ | ||
level: 9 // maximum compression | ||
}), | ||
inp, | ||
out; | ||
gzip.on('error', function(err) { | ||
deferred.reject(err); | ||
}); | ||
inp = fs.createReadStream(source); | ||
tempFile = source + '.zip'; | ||
out = fs.createWriteStream(tempFile); | ||
out.on('close', function() { | ||
deferred.resolve(tempFile); | ||
}); | ||
inp.pipe(gzip).pipe(out); | ||
return deferred.promise; | ||
} | ||
function clone(obj) { | ||
if (null == obj || "object" != typeof obj) return obj; | ||
var copy = obj.constructor(); | ||
for (var attr in obj) { | ||
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; | ||
} | ||
return copy; | ||
} | ||
function deploy(opt, files, logger, cb) { | ||
var options = extend({}, { | ||
serviceOptions: [], // custom arguments to azure.createBlobService | ||
containerName: null, // container name, required | ||
containerOptions: {publicAccessLevel: "blob"}, // container options | ||
folder: '', // path within container | ||
deleteExistingBlobs: true, // true means recursively deleting anything under folder | ||
concurrentUploadThreads: 10, // number of concurrent uploads, choose best for your network condition | ||
gzip: false, // gzip files if they become smaller after zipping, content-encoding header will change if file is zipped | ||
metadata: {cacheControl: 'public, max-age=31556926'}, // metadata for each uploaded file | ||
testRun: false // test run - means no blobs will be actually deleted or uploaded, see log messages for details | ||
}, opt); | ||
if (!options.containerName) { | ||
return cb("Missing containerName!"); | ||
} | ||
var blobService = azure.createBlobService.apply(azure, options.serviceOptions); | ||
var preparation = createAzureCdnContainer(blobService, options). | ||
then(function() { | ||
return emptyAzureCdnTargetFolder(blobService, options, logger); | ||
}); | ||
async.eachLimit(files, options.concurrentUploadThreads, function(file, eachCallback) { | ||
var relativePath = file.path.replace(file.cwd + path.sep, ''); | ||
var destFileName = options.folder + relativePath; | ||
var sourceFile = file.path; | ||
var metadata = clone(options.metadata); | ||
metadata.contentType = mime.lookup(sourceFile); | ||
if (options.zip) { | ||
preparation.then(function () { | ||
return gzipFile(sourceFile) | ||
}).then(function (tmpFile) { | ||
return chooseSmallerFileAndModifyContentType(tmpFile, sourceFile, metadata); | ||
}).then(function (res) { | ||
return uploadFileToAzureCdn(blobService, options, logger, destFileName, res.fileToUpload, res.updatedMetadata) | ||
.finally(function () { | ||
fs.unlinkSync(res.zippedTmpFile); | ||
}); | ||
}).then(function(){ | ||
eachCallback(); | ||
}).catch(function (error) { | ||
eachCallback(error); | ||
}); | ||
} else { | ||
preparation.then(function () { | ||
return uploadFileToAzureCdn(blobService, options, logger, destFileName, sourceFile, metadata) | ||
}).then(function(){ | ||
eachCallback(); | ||
}).catch(function (error) { | ||
eachCallback(error); | ||
}); | ||
} | ||
}, function(err) { | ||
cb(err); | ||
}); | ||
} | ||
module.exports = { | ||
gulpPlugin: function(opt){ | ||
const PLUGIN_NAME = 'gulp-deploy-azure-cdn '; | ||
var files = []; | ||
return through.obj( | ||
function (file, enc, cb) { | ||
var self = this; | ||
if (path.basename(file.path)[0] === '_') { | ||
return cb(); | ||
} | ||
if (file.isNull()) { | ||
self.push(file); | ||
return cb(); | ||
} | ||
if (file.isStream()) { | ||
this.emit('error', new gutil.PluginError(PLUGIN_NAME, 'Streaming not supported')); | ||
return cb(); | ||
} | ||
files.push(file); | ||
return cb(); | ||
}, | ||
function(cb){ | ||
var self = this; | ||
var logger = gutil.log.bind(PLUGIN_NAME); | ||
try { | ||
deploy(opt, files, logger, function(err){ | ||
if(err){ | ||
self.emit('error', new gutil.PluginError(PLUGIN_NAME, err)); | ||
} | ||
console.log("FINISHED") | ||
cb(); | ||
}) | ||
} catch (err) { | ||
self.emit('error', new gutil.PluginError(PLUGIN_NAME, err)); | ||
cb(); | ||
} | ||
}); | ||
}, | ||
vanilla: deploy | ||
}; | ||
module.exports = deploy; |
{ | ||
"name": "deploy-azure-cdn", | ||
"version": "0.1.2", | ||
"description": "A package that copies files to Azure CDN. Can be used as gulp task.", | ||
"main": "index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/bestander/deploy-azure-cdn.git" | ||
}, | ||
"keywords": [ | ||
"gulp", | ||
"gulpfriendly", | ||
"gulpplugin", | ||
"azure", | ||
"deployment", | ||
"cdn", | ||
"upload" | ||
], | ||
"author": "Konstantin Raev", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/bestander/deploy-azure-cdn/issues" | ||
}, | ||
"homepage": "https://github.com/bestander/deploy-azure-cdn", | ||
"dependencies": { | ||
"azure": "^0.8.1", | ||
"mime": "^1.2.11", | ||
"gulp-util": "^2.2.14", | ||
"through2": "^0.4.1", | ||
"q": "^1.0.1", | ||
"node.extend": "^1.0.10", | ||
"async": "^0.6.1" | ||
} | ||
"name": "deploy-azure-cdn", | ||
"version": "1.0.1", | ||
"description": "A package that copies files to Azure CDN. Can be used as gulp task.", | ||
"main": "index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/bestander/deploy-azure-cdn.git" | ||
}, | ||
"keywords": [ | ||
"azure", | ||
"deployment", | ||
"ci", | ||
"continuous integration", | ||
"cdn", | ||
"blob", | ||
"upload" | ||
], | ||
"author": "Konstantin Raev", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/bestander/deploy-azure-cdn/issues" | ||
}, | ||
"scripts": { | ||
"test": "jest" | ||
}, | ||
"homepage": "https://github.com/bestander/deploy-azure-cdn", | ||
"dependencies": { | ||
"async": "^0.9.0", | ||
"azure-storage": "^0.3.3", | ||
"mime": "^1.2.11", | ||
"node.extend": "^1.1.0", | ||
"q": "^1.0.1" | ||
}, | ||
"devDependencies": { | ||
"jest-cli": "^0.1.18" | ||
} | ||
} |
108
README.md
# deploy-azure-cdn | ||
[![Build Status](https://travis-ci.org/bestander/deploy-azure-cdn.svg?branch=master)](https://travis-ci.org/bestander/deploy-azure-cdn) | ||
A node package for copying a directory to Azure CDN storage. | ||
Also it provides a gulp plugin interface for easy deploy with (gulp)[http://gulpjs.com/] | ||
A node package for uploading files to Azure Blob Storage. | ||
It is perfect for deploying compiled assets to Microsoft Azure CDN as a last step in a Continuous Integration setup. | ||
Azure SDK uses by default the environment variables AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY. | ||
Custom connection arguments can be set in options. | ||
## Features | ||
- Ability to execute a "dry run" of deployment. The logging will indicate all files that will be deleted or uploaded but no actual changes to the blob storage will be done | ||
- Ability to gzip content and set a proper content encoding. If gzipped file becomes larger than original then only the original file will be uploaded | ||
- Ability to recursively remove files in a path of Azure Blob Storage | ||
- Ability to control number of concurrent files to be uploaded to avoid network congestion | ||
- [Grunt](https://github.com/bestander/grunt-azure-cdn-deploy) and [gulp](https://github.com/bestander/gulp-deploy-azure-cdn) plugins available | ||
## Installing | ||
@@ -15,49 +21,61 @@ | ||
## Exported interface | ||
## Using | ||
- gulpPlugin: function(deployOptions) - to use with gulp | ||
- vanilla: function(deployOptions, files, logger, cb) - to use directly in node.js | ||
See `__tests__` folder for all possible scenarios. | ||
parameters: | ||
- deployOptions - azure cdn and upload configs | ||
- serviceOptions: [] - custom arguments to azure.createBlobService | ||
- containerName: null - container name, required | ||
- containerOptions: {publicAccessLevel: "blob"} - container options | ||
- folder: '', // path within container. Default is root directory of container | ||
- deleteExistingBlobs: true, // set it to false to skip recursive deleting blobs in folder | ||
- concurrentUploadThreads : 10, // number of concurrent uploads, choose best for your network condition | ||
- gzip: false, // true if want to gzip the files before uploading. File will be zipped only if compressed file is smaller than original | ||
- metadata: {cacheControl: 'public, max-age=31556926'} // metadata for each uploaded file | ||
- testRun: false, // set to true if you just want to check connectivity and see deployment logs. No blobs will be removed or uplaoded. | ||
- files: [] - array of files objects to be deployed | ||
- cwd - current working directory path | ||
- path - absolute path of file | ||
- logger - logger compatible with gutil.log(param1, param2...) | ||
- cb - node callback | ||
### Deploying a set of files to a path in blob storage | ||
## Using in Gulp example | ||
```javascript | ||
var deployCdn = require('gulp-deploy-azure-cdn'); | ||
gulp.task('upload-app-to-azure', function () { | ||
return gulp.src(['build/**/*'], { | ||
cwd: 'build' | ||
}).pipe(deployCdn.gulpPlugin({ | ||
containerName: 'test', | ||
serviceOptions: ['<my azure cdn name>', '<my azure cdn secret>'], | ||
folder: 'build-0.0.1/', | ||
zip: true, | ||
deleteExistingBlobs: true, | ||
metadata: { | ||
cacheControl: 'public, max-age=31530000', // cache in browser | ||
cacheControlHeader: 'public, max-age=31530000' // cache in azure CDN. As this data does not change, we set it to 1 year | ||
} | ||
})) | ||
var logger = console.log; | ||
var files = [ | ||
{cwd: 'node_modules/deploy-azure-cdn', path: '/Users/bestander/work/opensource/gulp-deploy-azure-cdn/node_modules/deploy-azure-cdn/index.js'}, | ||
{cwd: 'node_modules/deploy-azure-cdn', path: '/Users/bestander/work/opensource/gulp-deploy-azure-cdn/node_modules/deploy-azure-cdn/LICENSE'}, | ||
{cwd: 'node_modules/deploy-azure-cdn', path: '/Users/bestander/work/opensource/gulp-deploy-azure-cdn/node_modules/deploy-azure-cdn/package.json'} | ||
]; | ||
var opts = { | ||
serviceOptions: ['blobstoragename', '/OwQ/MyLongSecretStringFromAzureConfigPanel'], // custom arguments to azure.createBlobService | ||
containerName: 'test', // container name in blob | ||
containerOptions: {publicAccessLevel: "blob"}, // container options | ||
folder: 'deploy/source', // path within container | ||
deleteExistingBlobs: true, // true means recursively deleting anything under folder | ||
concurrentUploadThreads: 2, // number of concurrent uploads, choose best for your network condition | ||
zip: true, // gzip files if they become smaller after zipping, content-encoding header will change if file is zipped | ||
metadata: {cacheControl: 'public, max-age=31556926'}, // metadata for each uploaded file | ||
testRun: false // test run - means no blobs will be actually deleted or uploaded, see log messages for details | ||
}; | ||
deploy(opts, files, logger, function(err){ | ||
if(err) { | ||
console.log("Error deploying", err) | ||
} | ||
console.log('Job\'s done!'); | ||
}); | ||
``` | ||
## Grunt plugin | ||
See my other repository: https://github.com/bestander/grunt-azure-cdn-deploy. | ||
Code is very similar but less structured. | ||
### Parameters | ||
- `deployOptions` - azure cdn and upload configs | ||
- `serviceOptions`: [] - custom arguments to azure.createBlobService, or you can use Azure SDK environment variables AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY | ||
- `containerName`: null - container name, required | ||
- `containerOptions`: {publicAccessLevel: "blob"} - container options | ||
- `folder`: '', // path within container. Default is root directory of container | ||
- `deleteExistingBlobs`: true, // set it to false to skip recursive deleting blobs in folder | ||
- `concurrentUploadThreads` : 10, // number of concurrent uploads, choose best for your network condition | ||
- `zip`: false, // true if want to gzip the files before uploading. File will be zipped only if compressed file is smaller than original | ||
- `metadata`: {cacheControl: 'public, max-age=31556926'} // metadata for each uploaded file | ||
- `testRun`: false, // set to true if you just want to check connectivity and see deployment logs. No blobs will be removed or uplaoded. | ||
- `files`: [] - array of files objects to be deployed | ||
- `cwd` - current working directory path | ||
- `path` - absolute path of file | ||
- `logger` - logger compatible with console.log(param1, param2...) | ||
- `cb` - node callback | ||
## Grunt and gulp plugins | ||
See plugins as repositories: | ||
- [https://github.com/bestander/grunt-azure-cdn-deploy](https://github.com/bestander/grunt-azure-cdn-deploy) | ||
- [https://github.com/bestander/gulp-deploy-azure-cdn](https://github.com/bestander/gulp-deploy-azure-cdn) | ||
## TODO, contributions are welcome | ||
- use streams to upload encoded files | ||
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
49969
5
15
832
0
81
1
4
1
+ Addedazure-storage@^0.3.3
+ Addedazure-storage@0.3.3(transitive)
+ Addedextend@1.2.1(transitive)
- Removedazure@^0.8.1
- Removedgulp-util@^2.2.14
- Removedthrough2@^0.4.1
- Removedansi-regex@0.2.1(transitive)
- Removedansi-styles@1.1.0(transitive)
- Removedarray-find-index@1.0.2(transitive)
- Removedasync@0.6.2(transitive)
- Removedazure@0.8.1(transitive)
- Removedazure-common@0.9.1-pre.2(transitive)
- Removedazure-mgmt@0.9.1-pre.2(transitive)
- Removedazure-mgmt-compute@0.9.1-pre.2(transitive)
- Removedazure-mgmt-sb@0.9.1-pre.2(transitive)
- Removedazure-mgmt-sql@0.9.1-pre.2(transitive)
- Removedazure-mgmt-storage@0.9.1-pre.2(transitive)
- Removedazure-mgmt-vnet@0.9.1-pre.2(transitive)
- Removedazure-mgmt-website@0.9.1-pre.2(transitive)
- Removedcamelcase@2.1.1(transitive)
- Removedcamelcase-keys@2.1.0(transitive)
- Removedchalk@0.5.1(transitive)
- Removedclone-stats@0.0.1(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removedcurrently-unhandled@0.4.1(transitive)
- Removeddateformat@1.0.121.0.2-1.2.3(transitive)
- Removeddecamelize@1.2.0(transitive)
- Removedduplexer@0.1.2(transitive)
- Removedduplexer2@0.0.2(transitive)
- Removedenvconf@0.0.4(transitive)
- Removederror-ex@1.3.2(transitive)
- Removedescape-string-regexp@1.0.5(transitive)
- Removedfind-up@1.1.2(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-stdin@4.0.1(transitive)
- Removedgraceful-fs@4.2.11(transitive)
- Removedgulp-util@2.2.20(transitive)
- Removedhas-ansi@0.1.0(transitive)
- Removedhasown@2.0.2(transitive)
- Removedhosted-git-info@2.8.9(transitive)
- Removedindent-string@2.1.0(transitive)
- Removedinherits@2.0.4(transitive)
- Removedis-arrayish@0.2.1(transitive)
- Removedis-core-module@2.16.1(transitive)
- Removedis-finite@1.1.0(transitive)
- Removedis-utf8@0.2.1(transitive)
- Removedisarray@0.0.1(transitive)
- Removedload-json-file@1.1.0(transitive)
- Removedlodash._escapehtmlchar@2.4.1(transitive)
- Removedlodash._escapestringchar@2.4.1(transitive)
- Removedlodash._htmlescapes@2.4.1(transitive)
- Removedlodash._isnative@2.4.1(transitive)
- Removedlodash._objecttypes@2.4.1(transitive)
- Removedlodash._reinterpolate@2.4.1(transitive)
- Removedlodash._reunescapedhtml@2.4.1(transitive)
- Removedlodash._shimkeys@2.4.1(transitive)
- Removedlodash.defaults@2.4.1(transitive)
- Removedlodash.escape@2.4.1(transitive)
- Removedlodash.isobject@2.4.1(transitive)
- Removedlodash.keys@2.4.1(transitive)
- Removedlodash.template@2.4.1(transitive)
- Removedlodash.templatesettings@2.4.1(transitive)
- Removedlodash.values@2.4.1(transitive)
- Removedloud-rejection@1.6.0(transitive)
- Removedmap-obj@1.0.1(transitive)
- Removedmeow@3.7.0(transitive)
- Removedminimist@0.2.41.2.8(transitive)
- Removedmpns@2.0.1(transitive)
- Removedmultipipe@0.1.2(transitive)
- Removednode-uuid@1.2.0(transitive)
- Removednormalize-package-data@2.5.0(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedobject-keys@0.4.0(transitive)
- Removedparse-json@2.2.0(transitive)
- Removedpath-exists@2.1.0(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedpath-type@1.1.0(transitive)
- Removedpify@2.3.0(transitive)
- Removedpinkie@2.0.4(transitive)
- Removedpinkie-promise@2.0.1(transitive)
- Removedread-pkg@1.1.0(transitive)
- Removedread-pkg-up@1.0.1(transitive)
- Removedreadable-stream@1.0.341.1.14(transitive)
- Removedredent@1.0.0(transitive)
- Removedrepeating@2.0.1(transitive)
- Removedresolve@1.22.10(transitive)
- Removedsemver@5.7.2(transitive)
- Removedsignal-exit@3.0.7(transitive)
- Removedspdx-correct@3.2.0(transitive)
- Removedspdx-exceptions@2.5.0(transitive)
- Removedspdx-expression-parse@3.0.1(transitive)
- Removedspdx-license-ids@3.0.20(transitive)
- Removedstring_decoder@0.10.31(transitive)
- Removedstrip-ansi@0.3.0(transitive)
- Removedstrip-bom@2.0.0(transitive)
- Removedstrip-indent@1.0.1(transitive)
- Removedsupports-color@0.2.0(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)
- Removedthrough@2.3.8(transitive)
- Removedthrough2@0.4.20.5.1(transitive)
- Removedtrim-newlines@1.0.0(transitive)
- Removedtunnel@0.0.6(transitive)
- Removedvalidate-npm-package-license@3.0.4(transitive)
- Removedvinyl@0.2.3(transitive)
- Removedwns@0.5.4(transitive)
- Removedxtend@2.1.23.0.0(transitive)
Updatedasync@^0.9.0
Updatednode.extend@^1.1.0