Skip to content

Commit

Permalink
Deploy, rollback, retry and cancel deployments from the web console
Browse files Browse the repository at this point in the history
  • Loading branch information
fabianofranz committed Aug 28, 2015
1 parent 7c61caf commit 71558bc
Show file tree
Hide file tree
Showing 6 changed files with 756 additions and 138 deletions.
246 changes: 245 additions & 1 deletion assets/app/scripts/controllers/deployments.js
Expand Up @@ -30,7 +30,7 @@ angular.module('openshiftConsole')
});
}

watches.push(DataService.watch("replicationcontrollers", $scope, function(deployments) {
watches.push(DataService.watch("replicationcontrollers", $scope, function(deployments, action, deployment) {
$scope.unfilteredDeployments = deployments.by("metadata.name");
LabelFilter.addLabelSuggestionsFromResources($scope.unfilteredDeployments, $scope.labelSuggestions);
LabelFilter.setLabelSuggestions($scope.labelSuggestions);
Expand All @@ -41,6 +41,27 @@ angular.module('openshiftConsole')
associateDeploymentsToDeploymentConfig();
updateFilterWarning();

var deploymentConfigName;
var deploymentName;
if (deployment) {
deploymentConfigName = $filter('annotation')(deployment, 'deploymentConfig');
deploymentName = deployment.metadata.name;
}
if (!action) {
// Loading of the page that will create deploymentConfigDeploymentsInProgress structure, which will associate running deployment to his deploymentConfig.
$scope.deploymentConfigDeploymentsInProgress = associateRunningDeploymentToDeploymentConfig($scope.deploymentsByDeploymentConfig);
} else if (action === 'ADDED' || (action === 'MODIFIED' && ['New', 'Pending', 'Running'].indexOf($scope.deploymentStatus(deployment)) > -1)) {
// When new deployment id instantiated/cloned, or in case of a retry, associate him to his deploymentConfig and add him into deploymentConfigDeploymentsInProgress structure.
$scope.deploymentConfigDeploymentsInProgress[deploymentConfigName] = $scope.deploymentConfigDeploymentsInProgress[deploymentConfigName] || {};
$scope.deploymentConfigDeploymentsInProgress[deploymentConfigName][deploymentName] = deployment;
} else if (action === 'MODIFIED') {
// After the deployment ends remove him from the deploymentConfigDeploymentsInProgress structure.
var deploymentStatus = $scope.deploymentStatus(deployment);
if (deploymentStatus === "Complete" || deploymentStatus === "Failed"){
delete $scope.deploymentConfigDeploymentsInProgress[deploymentConfigName][deploymentName];
}
}

Logger.log("deployments (subscribe)", $scope.deployments);
}));

Expand Down Expand Up @@ -73,6 +94,20 @@ angular.module('openshiftConsole')
});
}

function associateRunningDeploymentToDeploymentConfig(deploymentsByDeploymentConfig) {
var deploymentConfigDeploymentsInProgress = {};
angular.forEach(deploymentsByDeploymentConfig, function(deploymentConfigDeployments, deploymentConfigName) {
deploymentConfigDeploymentsInProgress[deploymentConfigName] = {};
angular.forEach(deploymentConfigDeployments, function(deployment, deploymentName) {
var deploymentStatus = $scope.deploymentStatus(deployment);
if (deploymentStatus === "New" || deploymentStatus === "Pending" || deploymentStatus === "Running") {
deploymentConfigDeploymentsInProgress[deploymentConfigName][deploymentName] = deployment;
}
});
});
return deploymentConfigDeploymentsInProgress;
}

