Skip to content

Commit

Permalink
Add commands for deployment targets (#216)
Browse files Browse the repository at this point in the history
* Add commands for deployment targets

* Remove unused code

* Sync with API changes

* Remove useless character

* Rename function, variable

* Fixes after code review, using UUIDv4

* Add create_target_test, used UUIDv4 for mock

* Enrich create target test

* fix object rule mode: pattern->regex

* Remove unused file

* Use DescribeWithSecret instead of optional parameter

* Better handling history cmd, object-rule parsing

* Remove fmt printing
  • Loading branch information
dexyk authored Jun 16, 2023
1 parent ab41985 commit eb094f1
Show file tree
Hide file tree
Showing 36 changed files with 2,038 additions and 74 deletions.
12 changes: 6 additions & 6 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ builds:
- amd64
- arm64
archives:
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
replacements:
darwin: Darwin
linux: Linux
386: i386
amd64: x86_64
- name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
checksum:
name_template: '{{ .ProjectName }}_checksums.txt'
changelog:
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ REL_VERSION=$(shell git rev-parse HEAD)
REL_BUCKET=sem-cli-releases

install.goreleaser:
curl -L https://github.com/goreleaser/goreleaser/releases/download/v1.9.1/goreleaser_Linux_x86_64.tar.gz -o /tmp/goreleaser.tar.gz
curl -L https://github.com/goreleaser/goreleaser/releases/download/v1.14.1/goreleaser_Linux_x86_64.tar.gz -o /tmp/goreleaser.tar.gz
tar -xf /tmp/goreleaser.tar.gz -C /tmp
sudo mv /tmp/goreleaser /usr/bin/goreleaser

go.install:
cd /tmp
sudo curl -O https://dl.google.com/go/go1.16.linux-amd64.tar.gz
sudo tar -xf go1.16.linux-amd64.tar.gz
sudo curl -O https://dl.google.com/go/go1.17.13.linux-amd64.tar.gz
sudo tar -xf go1.17.13.linux-amd64.tar.gz
sudo mv go /usr/local
cd -

Expand Down
10 changes: 6 additions & 4 deletions api/client/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ func (c *BaseClient) Get(kind string, resource string) ([]byte, int, error) {

func (c *BaseClient) List(kind string) ([]byte, int, error) {
url := fmt.Sprintf("https://%s/api/%s/%s", c.host, c.apiVersion, kind)

log.Printf("GET %s\n", url)

req, err := http.NewRequest("GET", url, nil)

if err != nil {
return nil, 0, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.authToken))
req.Header.Set("User-Agent", UserAgent)
Expand Down Expand Up @@ -218,9 +219,10 @@ func (c *BaseClient) PostHeaders(kind string, resource []byte, headers map[strin
func (c *BaseClient) Patch(kind string, name string, resource []byte) ([]byte, int, error) {
url := fmt.Sprintf("https://%s/api/%s/%s/%s", c.host, c.apiVersion, kind, name)

log.Printf("PATCH %s\n", url)

req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(resource))
if err != nil {
return nil, 0, err
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.authToken))
Expand Down
244 changes: 244 additions & 0 deletions api/client/deployment_targets_v1_alpha.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package client

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"

models "github.com/semaphoreci/cli/api/models"
"github.com/semaphoreci/cli/api/uuid"
)

type DeploymentTargetsApiV1AlphaApi struct {
BaseClient BaseClient
ResourceNameSingular string
ResourceNamePlural string
}

const (
DeactivateOpName = "deactivate"
ActivateOpName = "activate"
)

func NewDeploymentTargetsV1AlphaApi() DeploymentTargetsApiV1AlphaApi {
baseClient := NewBaseClientFromConfig()
baseClient.SetApiVersion("v1alpha")

return DeploymentTargetsApiV1AlphaApi{
BaseClient: baseClient,
ResourceNamePlural: "deployment_targets",
ResourceNameSingular: "deployment_target",
}
}

func (c *DeploymentTargetsApiV1AlphaApi) Describe(targetId string) (*models.DeploymentTargetV1Alpha, error) {
return c.describe(targetId, false)
}

func (c *DeploymentTargetsApiV1AlphaApi) DescribeWithSecrets(targetId string) (*models.DeploymentTargetV1Alpha, error) {
return c.describe(targetId, true)
}

func (c *DeploymentTargetsApiV1AlphaApi) describe(targetId string, withSecrets bool) (*models.DeploymentTargetV1Alpha, error) {
var resource string
if withSecrets {
resource = fmt.Sprintf("%s?include_secrets=true", targetId)
} else {
resource = targetId
}
body, status, err := c.BaseClient.Get(c.ResourceNamePlural, resource)
if err != nil {
return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err)
}

if status != http.StatusOK {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

return models.NewDeploymentTargetV1AlphaFromJson(body)
}

func (c *DeploymentTargetsApiV1AlphaApi) DescribeByName(targetName, projectId string) (*models.DeploymentTargetV1Alpha, error) {
targets, err := c.list(projectId, targetName)
if err != nil {
return nil, err
}
if targets == nil || len(*targets) == 0 {
return nil, fmt.Errorf("target with the name '%s' doesn't exist in the project '%s'", targetName, projectId)
}
return (*targets)[0], nil
}

func (c *DeploymentTargetsApiV1AlphaApi) History(targetId string, historyRequest models.HistoryRequestFiltersV1Alpha) (*models.DeploymentsHistoryV1Alpha, error) {
if targetId == "" {
return nil, errors.New("target id must be provided")
}
values, err := historyRequest.ToURLValues()
if err != nil {
return nil, err
}
query := fmt.Sprintf("%s/history", targetId)
if len(values) != 0 {
query = fmt.Sprintf("%s/history?%s", targetId, values.Encode())
}

body, status, err := c.BaseClient.Get(c.ResourceNamePlural, query)
if err != nil {
return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err)
}

if status != http.StatusOK {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

return models.NewDeploymentsHistoryV1AlphaFromJson(body)
}

func (c *DeploymentTargetsApiV1AlphaApi) List(projectId string) (*models.DeploymentTargetListV1Alpha, error) {
kind := fmt.Sprintf("%s?project_id=%s", c.ResourceNamePlural, projectId)
body, status, err := c.BaseClient.List(kind)
if err != nil {
return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err)
}

if status != http.StatusOK {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}
return models.NewDeploymentTargetListV1AlphaFromJson(body)
}

func (c *DeploymentTargetsApiV1AlphaApi) list(projectId string, targetNames ...string) (*models.DeploymentTargetListV1Alpha, error) {
kind := fmt.Sprintf("%s?project_id=%s", c.ResourceNamePlural, projectId)
for _, targetName := range targetNames {
kind = fmt.Sprintf("%s&target_name=%s", kind, url.PathEscape(targetName))
}
body, status, err := c.BaseClient.List(kind)
if err != nil {
return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err)
}

if status != http.StatusOK {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}
return models.NewDeploymentTargetListV1AlphaFromJson(body)
}

