forked from cvbarros/go-teamcity
/
vcs_root.go
234 lines (181 loc) · 7 KB
/
vcs_root.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package teamcity
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/dghubble/sling"
)
//VcsRoot interface represents a base type of VCSRoot
type VcsRoot interface {
//GetID returns the ID of this VCS Root
GetID() string
//VcsName returns the type of VCS Root. See VcsNames for possible values returned.
//In addition, this can be used to type assert to the appropriate concrete VCS Root type.
VcsName() string
//Name returns the name of VCS Root.
Name() string
//SetName changes the name of VCSRoot.
SetName(name string)
//ModificationCheckInterval returns how often TeamCity polls the VCS repository for VCS changes, in seconds. Returns an *int32 pointer as this is an optional settings.
//If the return is nil, means that the VCS Root follows the global server setting.
ModificationCheckInterval() *int32
//SetModificationCheckInterval specifies how often TeamCity polls the VCS repository for VCS changes, in seconds.
SetModificationCheckInterval(seconds int32)
//ProjectID returns the projectID where this VCS Root is defined
ProjectID() string
//SetProjectID specifies the project for this VCS Root. When moving VCS Roots between projects, it must not be in use by any other build configurations or sub-projects.
SetProjectID(id string)
//Properties returns the Properties collection for this VCS Root. This should be used for querying only.
Properties() *Properties
}
type vcsRootJSON struct {
// id
ID string `json:"id,omitempty" xml:"id"`
// internal Id
InternalID string `json:"internalId,omitempty" xml:"internalId"`
// ModificationCheckInterval value in seconds to override the global server setting.
ModificationCheckInterval int32 `json:"modificationCheckInterval,omitempty" xml:"modificationCheckInterval"`
// name
Name string `json:"name,omitempty" xml:"name"`
// project
Project *ProjectReference `json:"project,omitempty"`
// Properties for the VCS Root. Do not set directly, instead use NewVcsRoot... constructors.
Properties *Properties `json:"properties,omitempty"`
// VcsName is the VCS Type used for this VCS Root. See VcsNames for allowed values.
// Use NewVcsRoot... constructors to avoid setting this directly.
VcsName string `json:"vcsName,omitempty" xml:"vcsName"`
}
// VcsRootReference represents a subset detail of a VCS Root
type VcsRootReference struct {
// href
Href string `json:"href,omitempty" xml:"href"`
// id
ID string `json:"id,omitempty" xml:"id"`
// name
Name string `json:"name,omitempty" xml:"name"`
// project Id
Project *ProjectReference `json:"project,omitempty" xml:"project"`
}
// VcsRootService has operations for handling vcs roots
type VcsRootService struct {
sling *sling.Sling
httpClient *http.Client
restHelper *restHelper
}
func newVcsRootService(base *sling.Sling, httpClient *http.Client) *VcsRootService {
sling := base.Path("vcs-roots/")
return &VcsRootService{
sling: sling,
httpClient: httpClient,
restHelper: newRestHelperWithSling(httpClient, sling),
}
}
// Create creates a new vcs root
func (s *VcsRootService) Create(projectID string, vcsRoot VcsRoot) (*VcsRootReference, error) {
var created VcsRootReference
err := s.restHelper.post("", vcsRoot, &created, "VcsRoot")
if err != nil {
return nil, err
}
return &created, nil
}
//Update changes the resource in-place for a VCS Root.
//TeamCity API does not support "PUT" on the whole VCS Root resource. Updateable fields are "name", "project" and "modificationCheckInterval".
//This method also updates Settings and Parameters, but this is not an atomic operation. If an error occurs, it will be returned to caller what was updated or not.
func (s *VcsRootService) Update(vcsRoot VcsRoot) (VcsRoot, error) {
var props Properties
//Do a diff change update. Since properties can only be modified individually, check for changes before sending requests.
dt, err := s.GetByID(vcsRoot.GetID())
if err != nil {
return nil, fmt.Errorf("could not refresh VcsRoot for diff prior to update: %s", err)
}
err = s.restHelper.put(fmt.Sprintf("%s/properties", dt.GetID()), vcsRoot.Properties(), &props, "VcsRoot")
if err != nil {
return nil, err
}
if dt.Name() != vcsRoot.Name() {
_, err = s.restHelper.putTextPlain(fmt.Sprintf("%s/name", vcsRoot.GetID()), vcsRoot.Name(), "VcsRoot name field")
if err != nil {
return nil, fmt.Errorf("error when updating 'name' field for VcsRoot. Resource may be in partial update state. %s", err)
}
}
if dt.ProjectID() != vcsRoot.ProjectID() {
_, err = s.restHelper.putTextPlain(fmt.Sprintf("%s/projectId", vcsRoot.GetID()), vcsRoot.ProjectID(), "VcsRoot projectId field")
if err != nil {
return nil, fmt.Errorf("error when updating 'projectId' field for VcsRoot. Resource may be in partial update state. %s", err)
}
}
if dt.ModificationCheckInterval() != vcsRoot.ModificationCheckInterval() && vcsRoot.ModificationCheckInterval() != nil {
v := vcsRoot.ModificationCheckInterval()
_, err = s.restHelper.putTextPlain(fmt.Sprintf("%s/modificationCheckInterval", vcsRoot.GetID()), fmt.Sprintf("%d", *v), "VcsRoot modificationCheckInterval field")
if err != nil {
return nil, fmt.Errorf("error when updating 'modificationCheckInterval' field for VcsRoot. Resource may be in partial update state. %s", err)
}
}
//Refresh after update
updated, err := s.GetByID(vcsRoot.GetID())
if err != nil {
return nil, err
}
return updated, nil
}
// GetByID Retrieves a vcs root by id using the id: locator
func (s *VcsRootService) GetByID(id string) (VcsRoot, error) {
req, err := s.sling.New().Get(id).Request()
if err != nil {
return nil, err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Error when retrieving VcsRoot id = '%s', status: %d", id, resp.StatusCode)
}
return s.readVcsRootResponse(resp)
}
//Delete a VCS Root resource using id: locator
func (s *VcsRootService) Delete(id string) error {
request, _ := s.sling.New().Delete(id).Request()
//TODO: Expose the same httpClient used by sling
response, err := s.httpClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode == 204 {
return nil
}
if response.StatusCode != 200 && response.StatusCode != 204 {
respData, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
return fmt.Errorf("Error '%d' when deleting vcsRoot: %s", response.StatusCode, string(respData))
}
return nil
}
func (s *VcsRootService) readVcsRootResponse(resp *http.Response) (VcsRoot, error) {
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var payload vcsRootJSON
if err := json.Unmarshal(bodyBytes, &payload); err != nil {
return nil, err
}
var out VcsRoot
switch payload.VcsName {
case VcsNames.Git:
var git GitVcsRoot
if err := git.UnmarshalJSON(bodyBytes); err != nil {
return nil, err
}
out = &git
default:
return nil, fmt.Errorf("Unsupported VCS Root type: '%s' (id:'%s') for projectID: %s", payload.VcsName, payload.ID, payload.Project.ID)
}
return out, nil
}