function updateFilterWarning() {
if (!LabelFilter.getLabelSelector().isEmpty() && $.isEmptyObject($scope.deployments) && !$.isEmptyObject($scope.unfilteredDeployments)) {
$scope.alerts["deployments"] = {
Expand All @@ -85,6 +120,201 @@ angular.module('openshiftConsole')
}
}

$scope.startLatestDeployment = function(deploymentConfigName) {
var deploymentConfig = $scope.deploymentConfigs[deploymentConfigName];

// increase latest version by one so starts new deployment based on latest
var req = {
kind: "DeploymentConfig",
apiVersion: "v1",
metadata: deploymentConfig.metadata,
spec: deploymentConfig.spec,
status: deploymentConfig.status
};
if (!req.status.latestVersion) {
req.status.latestVersion = 0;
}
req.status.latestVersion++;

// update the deployment config
DataService.update("deploymentconfigs", deploymentConfigName, req, $scope).then(
function() {
$scope.alerts = [
{
type: "success",
message: "Deployment #" + req.status.latestVersion + " of " + deploymentConfigName + " has started.",
}
];
},
function(result) {
$scope.alerts = [
{
type: "error",
message: "An error occurred while starting the deployment.",
details: getErrorDetails(result)
}
];
}
);
};

$scope.retryFailedDeployment = function(deploymentConfigName, deploymentName) {
var deployment = $scope.deploymentsByDeploymentConfig[deploymentConfigName][deploymentName];
var req = deployment;

// delete the deployer pod as well as the deployment hooks pods, if any
DataService.list("pods", $scope, function(list) {
var pods = list.by("metadata.name");
var deleteDeployerPod = function(pod) {
var deployerPodForAnnotation = $filter('annotationName')('deployerPodFor');
if (pod.metadata.labels[deployerPodForAnnotation] === deploymentName) {
DataService.delete("pods", pod.metadata.name, $scope).then(
function() {
Logger.info("Deployer pod " + pod.metadata.name + " deleted");
},
function(result) {
$scope.alerts = [
{
type: "error",
message: "An error occurred while deleting the deployer pod.",
details: getErrorDetails(result)
}
];
}
);
}
};
angular.forEach(pods, deleteDeployerPod);
});

// set deployment to "New" and remove statuses so we can retry
var deploymentStatusAnnotation = $filter('annotationName')('deploymentStatus');
var deploymentStatusReasonAnnotation = $filter('annotationName')('deploymentStatusReason');
var deploymentCancelledAnnotation = $filter('annotationName')('deploymentCancelled');
req.metadata.annotations[deploymentStatusAnnotation] = "New";
delete req.metadata.annotations[deploymentStatusReasonAnnotation];
delete req.metadata.annotations[deploymentCancelledAnnotation];

// update the deployment
DataService.update("replicationcontrollers", deploymentName, req, $scope).then(
function() {
$scope.alerts = [
{
type: "success",
message: "Retrying deployment " + deploymentName + " of " + deploymentConfigName + ".",
}
];
},
function(result) {
$scope.alerts = [
{
type: "error",
message: "An error occurred while retrying the deployment.",
details: getErrorDetails(result)
}
];
}
);
};

$scope.rollbackToDeployment = function(deploymentConfigName, deploymentName, changeScaleSettings, changeStrategy, changeTriggers) {
// put together a new rollback request
var req = {
kind: "DeploymentConfigRollback",
apiVersion: "v1",
spec: {
from: {
name: deploymentName
},
includeTemplate: true,
includeReplicationMeta: changeScaleSettings,
includeStrategy: changeStrategy,
includeTriggers: changeTriggers
}
};

// create the deployment config rollback
DataService.create("deploymentconfigrollbacks", null, req, $scope).then(
function(newDeploymentConfig) {
// update the deployment config based on the one returned by the rollback
DataService.update("deploymentconfigs", deploymentConfigName, newDeploymentConfig, $scope).then(
function(rolledBackDeploymentConfig) {
$scope.alerts = [
{
type: "success",
message: "Deployment #" + rolledBackDeploymentConfig.status.latestVersion + " is rolling back " + deploymentConfigName + " to " + deploymentName + ".",
}
];
},
function(result) {
$scope.alerts = [
{
type: "error",
message: "An error occurred while rolling back the deployment.",
details: getErrorDetails(result)
}
];
}
);
},
function(result) {
$scope.alerts = [
{
type: "error",
message: "An error occurred while rolling back the deployment.",
details: getErrorDetails(result)
}
];
}
);
};

$scope.cancelRunningDeployment = function(deploymentConfigName, deploymentName) {
var deployment = $scope.deploymentsByDeploymentConfig[deploymentConfigName][deploymentName];
var req = deployment;

// set the cancellation annotations
var deploymentCancelledAnnotation = $filter('annotationName')('deploymentCancelled');
var deploymentStatusReasonAnnotation = $filter('annotationName')('deploymentStatusReason');
req.metadata.annotations[deploymentCancelledAnnotation] = "true";
req.metadata.annotations[deploymentStatusReasonAnnotation] = "The deployment was cancelled by the user";

// update the deployment with cancellation annotations
DataService.update("replicationcontrollers", deploymentName, req, $scope).then(
function() {
$scope.alerts = [
{
type: "success",
message: "Cancelling deployment " + deploymentName + " of " + deploymentConfigName + ".",
}
];
},
function(result) {
$scope.alerts = [
{
type: "error",
message: "An error occurred while cancelling the deployment.",
details: getErrorDetails(result)
}
];
}
);
};

$scope.deploymentIsLatest = function(deploymentConfig, deployment) {
var deploymentVersion = parseInt($filter('annotation')(deployment, 'deploymentVersion'));
var deploymentConfigVersion = deploymentConfig.status.latestVersion;
return deploymentVersion === deploymentConfigVersion;
};

$scope.deploymentStatus = function(deployment) {
return $filter('annotation')(deployment, 'deploymentStatus');
};

$scope.deploymentIsInProgress = function(deployment) {
return ['New', 'Pending', 'Running'].indexOf($scope.deploymentStatus(deployment)) > -1;
};

LabelFilter.onActiveFiltersChanged(function(labelSelector) {
// trigger a digest loop
$scope.$apply(function() {
Expand All @@ -94,6 +324,20 @@ angular.module('openshiftConsole')
});
});

function getErrorDetails(result) {
var error = result.data || {};
if (error.message) {
return error.message;
}

var status = result.status || error.status;
if (status) {
return "Status: " + status;
}

return "";
}

$scope.$on('$destroy', function(){
DataService.unwatchAll(watches);
});
Expand Down
38 changes: 23 additions & 15 deletions assets/app/scripts/filters/resources.js
Expand Up @@ -14,28 +14,36 @@ angular.module('openshiftConsole')
}
};
})
.filter('annotation', function() {
// This maps an annotation key to all known synonymous keys to insulate
// the referring code from key renames across API versions.
var annotationMap = {
"deploymentConfig": ["openshift.io/deployment-config.name"],
"deployment": ["openshift.io/deployment.name"],
"pod": ["openshift.io/deployer-pod.name"],
"deploymentStatus": ["openshift.io/deployment.phase"],
"encodedDeploymentConfig": ["openshift.io/encoded-deployment-config"],
"deploymentVersion": ["openshift.io/deployment-config.latest-version"],
"displayName": ["openshift.io/display-name"],
"description": ["openshift.io/description"],
"buildNumber": ["openshift.io/build.number"]
};
.filter('annotationName', function() {
return function(annotationKey) {
// This maps an annotation key to all known synonymous keys to insulate
// the referring code from key renames across API versions.
var annotationMap = {
"deploymentConfig": ["openshift.io/deployment-config.name"],
"deployment": ["openshift.io/deployment.name"],
"pod": ["openshift.io/deployer-pod.name"],
"deployerPodFor": ["openshift.io/deployer-pod-for.name"],
"deploymentStatus": ["openshift.io/deployment.phase"],
"deploymentStatusReason": ["openshift.io/deployment.status-reason"],
"deploymentCancelled": ["openshift.io/deployment.cancelled"],
"encodedDeploymentConfig": ["openshift.io/encoded-deployment-config"],
"deploymentVersion": ["openshift.io/deployment-config.latest-version"],
"displayName": ["openshift.io/display-name"],
"description": ["openshift.io/description"],
"buildNumber": ["openshift.io/build.number"]
};
return annotationMap[annotationKey] || null;
};
})
.filter('annotation', function(annotationNameFilter) {
return function(resource, key) {
if (resource && resource.metadata && resource.metadata.annotations) {
// If the key's already in the annotation map, return it.
if (resource.metadata.annotations[key] !== undefined) {
return resource.metadata.annotations[key];
}
// Try and return a value for a mapped key.
var mappings = annotationMap[key] || [];
var mappings = annotationNameFilter(key) || [];
for (var i=0; i < mappings.length; i++) {
var mappedKey = mappings[i];
if (resource.metadata.annotations[mappedKey] !== undefined) {
Expand Down
32 changes: 32 additions & 0 deletions assets/app/scripts/services/data.js
Expand Up @@ -166,6 +166,38 @@ angular.module('openshiftConsole')
return deferred.promise;
};

// type: API type (e.g. "pods")
// name: API name, the unique name for the object
// object: API object data(eg. { kind: "Build", parameters: { ... } } )
// context: API context (e.g. {project: "..."})
// opts: http - options to pass to the inner $http call
// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
DataService.prototype.update = function(type, name, object, context, opts) {
type = normalizeType(type);
opts = opts || {};
var deferred = $q.defer();
var self = this;
this._getNamespace(type, context, opts).then(function(ns){
$http(angular.extend({
method: 'PUT',
data: object,
url: self._urlForType(type, name, context, false, ns)
}, opts.http || {}))
.success(function(data, status, headerFunc, config, statusText) {
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
deferred.reject({
data: data,
status: status,
headers: headers,
config: config
});
});
});
return deferred.promise;
};

// type: API type (e.g. "pods")
// name: API name, the unique name for the object.
// In case the name of the Object is provided, expected format of 'type' parameter is 'type/subresource', eg: 'buildconfigs/instantiate'.
Expand Down

0 comments on commit 71558bc

Please sign in to comment.