651.288.7000 info@intertech.com

TFS Tutorial (Part 2) – REST Service Release Definition Creation

by | Feb 16, 2017

If you would like a little background on this TFS Tutorial, in the series.

In the first article of this TFS Tutorial series, I showed how to create the build definition. Now, for this part, I will show you how to create the release definition.  This will be very similar to the build definition since they share the same base class.  When researching the release definition services online they only showed the call with a JSON request and response.  When I took the exact same JSON object and passed it to the service, I didn’t get the expected 200 result. Instead, I was returned with an exception.  Then I used Redgate’s reflector and searched the TFS assemblies and found that the service was calling a method in the Microsoft.VisualStudio.Services.ReleaseManagement2.Server assembly under the namespace Microsoft.VisualStudio.Services.ReleaseManagement.Server.Controllers.RmDefinitionsController.  The method was CreateReleaseDefinition.

TFS Tutorial example code

After finding the method, I was able to figure out that I could use the ReleaseDefinition object located in the Microsoft.VisualStudio.Services.ReleaseManagement.WebApi assembly under the same namespace.

In order to create a build definition in this TFS Tutorial, I created three projects:

1. Tfs.Common – This project is used for constants and any utility methods that will be shared across any TFS project.

2. Configuration – This project is used to define build templates. Alone with showing how to map variables.

3. TFS.RestServiceCaller – This project is the one that will make the actual calls to the REST services.

All the calls for the builds are in the Intertech.TFS.RestServiceCaller.Api.RestReleaseDefinition namespace.  The namespace has the following methods:

1. CreateReleaseDefinition

a. Parameters

i. template – Is the template that you are basing your release definition off of

ii. buildArtifact – The build definition that will be tied to this release

iii. itemInfo – Is model object which contains branch information

iv. releaseTemplate – Which is the configuration information from a config file

b. Returns

i. A release definition

2. DeleteReleaseDefinition

a. Parameters

i. itemInfo – Is used to build a build definition name to find it from the collection of build definitions and then delete it using the found definition’s id

3. DeleteReleaseDefinitionById

a. Parameters

i. definitionId – Is the id of the build definition to delete

4. GetReleaseDefinitionById

a. Parameters

i. definitionId – Is the id of the build definition to retrieve

b. Returns

i. A release definition

5. GetReleaseDefinitions

a. Returns

i. A collection of release definitions

Currently the CreateBuildDefinition and DeleteBuildDefinition are using a naming convention of:


ProgramName and BranchName are properties of the ChangeSetItemInfo model.  This could be changed to be more generic in the future but for the current implementation if suited my client’s needs.

Most of the methods either take no parameter or take one and find what you are looking for. The create method does a little more so I will walk through that method too.

public ReleaseDefinition CreateReleaseDefinition(ReleaseDefinition template, BuildDefinition buildArtifact, ChangeSetItemInfo itemInfo, ReleaseTemplateElement releaseTemplate) {    
    var paramList = new List < object > {    
        template, buildArtifact, itemInfo    
    var releaseDefinition = new ReleaseDefinition {    
        Name = %code%nbsp;"{itemInfo.ProgramName}_{itemInfo.BranchName}"    
    releaseDefinition.Environments = new List < ReleaseDefinitionEnvironment > ();    
    foreach(var environment in template.Environments) {    
        var configEnvironemt = releaseTemplate.Environments.SingleOrDefault(en => en.Name == environment.Name);    
        if (configEnvironemt != null) PopulateVariables(configEnvironemt.Variables, environment.Variables, paramList);    
        var sourcePath = environment.DeployStep.Tasks[0].Inputs["SourcePath"];    
        var sourPathElements = sourcePath.Split('/');    
        sourPathElements[sourPathElements.Length - 2] = buildArtifact.Name;    
        sourcePath = string.Join("/", sourPathElements);    
        environment.DeployStep.Tasks[0].Inputs["SourcePath"] = sourcePath;    
    releaseDefinition.RetentionPolicy = template.RetentionPolicy;    
    releaseDefinition.Triggers = template.Triggers;    
    releaseDefinition.Triggers[0].ArtifactAlias = buildArtifact.Name;    
    PopulateVariables(releaseTemplate.Variables, releaseDefinition.Variables, paramList);    
    releaseDefinition.Artifacts = template.Artifacts;    
    releaseDefinition.Artifacts[0].Alias = buildArtifact.Name;    
    releaseDefinition.Artifacts[0].DefinitionReference["definition"].Id = buildArtifact.Id.ToString();    
    releaseDefinition.Artifacts[0].DefinitionReference["definition"].Name = buildArtifact.Name;    
    var resultString = RestCaller.MakeHttpCall(string.Format(Constants.TfsCreateReleaseDefinitionPattern, ApiVersion), releaseDefinition, HttpMethod.Post).Result;    
    var returnedReleaseDefinition = JsonConvert.DeserializeObject < ReleaseDefinition > (resultString);    
    return returnedReleaseDefinition;    

At the start of the method I create an array that will be used when processing the definition variables.  Then we create the name of the definition which I explained above. After the name is created, we start to copy the environment information from our base definition to our newly created release definition.  If the environment name is found in our configuration section, we will populate any variable under that section.  Below we will show how the variables are populated. After looping through all the environments, we will copy the retention policy and triggers. Then on line 23 we process the definition variables.  In PopulateVariables we loop through every variable in our configuration section and either match it to a variable in the existing variable definition collection or we create it.  The first thing we check is if the VariableValue element has a value or exists.  If it does that means that this is a hard coded value and we just use it.  If a VariableValue doesn’t exist we use the VariableType, VariableProp configuration properties to try to find that type with the appropriate property in the array we created at the beginning of the CreateBuildDefinition method.  If the property does exist, we get its value and assign it to the definition variable.

public void PopulateVariables(VariableCollection variables, IDictionary < string, BuildDefinitionVariable > definitionVaribles, List < object > paramList) {    
foreach(VariableElement ele in variables) {    
var propertyTypeToGetValue = Type.GetType(ele.VariableType);    
var val = string.Empty;    
if (!string.IsNullOrWhiteSpace(ele.VariableValue)) {    
val = ele.VariableValue;    
} else {    
var foundParam = paramList.SingleOrDefault(pl => pl.GetType() == propertyTypeToGetValue);    
if (foundParam != null && foundParam.GetType().GetProperties().ToList().Exists(p => p.Name == ele.VariableProp)) {    
var prop = foundParam.GetType().GetProperty(ele.VariableProp);    
var propValue = prop.GetValue(foundParam);    
val = propValue.ToString();    
bool exists = definitionVaribles.ContainsKey(ele.Name);    
if (exists) definitionVaribles[ele.Name].Value = val;    
else definitionVaribles.Add(ele.Name, new BuildDefinitionVariable {    
Value = val    

After all the variables are populated we associate the build to the release definition. Finally, at the end we call the REST service with our newly created release definition and hopefully get a newly created release definition.

You can get the source code for the complete plugin here.

Check out the other posts in this series:

TFS Tutorial (Part 1) – REST Service Build Definition Creation

TFS Tutorial (Part 3) – TFS Plugin

Intertech both consults and instructs TFS! Check out our TFS Training courses and our services.