func (c *DeploymentTargetsApiV1AlphaApi) Delete(targetId string) error {
unique_token, err := uuid.NewUUIDv4()
if err != nil {
return fmt.Errorf("unique token generation failed: %s", err)
}
params := fmt.Sprintf("%s?unique_token=%s", targetId, unique_token)

body, status, err := c.BaseClient.Delete(c.ResourceNamePlural, params)
if err != nil {
return err
}

if status != http.StatusOK {
return fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

return nil
}

func (c *DeploymentTargetsApiV1AlphaApi) Create(createRequest *models.DeploymentTargetCreateRequestV1Alpha) (*models.DeploymentTargetV1Alpha, error) {
if createRequest == nil {
return nil, errors.New("create request must not be nil")
}
err := createRequest.LoadFiles()
if err != nil {
return nil, err
}
unique_token, err := uuid.NewUUIDv4()
if err != nil {
return nil, fmt.Errorf("unique token generation failed: %s", err)
}
createRequest.UniqueToken = unique_token.String()
json_body, err := json.Marshal(createRequest)
if err != nil {
return nil, fmt.Errorf("failed to serialize deployment target create request: %s", err)
}

body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body)
if err != nil {
return nil, fmt.Errorf("creating %s on Semaphore failed '%s'", c.ResourceNamePlural, err)
}

if status != http.StatusOK {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

return models.NewDeploymentTargetV1AlphaFromJson(body)
}

func (c *DeploymentTargetsApiV1AlphaApi) Update(updateRequest *models.DeploymentTargetUpdateRequestV1Alpha) (*models.DeploymentTargetV1Alpha, error) {
if updateRequest == nil {
return nil, errors.New("update request must not be nil")
}
if updateRequest.Id == "" {
return nil, errors.New("update request id must not be empty")
}
if updateRequest.ProjectId == "" {
return nil, errors.New("update request project id must not be empty")
}
unique_token, err := uuid.NewUUIDv4()
if err != nil {
return nil, fmt.Errorf("unique token generation failed: %s", err)
}
updateRequest.UniqueToken = unique_token.String()

json_body, err := json.Marshal(updateRequest)
if err != nil {
return nil, fmt.Errorf("failed to serialize deployment target update request: %s", err)
}

body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, updateRequest.Id, json_body)
if err != nil {
return nil, fmt.Errorf("creating %s on Semaphore failed '%s'", c.ResourceNamePlural, err)
}

if status != http.StatusOK {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

return models.NewDeploymentTargetV1AlphaFromJson(body)
}

func (c *DeploymentTargetsApiV1AlphaApi) Activate(targetId string) (bool, error) {
return c.cordon(targetId, ActivateOpName)
}

func (c *DeploymentTargetsApiV1AlphaApi) Deactivate(targetId string) (bool, error) {
return c.cordon(targetId, DeactivateOpName)
}

func (c *DeploymentTargetsApiV1AlphaApi) cordon(targetId, opName string) (bool, error) {
query := fmt.Sprintf("%s/%s", targetId, opName)

body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, query, nil)
if err != nil {
return false, fmt.Errorf("creating %s on Semaphore failed '%s'", c.ResourceNamePlural, err)
}
if status != http.StatusOK {
return false, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

response, err := models.NewCordonResponseV1AlphaFromJson(body)
if err != nil {
return false, fmt.Errorf("wrong response: %s", err)
}
if response.TargetId != targetId {
return false, fmt.Errorf("wrong target id in the response")
}
shouldBeCordoned := opName == DeactivateOpName
if response.Cordoned != shouldBeCordoned {
if response.Cordoned {
return false, fmt.Errorf("failed to activate deployment target")
}
return false, fmt.Errorf("failed to deactivate deployment target")
}
return true, nil
}
10 changes: 5 additions & 5 deletions api/client/pipelines_v1_alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"errors"
"fmt"

uuid "github.com/google/uuid"
models "github.com/semaphoreci/cli/api/models"
"github.com/semaphoreci/cli/api/uuid"
)

