Skip to content

Commit

Permalink
Add support to manage project features (#17)
Browse files Browse the repository at this point in the history
Changing the artifact storage for a project is done through project
features, add support to the TeamCity client used by our Terraform
provider to allow managing project features

This was based on existing code to manage build features

TEST=manual

Testing was done in conjunction with changes done with the Terraform
provider, applied a pipeline to a local TeamCity where a TeamCity
project was made with some random project feature
  • Loading branch information
andrewsheng2 authored Sep 20, 2023
1 parent 7cb1b17 commit 3a0a9e1
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 20 deletions.
23 changes: 11 additions & 12 deletions teamcity/build_feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/dghubble/sling"
)

//BuildFeature is an interface representing different types of build features that can be added to a build type.
// BuildFeature is an interface representing different types of build features that can be added to a build type.
type BuildFeature interface {
ID() string
SetID(value string)
Expand Down Expand Up @@ -43,7 +43,7 @@ type Features struct {
Items []buildFeatureJSON `json:"feature"`
}

//BuildFeatureService provides operations for managing build features for a buildType
// BuildFeatureService provides operations for managing build features for a buildType
type BuildFeatureService struct {
BuildTypeID string
httpClient *http.Client
Expand All @@ -61,7 +61,7 @@ func newBuildFeatureService(buildTypeID string, c *http.Client, base *sling.Slin
}
}

//Create adds a new build feature to build type
// Create adds a new build feature to build type
func (s *BuildFeatureService) Create(bf BuildFeature) (BuildFeature, error) {
if bf == nil {
return nil, errors.New("bf can't be nil")
Expand All @@ -87,7 +87,7 @@ func (s *BuildFeatureService) Create(bf BuildFeature) (BuildFeature, error) {
return s.readBuildFeatureResponse(resp)
}

//GetByID returns a build feature by its id
// GetByID returns a build feature by its id
func (s *BuildFeatureService) GetByID(id string) (BuildFeature, error) {
req, err := s.base.New().Get(id).Request()

Expand Down Expand Up @@ -126,18 +126,18 @@ func (s *BuildFeatureService) GetBuildFeatures() ([]BuildFeature, error) {
return nil, err
}

cbf := GenericBuildFeature{}
err = cbf.UnmarshalJSON(dt)
gbf := GenericBuildFeature{}
err = gbf.UnmarshalJSON(dt)
if err != nil {
return nil, err
}
buildFeatures[i] = &cbf
buildFeatures[i] = &gbf
}

return buildFeatures, nil
}

//Delete removes a build feature from the build configuration by its id.
// Delete removes a build feature from the build configuration by its id.
func (s *BuildFeatureService) Delete(id string) error {
request, _ := s.base.New().Delete(id).Request()
response, err := s.httpClient.Do(request)
Expand Down Expand Up @@ -207,14 +207,13 @@ func (s *BuildFeatureService) readBuildFeatureResponse(resp *http.Response) (Bui
if err := csp.UnmarshalJSON(bodyBytes); err != nil {
return nil, err
}

out = &csp
default:
var cbf GenericBuildFeature
if err := cbf.UnmarshalJSON(bodyBytes); err != nil {
var gbf GenericBuildFeature
if err := gbf.UnmarshalJSON(bodyBytes); err != nil {
return nil, err
}
return out, nil
out = &gbf
}

out.SetBuildTypeID(s.BuildTypeID)
Expand Down
94 changes: 94 additions & 0 deletions teamcity/generic_project_feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package teamcity

import (
"encoding/json"
)

type GenericProjectFeature struct {
id string
featureType string
projectID string
disabled bool
properties *Properties
}

func (pf *GenericProjectFeature) ID() string {
return pf.id
}

func (pf *GenericProjectFeature) SetID(value string) {
pf.id = value
}

func (pf *GenericProjectFeature) Type() string {
return pf.featureType
}

func (pf *GenericProjectFeature) Properties() *Properties {
return pf.properties
}

func (pf *GenericProjectFeature) ProjectID() string {
return pf.projectID
}

func (pf *GenericProjectFeature) SetProjectID(value string) {
pf.projectID = value
}

func (pf *GenericProjectFeature) Disabled() bool {
return pf.disabled
}

func (pf *GenericProjectFeature) SetDisabled(value bool) {
pf.disabled = value
}

func (pf *GenericProjectFeature) MarshalJSON() ([]byte, error) {
out := &projectFeatureJSON{
ID: pf.id,
Disabled: NewBool(pf.disabled),
Properties: pf.properties,
Inherited: NewFalse(),
Type: pf.Type(),
}

return json.Marshal(out)
}

func (pf *GenericProjectFeature) UnmarshalJSON(data []byte) error {
var aux projectFeatureJSON
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
pf.id = aux.ID
pf.featureType = aux.Type

disabled := aux.Disabled
if disabled == nil {
disabled = NewFalse()
}
pf.disabled = *disabled

if aux.Properties != nil {
pf.properties = NewProperties(aux.Properties.Items...)
}

return nil
}

func NewGenericProjectFeature(featureType string, propertiesRaw map[string]interface{}) (*GenericProjectFeature, error) {
properties := NewPropertiesEmpty()
for name, value := range propertiesRaw {
value := value.(string)
properties.Add(&Property{
Name: name,
Value: value,
})
}

return &GenericProjectFeature{
featureType: featureType,
properties: properties,
}, nil
}
197 changes: 197 additions & 0 deletions teamcity/project_feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package teamcity

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"

"github.com/dghubble/sling"
)

// ProjectFeature is an interface representing different types of project features that can be added to a project.
type ProjectFeature interface {
ID() string
SetID(value string)
Type() string
Properties() *Properties
ProjectID() string
SetProjectID(value string)
Disabled() bool
SetDisabled(value bool)
MarshalJSON() ([]byte, error)
UnmarshalJSON(data []byte) error
}

type projectFeatureJSON struct {
Disabled *bool `json:"disabled,omitempty" xml:"disabled"`
Href string `json:"href,omitempty" xml:"href"`
ID string `json:"id,omitempty" xml:"id"`
Inherited *bool `json:"inherited,omitempty" xml:"inherited"`
Name string `json:"name,omitempty" xml:"name"`
Properties *Properties `json:"properties,omitempty"`
Type string `json:"type,omitempty" xml:"type"`
}

// ProjectFeatures is a collection of ProjectFeature
type ProjectFeatures struct {
Count int32 `json:"count,omitempty" xml:"count"`
Href string `json:"href,omitempty" xml:"href"`
Items []projectFeatureJSON `json:"projectFeature"`
}

// ProjectFeatureService provides operations for managing project features for a project
type ProjectFeatureService struct {
ProjectID string
httpClient *http.Client
base *sling.Sling
restHelper *restHelper
}

func newProjectFeatureService(projectID string, c *http.Client, base *sling.Sling) *ProjectFeatureService {
slingName := base.New().Path(fmt.Sprintf("projects/%s/projectFeatures/", projectID))
return &ProjectFeatureService{
ProjectID: projectID,
httpClient: c,
base: slingName,
restHelper: newRestHelperWithSling(c, slingName),
}
}

// Create adds a new project feature to project
func (s *ProjectFeatureService) Create(pf ProjectFeature) (ProjectFeature, error) {
if pf == nil {
return nil, errors.New("pf can't be nil")
}

req, err := s.base.New().Post("").BodyJSON(pf).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("Unknown error when adding project feature, statusCode: %d", resp.StatusCode)
}

return s.readProjectFeatureResponse(resp)
}

// GetByID returns a project feature by its id
func (s *ProjectFeatureService) GetByID(id string) (ProjectFeature, error) {
req, err := s.base.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 == 404 {
return nil, fmt.Errorf("404 Not Found - Project feature (id: %s) for projectId (id: %s) was not found", id, s.ProjectID)
}

return s.readProjectFeatureResponse(resp)
}

// GetProjectFeatures gets all the project features of a Project
func (s *ProjectFeatureService) GetProjectFeatures() ([]ProjectFeature, error) {
var features ProjectFeatures
err := s.restHelper.get("", &features, "project features")
if err != nil {
return nil, err
}

projectFeatures := make([]ProjectFeature, features.Count)

for i := range features.Items {
dt, err := json.Marshal(features.Items[i])
if err != nil {
return nil, err
}

gpf := GenericProjectFeature{}
err = gpf.UnmarshalJSON(dt)
if err != nil {
return nil, err
}
projectFeatures[i] = &gpf
}

return projectFeatures, nil
}

// Delete removes a project feature from the project configuration by its id.
func (s *ProjectFeatureService) Delete(id string) error {
request, _ := s.base.New().Delete(id).Request()
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 project feature: %s", response.StatusCode, string(respData))
}

return nil
}

// DeleteAll removes all project features of a project configuration
func (s *ProjectFeatureService) DeleteAll() error {
features, err := s.GetProjectFeatures()
if err != nil {
return err
}

for _, feature := range features {
if err := s.Delete(feature.ID()); err != nil {
return err
}
}

return nil
}

func (s *ProjectFeatureService) readProjectFeatureResponse(resp *http.Response) (ProjectFeature, error) {
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var payload projectFeatureJSON
if err := json.Unmarshal(bodyBytes, &payload); err != nil {
return nil, err
}

var out ProjectFeature
var gpf GenericProjectFeature
if err := gpf.UnmarshalJSON(bodyBytes); err != nil {
return nil, err
}
out = &gpf
out.SetProjectID(s.ProjectID)
return out, nil
}
Loading

0 comments on commit 3a0a9e1

Please sign in to comment.