浏览代码

Feature: add deployment + deployment logs

febbweiss 10 年之前
父节点
当前提交
e135c5d2c6

+ 2 - 0
.meteor/packages

@@ -22,3 +22,5 @@ fortawesome:fontawesome
 iron:router
 zenorocha:clipboard
 simple:reactive-method
+vsivsi:job-collection
+ogamedia:timer

+ 1 - 1
.meteor/release

@@ -1 +1 @@
-METEOR@1.2.0.4-logging.0
+METEOR@1.2.1

+ 21 - 16
.meteor/versions

@@ -1,4 +1,4 @@
-autoupdate@1.2.4-logging
+autoupdate@1.2.4
 babel-compiler@5.8.24_1
 babel-runtime@0.1.4
 base64@1.0.4
@@ -10,19 +10,20 @@ boilerplate-generator@1.0.4
 caching-compiler@1.0.0
 caching-html-compiler@1.0.2
 callback-hook@1.0.4
-check@1.0.6
+check@1.1.0
+coffeescript@1.0.11
 ddp@1.2.2
 ddp-client@1.2.1
-ddp-common@1.2.1
-ddp-server@1.2.1
+ddp-common@1.2.2
+ddp-server@1.2.2
 deps@1.0.9
 diff-sequence@1.0.1
-ecmascript@0.1.5
-ecmascript-collections@0.1.6
+ecmascript@0.1.6
+ecmascript-runtime@0.2.6
 ejson@1.0.7
-es5-shim@4.1.13
+es5-shim@4.1.14
 fastclick@1.0.7
-fortawesome:fontawesome@4.4.0
+fortawesome:fontawesome@4.4.0_1
 geojson-utils@1.0.4
 hot-code-push@1.0.0
 html-tools@1.0.5
@@ -42,20 +43,23 @@ jquery@1.11.4
 launch-screen@1.0.4
 livedata@1.0.15
 logging@1.0.8
-meteor@1.1.9
+meteor@1.1.10
 meteor-base@1.0.1
 minifiers@1.1.7
 minimongo@1.0.10
 mobile-experience@1.0.1
 mobile-status-bar@1.0.6
-mongo@1.1.2
+mongo@1.1.3
 mongo-id@1.0.1
+mrt:later@1.6.1
+mrt:moment@2.8.1
 npm-mongo@1.4.39_1
 observe-sequence@1.0.7
+ogamedia:timer@0.0.9
 ordered-dict@1.0.4
-promise@0.5.0
-random@1.0.4
-reactive-dict@1.1.2
+promise@0.5.1
+random@1.0.5
+reactive-dict@1.1.3
 reactive-var@1.0.6
 reload@1.1.4
 retry@1.0.4
@@ -64,14 +68,15 @@ session@1.1.1
 simple:reactive-method@1.0.2
 spacebars@1.0.7
 spacebars-compiler@1.0.7
-standard-minifiers@1.0.1
-templating@1.1.4
+standard-minifiers@1.0.2
+templating@1.1.5
 templating-tools@1.0.0
 tracker@1.0.9
 twbs:bootstrap-noglyph@3.3.5
 ui@1.0.8
 underscore@1.0.4
 url@1.0.5
-webapp@1.2.2
+vsivsi:job-collection@1.2.3
+webapp@1.2.3
 webapp-hashing@1.0.5
 zenorocha:clipboard@1.4.2

+ 11 - 0
client/deployment.service.js

@@ -0,0 +1,11 @@
+Deployments = new Mongo.Collection('deployments');
+
+DeploymentService = {
+    list: function(id) {
+      var deployments = Deployments.find(),
+            altered = deployments.map(function(doc, index, cursor) {
+                return _.extend(doc, {index: deployments.count() - index});
+              });
+      return altered;  
+    }
+};

+ 1 - 1
client/nav.html

@@ -14,7 +14,7 @@
       <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
         <ul class="nav navbar-nav">
           <li class="active">
-            <a href="/management">Projects <span class="sr-only">(current)</span></a>
+            <a href="/projects">Projects <span class="sr-only">(current)</span></a>
           </li>
         </ul>
       </div>

+ 21 - 0
client/project_details.controller.js