type PipelinesApiV1AlphaApi struct {
Expand Down Expand Up @@ -78,10 +78,10 @@ func (c *PipelinesApiV1AlphaApi) PartialRebuildPpl(id string) ([]byte, error) {
}

func (c *PipelinesApiV1AlphaApi) ListPplByWfID(projectID, wfID string) ([]byte, error) {
detailed := fmt.Sprintf("%s?project_id=%s&wf_id=%s", c.ResourceNamePlural, projectID, wfID)
detailed := fmt.Sprintf("%s?project_id=%s&wf_id=%s", c.ResourceNamePlural, projectID, wfID)
body, status, err := c.BaseClient.List(detailed)

if err != nil {
if err != nil {
return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err))
}

Expand All @@ -93,10 +93,10 @@ func (c *PipelinesApiV1AlphaApi) ListPplByWfID(projectID, wfID string) ([]byte,
}

func (c *PipelinesApiV1AlphaApi) ListPpl(projectID string) ([]byte, error) {
detailed := fmt.Sprintf("%s?project_id=%s", c.ResourceNamePlural, projectID)
detailed := fmt.Sprintf("%s?project_id=%s", c.ResourceNamePlural, projectID)
body, status, err := c.BaseClient.List(detailed)

if err != nil {
if err != nil {
return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err))
}

Expand Down
1 change: 0 additions & 1 deletion api/client/projects_v1_alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func NewProjectV1AlphaApiWithCustomClient(client BaseClient) ProjectApiV1AlphaAp

func (c *ProjectApiV1AlphaApi) ListProjects() (*models.ProjectListV1Alpha, error) {
body, status, err := c.BaseClient.List(c.ResourceNamePlural)

if err != nil {
return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err))
}
Expand Down
2 changes: 1 addition & 1 deletion api/client/workflows_v1_alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"errors"
"fmt"

"github.com/google/uuid"
models "github.com/semaphoreci/cli/api/models"
"github.com/semaphoreci/cli/api/uuid"
)

type WorkflowApiV1AlphaApi struct {
Expand Down
Loading

0 comments on commit eb094f1

Please sign in to comment.