diff --git a/packages/repositories.config b/packages/repositories.config
index bcd17bb0..3468aa5e 100644
--- a/packages/repositories.config
+++ b/packages/repositories.config
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/src/TeamCitySharp/ActionTypes/BuildConfigs.cs b/src/TeamCitySharp/ActionTypes/BuildConfigs.cs
index cdcd4a0f..83cb8a85 100644
--- a/src/TeamCitySharp/ActionTypes/BuildConfigs.cs
+++ b/src/TeamCitySharp/ActionTypes/BuildConfigs.cs
@@ -66,7 +66,7 @@ public BuildConfig ByProjectIdAndConfigurationId(string projectId, string buildC
public List ByProjectId(string projectId)
{
- var buildWrapper = _caller.GetFormat("/app/rest/projects/id:{0}/buildTypes", projectId);
+ var buildWrapper = _caller.GetFormat("/app/rest/projects/{0}/buildTypes", projectId);
if (buildWrapper == null || buildWrapper.BuildType == null) return new List();
return buildWrapper.BuildType;
diff --git a/src/TeamCitySharp/ActionTypes/IProjects.cs b/src/TeamCitySharp/ActionTypes/IProjects.cs
index febce5c2..b1de0aa4 100644
--- a/src/TeamCitySharp/ActionTypes/IProjects.cs
+++ b/src/TeamCitySharp/ActionTypes/IProjects.cs
@@ -10,8 +10,10 @@ public interface IProjects
Project ById(string projectLocatorId);
Project Details(Project project);
Project Create(string projectName);
+ Project Create(string projectName, string projectId);
void Delete(string projectName);
void DeleteProjectParameter(string projectName, string parameterName);
void SetProjectParameter(string projectName, string settingName, string settingValue);
+ bool SetName(string projectCode, string name);
}
}
\ No newline at end of file
diff --git a/src/TeamCitySharp/ActionTypes/IUserGroups.cs b/src/TeamCitySharp/ActionTypes/IUserGroups.cs
new file mode 100644
index 00000000..54d35f78
--- /dev/null
+++ b/src/TeamCitySharp/ActionTypes/IUserGroups.cs
@@ -0,0 +1,9 @@
+namespace TeamCitySharp.ActionTypes
+{
+ public interface IUserGroups
+ {
+ bool AddGroup(string groupKey, string groupName);
+ bool RemoveGroup(string groupKey);
+ bool AddRoleToGroup(string groupKey, string roleId, string projectKey);
+ }
+}
diff --git a/src/TeamCitySharp/ActionTypes/IUsers.cs b/src/TeamCitySharp/ActionTypes/IUsers.cs
index 7d60ec9d..5727be71 100644
--- a/src/TeamCitySharp/ActionTypes/IUsers.cs
+++ b/src/TeamCitySharp/ActionTypes/IUsers.cs
@@ -14,5 +14,6 @@ public interface IUsers
List AllUserRolesByUserGroup(string userGroupName);
bool Create(string username, string name, string email, string password);
bool AddPassword(string username, string password);
+ bool AddUserToGroup(string username, string groupKey);
}
}
\ No newline at end of file
diff --git a/src/TeamCitySharp/ActionTypes/IVcsRoots.cs b/src/TeamCitySharp/ActionTypes/IVcsRoots.cs
index 6e2b381a..cf00a596 100644
--- a/src/TeamCitySharp/ActionTypes/IVcsRoots.cs
+++ b/src/TeamCitySharp/ActionTypes/IVcsRoots.cs
@@ -11,5 +11,6 @@ public interface IVcsRoots
VcsRoot AttachVcsRoot(BuildTypeLocator locator, VcsRoot vcsRoot);
void DetachVcsRoot(BuildTypeLocator locator, string vcsRootId);
void SetVcsRootField(VcsRoot vcsRoot, VcsRootField field, object value);
+ VcsRoot Create(VcsRoot root);
}
}
\ No newline at end of file
diff --git a/src/TeamCitySharp/ActionTypes/Projects.cs b/src/TeamCitySharp/ActionTypes/Projects.cs
index d19ec642..1bdfd640 100644
--- a/src/TeamCitySharp/ActionTypes/Projects.cs
+++ b/src/TeamCitySharp/ActionTypes/Projects.cs
@@ -5,6 +5,10 @@
namespace TeamCitySharp.ActionTypes
{
+ using System.Net;
+
+ using EasyHttp.Infrastructure;
+
internal class Projects : IProjects
{
private readonly TeamCityCaller _caller;
@@ -23,16 +27,12 @@ public List All()
public Project ByName(string projectLocatorName)
{
- var project = _caller.GetFormat("/app/rest/projects/name:{0}", projectLocatorName);
-
- return project;
+ return GetProject(string.Format("name:{0}", projectLocatorName));
}
public Project ById(string projectLocatorId)
{
- var project = _caller.GetFormat("/app/rest/projects/id:{0}", projectLocatorId);
-
- return project;
+ return GetProject(projectLocatorId);
}
public Project Details(Project project)
@@ -45,6 +45,37 @@ public Project Create(string projectName)
return _caller.Post(projectName, HttpContentTypes.TextPlain, "/app/rest/projects/", HttpContentTypes.ApplicationJson);
}
+ public bool SetName(string projectCode, string name)
+ {
+ try
+ {
+ var response = _caller.Put(name, HttpContentTypes.TextPlain, string.Format("/app/rest/projects/{0}/name", projectCode), null);
+ return response.StatusCode == HttpStatusCode.OK;
+ }
+ catch (HttpException ex)
+ {
+ if (ex.StatusCode == HttpStatusCode.NotFound)
+ {
+ return false;
+ }
+
+ throw;
+ }
+ }
+
+ public Project Create(string projectName, string projectId)
+ {
+
+ var content = string.Format
+ (@" "
+ , projectName, projectId);
+ /* extended xml version:
+ *
+ * more details could be found in documentation: https://confluence.jetbrains.com/display/TCD9/REST+API#RESTAPI-ProjectSettings
+ */
+ return _caller.Post(content, HttpContentTypes.ApplicationXml, "/app/rest/projects/", HttpContentTypes.ApplicationJson);
+ }
+
public void Delete(string projectName)
{
_caller.DeleteFormat("/app/rest/projects/name:{0}", projectName);
@@ -59,5 +90,23 @@ public void SetProjectParameter(string projectName, string settingName, string s
{
_caller.PutFormat(settingValue, "/app/rest/projects/name:{0}/parameters/{1}", projectName, settingName);
}
+
+ private Project GetProject(string locator)
+ {
+ try
+ {
+ var project = _caller.GetFormat("/app/rest/projects/{0}", locator);
+ return project;
+ }
+ catch (HttpException ex)
+ {
+ if (ex.StatusCode == HttpStatusCode.NotFound)
+ {
+ return null;
+ }
+
+ throw;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/TeamCitySharp/ActionTypes/UserGroups.cs b/src/TeamCitySharp/ActionTypes/UserGroups.cs
new file mode 100644
index 00000000..1cdddd18
--- /dev/null
+++ b/src/TeamCitySharp/ActionTypes/UserGroups.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Net;
+
+using EasyHttp.Http;
+
+using TeamCitySharp.Connection;
+using TeamCitySharp.Util;
+
+namespace TeamCitySharp.ActionTypes
+{
+ using EasyHttp.Infrastructure;
+
+ internal class UserGroups : IUserGroups
+ {
+ private readonly TeamCityCaller _caller;
+
+ internal UserGroups(TeamCityCaller caller)
+ {
+ _caller = caller;
+ }
+
+ public bool AddGroup(string groupKey, string groupName)
+ {
+ ArgumentUtil.CheckNotNull(() => groupKey, () => groupName);
+
+ var attributesDictionary = new Dictionary { { "key", groupKey }, { "name", groupName } };
+ var payload = XmlUtil.SingleElementDocument("group", attributesDictionary);
+ var response = _caller.Post(payload, HttpContentTypes.ApplicationXml, "/app/rest/userGroups", null);
+
+ return response.StatusCode == HttpStatusCode.OK;
+ }
+
+ public bool RemoveGroup(string groupKey)
+ {
+ ArgumentUtil.CheckNotNull(() => groupKey);
+
+ try
+ {
+ _caller.Delete(string.Format("/app/rest/userGroups/key:{0}", groupKey));
+ return true;
+ }
+ catch (HttpException ex)
+ {
+ if (ex.StatusCode == HttpStatusCode.NotFound)
+ {
+ return false;
+ }
+
+ throw;
+ }
+ }
+
+ public bool AddRoleToGroup(string groupKey, string roleId, string projectKey)
+ {
+ ArgumentUtil.CheckNotNull(() => groupKey, () => roleId, () => projectKey);
+
+ var attributesDictionary = new Dictionary { { "roleId", roleId }, { "scope", string.Format("p:{0}", projectKey) } };
+ var payload = XmlUtil.SingleElementDocument("role", attributesDictionary);
+ var response = _caller.Post(payload, HttpContentTypes.ApplicationXml, string.Format("/app/rest/userGroups/key:{0}/roles", groupKey), null);
+
+ return response.StatusCode == HttpStatusCode.OK;
+ }
+ }
+}
diff --git a/src/TeamCitySharp/ActionTypes/Users.cs b/src/TeamCitySharp/ActionTypes/Users.cs
index 6b494ac8..3f5b3c91 100644
--- a/src/TeamCitySharp/ActionTypes/Users.cs
+++ b/src/TeamCitySharp/ActionTypes/Users.cs
@@ -4,8 +4,13 @@
using TeamCitySharp.Connection;
using TeamCitySharp.DomainEntities;
+using TeamCitySharp.Locators;
+using TeamCitySharp.Util;
+
namespace TeamCitySharp.ActionTypes
{
+ using EasyHttp.Infrastructure;
+
internal class Users : IUsers
{
private readonly TeamCityCaller _caller;
@@ -100,5 +105,31 @@ public bool AddPassword(string username, string password)
return result;
}
+ public bool AddUserToGroup(string username, string groupKey)
+ {
+ ArgumentUtil.CheckNotNull(() => username, () => groupKey);
+
+ var attributesDictionary = new Dictionary { { "key", groupKey } };
+ var payload = XmlUtil.SingleElementDocument("group", attributesDictionary);
+
+ try
+ {
+ var response = _caller.Post(
+ payload,
+ HttpContentTypes.ApplicationXml,
+ string.Format("/app/rest/users/{0}/groups", UserLocator.WithUserName(username)),
+ null);
+ return response.StatusCode == HttpStatusCode.OK;
+ }
+ catch (HttpException ex)
+ {
+ if (ex.StatusCode == HttpStatusCode.NotFound)
+ {
+ return false;
+ }
+
+ throw;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/TeamCitySharp/ActionTypes/VcsRoots.cs b/src/TeamCitySharp/ActionTypes/VcsRoots.cs
index 9e1e95a9..8b3de6a5 100644
--- a/src/TeamCitySharp/ActionTypes/VcsRoots.cs
+++ b/src/TeamCitySharp/ActionTypes/VcsRoots.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Text;
using EasyHttp.Http;
using TeamCitySharp.Connection;
using TeamCitySharp.DomainEntities;
@@ -30,6 +32,35 @@ public VcsRoot ById(string vcsRootId)
return vcsRoot;
}
+
+ public VcsRoot Create(VcsRoot root)
+ {
+ var sb = new StringBuilder();
+ var rootStr =
+ string.Format(
+ @""
+ ,root.Id,root.Name,root.vcsName,root.Status);
+ sb.Append(rootStr);
+ var projectStr =
+ string.Format(
+ @""
+ ,root.Project.Id,root.Project.Name);
+ sb.Append(projectStr);
+ if (root.Properties != null && root.Properties.Property != null && root.Properties.Property.Any())
+ {
+ sb.Append(string.Format(@"", root.Properties.Property.Count));
+ foreach (var property in root.Properties.Property)
+ {
+ sb.Append(string.Format(@"", property.Name, property.Value));
+ }
+ sb.Append(@"");
+ }
+ sb.Append("");
+ var xml = sb.ToString();
+
+ return _caller.PostFormat(xml, HttpContentTypes.ApplicationXml, HttpContentTypes.ApplicationJson, "/app/rest/vcs-roots/");
+ }
+
public VcsRoot AttachVcsRoot(BuildTypeLocator locator, VcsRoot vcsRoot)
{
var xml = string.Format(@"", vcsRoot.Id);
diff --git a/src/TeamCitySharp/DomainEntities/Role.cs b/src/TeamCitySharp/DomainEntities/Role.cs
index b2fc41b0..f423633b 100644
--- a/src/TeamCitySharp/DomainEntities/Role.cs
+++ b/src/TeamCitySharp/DomainEntities/Role.cs
@@ -2,6 +2,9 @@
{
public class Role
{
+ public const string ProjectAdministrator = "PROJECT_ADMIN";
+ public const string ProjectDeveloper = "PROJECT_DEVELOPER";
+
public string Href { get; set; }
public string Scope { get; set; }
public string RoleId { get; set; }
diff --git a/src/TeamCitySharp/DomainEntities/VcsRoot.cs b/src/TeamCitySharp/DomainEntities/VcsRoot.cs
index 93411c59..4c3d92cb 100644
--- a/src/TeamCitySharp/DomainEntities/VcsRoot.cs
+++ b/src/TeamCitySharp/DomainEntities/VcsRoot.cs
@@ -18,5 +18,7 @@ public override string ToString()
}
public Properties Properties { get; set; }
+ public Project Project { get; set; }
+
}
}
\ No newline at end of file
diff --git a/src/TeamCitySharp/ITeamCityClient.cs b/src/TeamCitySharp/ITeamCityClient.cs
index f63f97d3..243ece1d 100644
--- a/src/TeamCitySharp/ITeamCityClient.cs
+++ b/src/TeamCitySharp/ITeamCityClient.cs
@@ -13,6 +13,7 @@ public interface ITeamCityClient
IProjects Projects { get; }
IServerInformation ServerInformation { get; }
IUsers Users { get; }
+ IUserGroups UserGroups { get; }
IAgents Agents { get; }
IVcsRoots VcsRoots { get; }
IChanges Changes { get; }
diff --git a/src/TeamCitySharp/TeamCityClient.cs b/src/TeamCitySharp/TeamCityClient.cs
index eeb9bb82..42c8279a 100644
--- a/src/TeamCitySharp/TeamCityClient.cs
+++ b/src/TeamCitySharp/TeamCityClient.cs
@@ -11,11 +11,12 @@ public class TeamCityClient : IClientConnection, ITeamCityClient
private IBuildConfigs _buildConfigs;
private IServerInformation _serverInformation;
private IUsers _users;
+ private IUserGroups _userGroups;
private IAgents _agents;
private IVcsRoots _vcsRoots;
private IChanges _changes;
- private IBuildArtifacts _artifacts;
-
+ private IBuildArtifacts _artifacts;
+
public TeamCityClient(string hostName, bool useSsl = false)
{
_caller = new TeamCityCaller(hostName, useSsl);
@@ -61,6 +62,11 @@ public IUsers Users
get { return _users ?? (_users = new Users(_caller)); }
}
+ public IUserGroups UserGroups
+ {
+ get { return _userGroups ?? (_userGroups = new UserGroups(_caller)); }
+ }
+
public IAgents Agents
{
get { return _agents ?? (_agents = new Agents(_caller)); }
diff --git a/src/TeamCitySharp/TeamCitySharp.csproj b/src/TeamCitySharp/TeamCitySharp.csproj
index 2e7df5aa..c5687f98 100644
--- a/src/TeamCitySharp/TeamCitySharp.csproj
+++ b/src/TeamCitySharp/TeamCitySharp.csproj
@@ -23,6 +23,7 @@
DEBUG;TRACE
prompt
4
+ false
pdbonly
@@ -31,6 +32,7 @@
TRACE
prompt
4
+ false
@@ -42,6 +44,7 @@
+
@@ -62,10 +65,12 @@
+
+
@@ -120,6 +125,9 @@
+
+
+
diff --git a/src/TeamCitySharp/Util/ArgumentUtil.cs b/src/TeamCitySharp/Util/ArgumentUtil.cs
new file mode 100644
index 00000000..3304cf9e
--- /dev/null
+++ b/src/TeamCitySharp/Util/ArgumentUtil.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace TeamCitySharp.Util
+{
+ using System.Linq.Expressions;
+
+ internal static class ArgumentUtil
+ {
+ public static void CheckNotNull(params Expression>[] memberExpressions)
+ {
+ foreach (var expression in memberExpressions)
+ {
+ CheckNotNull(expression);
+ }
+ }
+
+ public static void CheckNotNull(Expression> memberExpression)
+ {
+ if (memberExpression.Compile().Invoke() == null)
+ {
+ throw new ArgumentNullException(ReflectionUtil.GetMemberName(memberExpression));
+ }
+ }
+ }
+}
diff --git a/src/TeamCitySharp/Util/ReflectionUtil.cs b/src/TeamCitySharp/Util/ReflectionUtil.cs
new file mode 100644
index 00000000..153e9db7
--- /dev/null
+++ b/src/TeamCitySharp/Util/ReflectionUtil.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Linq.Expressions;
+
+namespace TeamCitySharp.Util
+{
+ internal static class ReflectionUtil
+ {
+ public static string GetMemberName(Expression> memberExpression)
+ {
+ var expressionBody = (MemberExpression)memberExpression.Body;
+ return expressionBody.Member.Name;
+ }
+ }
+}
diff --git a/src/TeamCitySharp/Util/XmlUtil.cs b/src/TeamCitySharp/Util/XmlUtil.cs
new file mode 100644
index 00000000..3e10b1d2
--- /dev/null
+++ b/src/TeamCitySharp/Util/XmlUtil.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+
+namespace TeamCitySharp.Util
+{
+ using System.IO;
+ using System.Xml;
+
+ internal class XmlUtil
+ {
+ public static string SingleElementDocument(string elementName, IDictionary attributes)
+ {
+ var stringWriter = new StringWriter();
+
+ using (var writer = new XmlTextWriter(stringWriter))
+ {
+ writer.WriteStartElement(elementName);
+
+ foreach (var attribute in attributes)
+ {
+ if (string.IsNullOrEmpty(attribute.Value))
+ {
+ throw new ArgumentNullException(attribute.Key);
+ }
+
+ writer.WriteAttributeString(attribute.Key, attribute.Value);
+ }
+
+ writer.WriteEndElement();
+ }
+
+ return stringWriter.ToString();
+ }
+ }
+}
diff --git a/src/Tests/IntegrationTests/SampleProjectUsage.cs b/src/Tests/IntegrationTests/SampleProjectUsage.cs
index 5bcd7bd4..bcd48b06 100644
--- a/src/Tests/IntegrationTests/SampleProjectUsage.cs
+++ b/src/Tests/IntegrationTests/SampleProjectUsage.cs
@@ -96,6 +96,23 @@ public void it_returns_project_details_when_creating_project()
Assert.That(project, Is.Not.Null);
Assert.That(project.Name, Is.EqualTo(projectName));
+ }
+
+
+
+ [Test]
+ public void it_returns_project_details_when_creating_project_with_project_id()
+ {
+ var client = new TeamCityClient("localhost:81");
+ client.Connect("admin", "qwerty");
+ var projectName = Guid.NewGuid().ToString("N");
+ var projectId = Guid.NewGuid().ToString("N");
+
+ var project = client.Projects.Create(projectName, projectId);
+
+ Assert.That(project, Is.Not.Null);
+ Assert.That(project.Name, Is.EqualTo(projectName));
+ Assert.That(project.Id, Is.EqualTo(projectId));
}
}
}
diff --git a/src/Tests/IntegrationTests/SampleVcsUsage.cs b/src/Tests/IntegrationTests/SampleVcsUsage.cs
index 9be8c268..4fc7d7e2 100644
--- a/src/Tests/IntegrationTests/SampleVcsUsage.cs
+++ b/src/Tests/IntegrationTests/SampleVcsUsage.cs
@@ -2,7 +2,9 @@
using System.Net;
using NUnit.Framework;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
+using System.Xml.Serialization;
using TeamCitySharp.DomainEntities;
namespace TeamCitySharp.IntegrationTests
@@ -66,5 +68,24 @@ public void it_returns_vcs_details_when_passing_vcs_root_id(string vcsRootId)
Assert.That(rootDetails != null, "Cannot find the specific VCSRoot");
}
+
+ [TestCase("1")]
+ public void it_serializes_vcs_root_to_xml(string vcsRootId)
+ {
+ VcsRoot rootDetails = _client.VcsRoots.ById(vcsRootId);
+
+
+ rootDetails.Id += "_new_kii";
+ rootDetails.Name+= "_new_kii";
+
+ var vcsRoot = _client.VcsRoots.Create(rootDetails);
+
+ Assert.NotNull(vcsRootId);
+
+ Assert.Equals(vcsRoot.Id, vcsRootId);
+
+ }
+
+
}
}
\ No newline at end of file