@@ -0,0 +1,21 @@
+Template.deploymentsList.helpers({
+  deployments: function () {
+    return DeploymentService.list();
+  }
+});
+
+Template.deploymentDetails.helpers({
+  format: function() {
+    return this.data.replace(/\n/g, '<br />');
+  },
+  running: function() {
+    var string = this.status;
+    return string.charAt(0).toUpperCase() + string.slice(1);
+  }
+});
+
+Template.deploymentBtn.helpers({
+  running: function() {
+    return this.status === 'pending' ? 'visible' : 'hidden';
+  }
+})

+ 55 - 0
client/project_details.view.html

@@ -0,0 +1,55 @@
+<template name="projectDetails">
+    <ol class="breadcrumb">
+      <li><a href="/projects">Projects</a></li>
+      <li class="active">{{label}}</li>
+    </ol>
+    
+    <h1>
+        {{label}}
+        <a href="{{git_url}}" target="_blank">
+            <i class="fa fa-fw fa-github" title="Go to GIT repository"></i>
+        </a>
+    </h1>
+   {{> deploymentsList}}
+</template>
+
+<template name="deploymentsList">
+    <div>
+        <ul class="nav nav-tabs" role="tablist">
+            {{#each deployments}}
+                {{> deploymentBtn}}
+            {{/each}}
+        </ul>
+        <div class="tab-content">
+            {{#each deployments}}
+                {{> deploymentDetails}}
+            {{/each}}
+        </div>
+    </div>
+</template>
+
+<template name="deploymentBtn">
+    <li role="presentation">
+        <a id="heading{{index}}" role="tab" data-toggle="tab" href="#collapseDeployment{{index}}" aria-controls="collapseDeployment{{index}}">
+            # {{index}}
+            <i class="fa fa-cog fa-spin {{running}}"></i>
+        </a>
+    </li>
+</template>
+
+<template name="deploymentDetails">
+    <div role="tabpanel" class="tab-pane" id="collapseDeployment{{index}}">
+        <div class="well">
+            <h4>Deployment # {{index}} - {{tm_cal timestamp}}</h4>
+            {{#each output}}
+                <p>
+                    <samp>{{{format}}}</samp>
+                </p>
+            {{/each}}
+            
+            <div class="pull-right">
+                <h5>Status : <small>{{running}}</small></h5>
+            </div>
+        </div>
+    </div>
+</template>

+ 2 - 5
client/projects.controller.js

@@ -1,4 +1,4 @@
-Template.management.helpers({
+Template.projects.helpers({
   projects: function () {
     return ProjectService.list();
   }
@@ -13,10 +13,7 @@ Template.projectForm.events({
       event.preventDefault();
       var form = event.target;
       if( form.id.value ) {
-        Meteor.call('editProject',form.id.value, form.label.value, form.git_url.value, form.public_url.value, form.commands.value, function(errors, result) {
-          console.log(errors);
-          console.log(result);
-        });
+        Meteor.call('editProject',form.id.value, form.label.value, form.git_url.value, form.public_url.value, form.commands.value);
         form.id.value = '';
       } else {
         Meteor.call('addProject', form.label.value, form.git_url.value, form.public_url.value, form.commands.value);

+ 4 - 0
client/projects.service.js

@@ -3,5 +3,9 @@ Projects = new Mongo.Collection('projects');
 ProjectService = {
   list: function() {
     return Projects.find({}, {sort: {label: 1}});
+  },
+  
+  get: function(id) {
+    return Projects.findOne({_id: id});
   }
 };

+ 8 - 1
client/projects.view.html

@@ -1,4 +1,8 @@
-<template name="management">
+<template name="projects">
+  <ol class="breadcrumb">
+      <li class="active">Projects</li>
+    </ol>
+    
   <div class="row">
     <div class="col-sm-6">
       <h2>Register or edit a project</h2>
@@ -97,6 +101,9 @@
     <a href="#" class="edit" title="Edit the project">
       <i class="fa fa-fw fa-pencil"></i>
     </a>
+    <a href="/project/{{_id}}" title="View project">
+      <i class="fa fa-fw fa-eye"></i>
+    </a>
   </li>
 </template>
 

+ 22 - 3
lib/routes.js

@@ -7,13 +7,32 @@ Router.map(function () {
   this.route('home', {
     path: '/',
   });
-  this.route('management', function() {
-        this.subscribe('projects').wait();
-        
+  this.route('projects', {
+    waitOn: function() {
+        return Meteor.subscribe('projects');
+    },
+    action: function() {
         if (this.ready()) {
             this.render();
         } else {
             this.render('Loading');
         }
+    }
+  });
+  this.route('project_details', {
+      path: '/project/:_id',
+      waitOn: function() {
+        return [Meteor.subscribe('projects', this.params._id), Meteor.subscribe('deployments', this.params._id)];
+      },
+      data: function () {
+        return ProjectService.get(this.params._id);
+      },
+      action: function() {
+          if (this.ready()) {
+            this.render();
+        } else {
+            this.render('Loading');
+        }
+      }
   });
 });

+ 16 - 6
server/constants.js

@@ -1,6 +1,6 @@
-var DEPLOYMENT_FOLDER = 'deployment',
+DEPLOYMENT_FOLDER = '/home/ubuntu/deployment';
 
-    SCRIPTS = {
+SCRIPTS = {
         CREATE : [
             {
                 cmd: 'mkdir %CWD%',
@@ -9,16 +9,26 @@ var DEPLOYMENT_FOLDER = 'deployment',
                 }
             },
             {
-                cmd: 'cd %CWD%',
+                cmd: 'git clone %GIT% .',
                 options : {
-                    cwd: '%ROOT_CWD%'
+                    cwd: '%ROOT_CWD%/%CWD%'
                 }
-            },
+            }
+        ],
+        UPDATE : [
             {
-                cmd: 'git clone %GIT% .',
+                cmd: 'git pull',
                 options : {
                     cwd: '%ROOT_CWD%/%CWD%'
                 }
             }
+        ],
+        DELETE : [
+            {
+                cmd: 'rm -rf %CWD%',
+                options : {
+                    cwd: '%ROOT_CWD%'
+                }
+            }
         ]
     };

+ 72 - 0
server/jobs/deploy.job.js

@@ -0,0 +1,72 @@
+Job.processJobs('projectDeployerJobQueue', 'create_repository',
+  function(job, callback) {
+    var deployment = DeploymentService.get(job.data.deploymentId),
+        project = ProjectService.get(deployment.project_id);
+        
+    DeploymentService.update_status( deployment._id, 'pending', function() {
+      CommandRunner.run(
+        {
+          script: SCRIPTS.CREATE, 
+          deployment: deployment,
+          project: project,
+          stdout: function(data) {
+              DeploymentService.appendLog(job.data.deploymentId, data, false);
+          }, 
+          stderr: function(data) {
+              DeploymentService.appendLog(job.data.deploymentId, data, true);
+          }
+        },
+        function() {
+            if( callback ) {
+                callback();
+            }   
+        }
+      );
+    });
+  }
+);
+
+Job.processJobs('projectDeployerJobQueue', 'delete_repository',
+  function(job, callback) {
+    CommandRunner.run(
+        { 
+          script: SCRIPTS.DELETE, 
+          project: job.data.project
+        },
+        function() {
+            if( callback ) {
+                callback();
+            }   
+        }
+    );    
+  }
+);
+
+Job.processJobs('projectDeployerJobQueue', 'update_repository',
+  function(job, callback) {
+    var deployment = DeploymentService.get(job.data.deploymentId),
+        project = ProjectService.get(deployment.project_id);
+    
+    DeploymentService.update_status( deployment._id, 'pending', function() {
+      CommandRunner.run(
+        { 
+          script: SCRIPTS.UPDATE, 
+          deployment: deployment,
+          project: project,
+          stdout: function(data) {
+              DeploymentService.appendLog(job.data.deploymentId, data, false);
+          }, 
+          stderr: function(data) {
+              DeploymentService.appendLog(job.data.deploymentId, data, true);
+          }
+        },
+        function() {
+            if( callback ) {
+                callback();
+            }   
+        }
+      );    
+    });
+    
+  }
+);

+ 60 - 12
server/lib/command_runner.js

@@ -1,35 +1,83 @@
 
 var exec = Npm.require('child_process').exec,
-    execSync = function(cmd, options, stdoutHandler, stderrHandler) {
+    execSync = function(cmd, options, stdoutHandler, stderrHandler, callback) {
+        stdoutHandler('$ ' + cmd);
         exec(cmd, 
             options, 
             Meteor.bindEnvironment(
                 function(error, stdout, stderr) {
-                    if( stdout != '' ) {
+                    if( stdout !== '' ) {
                         stdoutHandler(stdout);
                     }
                     if( stderr != '' ) {
                         stderrHandler(stderr);
                     }
+                    callback();
                 }
             )
         );
+    },
+    replace = function(string, customs = {}) {
+        var globals = {'%ROOT_CWD%': DEPLOYMENT_FOLDER};
+        for(var key in globals) {
+            string = string.replace(key, globals[key]);
+        }
+        for(var key in customs) {
+            string = string.replace(key, customs[key]);
+        }
+        
+        return string;
     };
     
-var CommandRunner = {
-    run: function( script, deployment, stdout, stderr, counter, callback ) {
-        var command = script[command].cmd.replace('%ROOT_CWD%', DEPLOYMENT_FOLDER).replace('%CWD%', deployment._id), 
-            options = script[command].options;
-        options.cwd.replace('%ROOT_CWD%', DEPLOYMENT_FOLDER).replace('%CWD%', deployment._id);
+CommandRunner = {
+    run: function( data, callback = undefined) {
+        var bundle = _.extend({deployment: {}, project:{}, stdout: console.log, stderr: console.error, counter: 0, deploy_script: true}, data),
+            customs = {'%CWD%': bundle.project._id, '%GIT%': bundle.project.git_url};
+            
+        var line = bundle.script[bundle.counter],
+            command = replace(line.cmd, customs ), 
+            options = line.options;
+            
+        options.cwd = replace(options.cwd, customs);
         
-        execSync(command, options, stdout, stderr, function() {
-            counter++;
-            if( counter > script.length ) {
-                if( callback ) {
+        execSync(command, options, bundle.stdout, bundle.stderr, function() {
+            bundle.counter++;
+            if( bundle.counter >= bundle.script.length ) {
+                if( bundle.deploy_script && bundle.project.commands ) {
+                    bundle.deploy_script = false;
+                    bundle.script = bundle.project.commands.split('\n');
+                    bundle.counter = 0;
+                    CommandRunner.commands(bundle, callback);
+                } else if( callback ) {
+                    if( bundle.deployment ) {
+                        DeploymentService.update_status(bundle.deployment._id, 'closed', callback);
+                    } else {
+                        callback();
+                    }
+                }
+            } else {
+                CommandRunner.run(bundle, callback);
+            }
+        });
+    },
+    
+    commands: function(bundle, callback = undefined) {
+        var command = bundle.script[bundle.counter], 
+            customs = {'%CWD%': bundle.project._id},
+            options = {
+                cwd: replace('%ROOT_CWD%/%CWD%', customs)
+            };
+            
+        execSync(command, options, bundle.stdout, bundle.stderr, function() {
+            bundle.counter++;
+            if( bundle.counter >= bundle.script.length ) {
+                if( bundle.deployment ) {
+                    DeploymentService.update_status(bundle.deployment._id, 'closed', callback);
+                } else {
                     callback();
                 }
             } else {
-                CommandRunner.run(script, deployment, stdout, stderr, counter, callback);
+                CommandRunner.commands(bundle, callback);
             }
         });
     }

+ 46 - 11
server/lib/deployment.service.js

@@ -2,31 +2,66 @@ Deployments = new Mongo.Collection('deployments');
 
 DeploymentService = {
     get: function(id) {
-      return Deployments.find({_id: id}, {date: 1});  
+      return Deployments.findOne({_id: id});  
     },
     
     appendLog: function(id, data, error) {
+        var deployment = DeploymentService.get(id),
+            project = ProjectService.get(deployment.project_id);
+        
         Deployments.update({ _id: id },{ $push: {
             output: {
                 timestamp : new Date().getTime(),
-                data : data
+                data : data.replace(new RegExp(project._id, 'g'), project.label),
+                error: error
             }
         }});
     },
-    create: function(project, callback) {
-        return Deployment.insert({
-            project_id: projet._id,
+    
+    create: function(project) {
+        return Deployments.insert({
+            project_id: project._id,
             timestamp: new Date().getTime(),
-            output: []
+            output: [],
+            status: 'opened'
         }, function(errors, deploymentId) {
-            _execSync(cmd, function(data) {
-                DeploymentService.appendLog(deploymentId, data, false);
-            }, function(data) {
-                DeploymentService.appendLog(deploymentId, data, true);
-            });
+            JobService.create_repository(deploymentId);
         });
     }, 
     
+    delete: function(projectId) {
+      Deployments.remove({project_id: projectId}, function(errors) {
+          if( !errors ) {
+              JobService.delete_repository({_id: projectId});
+          }
+      });  
+    },
+    
+    update: function(projectId) {
+       Deployments.insert({
+            project_id: projectId,
+            timestamp: new Date().getTime(),
+            output: []
+        }, function(errors, deploymentId) {
+            JobService.update_repository(deploymentId);
+        });
+    },
+    
+    update_status: function(id, status, callback) {
+        Deployments.update(
+            id, 
+            { $set: { 
+                status: status
+                }
+            },
+            callback
+            );
+    },
+    
+    list: function(projectId) {
+        return Deployments.find({project_id: projectId}, {sort: {timestamp: -1}});
+    },
+    
     deploy: function(project) {
         
     }

+ 51 - 0
server/lib/jobs.service.js

@@ -0,0 +1,51 @@
+var jobs = JobCollection('projectDeployerJobQueue');
+  jobs.allow({
+    // Grant full permission to any authenticated user
+    admin: function (userId, method, params) {
+      return true;
+    }
+  });
+
+jobs.startJobServer();
+
+JobService = {
+    create_repository : function(deploymentId) {
+        Job(jobs, 'create_repository',
+            {
+                deploymentId: deploymentId
+            })
+            .priority('normal')
+            .retry({
+                retries: 5,
+                wait: 10 * 1000
+            })
+            .save();
+    },
+    
+    update_repository : function(deploymentId) {
+        Job(jobs, 'update_repository',
+            {
+                deploymentId: deploymentId
+            })
+            .priority('normal')
+            .retry({
+                retries: 5,
+                wait: 10 * 1000
+            })
+            .save();
+    },
+    
+    delete_repository: function(project) {
+        Job(jobs, 'delete_repository',
+            {
+                project: project
+            })
+            .priority('normal')
+            .retry({
+                retries: 5,
+                wait: 10 * 1000
+            })
+            .save();
+    }
+}
+

+ 11 - 5
server/lib/projects.methods.js

@@ -10,18 +10,24 @@ Meteor.methods({
   addProject: function(label, git_url, public_url ,commands) {
     return ProjectService.insert(label, git_url, public_url ,commands, function(errors, id) {
       if( id ) {
-        /*DeploymentService.deploy(ProjectService.get(id), function(errors, deploymentId) {
-          
-        });*/
+        DeploymentService.create(ProjectService.get(id));
       }
     });
   },
   
   editProject: function(id, label, git_url, public_url ,commands) {
-    ProjectService.update(id, label, git_url, public_url ,commands);
+    ProjectService.update(id, label, git_url, public_url ,commands, function(errors, updated_count) {
+      if( updated_count ) {
+        DeploymentService.update(id);
+      }
+    });
   },
   
   deleteProject: function(id) {
-    ProjectService.delete(id);
+    ProjectService.delete(id, function(errors) {
+      if( ! errors ) {
+        DeploymentService.delete(id);
+      }
+    });
   }
 });

+ 5 - 4
server/lib/projects.service.js

@@ -10,7 +10,7 @@ ProjectService = {
     }, callback);
   },
   
-  update: function(id, label, git_url, public_url ,commands) {
+  update: function(id, label, git_url, public_url ,commands, callback) {
     Projects.update(
       id, 
       { $set: { 
@@ -19,12 +19,13 @@ ProjectService = {
         public_url: public_url,
         commands: commands
         } 
-      }
+      },
+      callback
     );
   },
   
-  delete: function(id) {
-    Projects.remove(id);
+  delete: function(id, callback) {
+    Projects.remove(id, callback);
   },
   
   get: function(id) {

+ 5 - 1
server/publications.js

@@ -3,5 +3,9 @@ Meteor.publish('projects', function() {
 });
 
 Meteor.publish('deployment', function(id) {
-    return DeploymentService.get(id);
+  return DeploymentService.get(id);
+});
+
+Meteor.publish('deployments', function(project_id) {
+  return DeploymentService.list(project_id);
 });