├── .gitignore ├── .goreleaser.yml ├── .semaphore ├── edge.yml ├── release.yml ├── semaphore.yml └── stable.yml ├── CHANGELOG.md ├── Dockerfile.dev ├── LICENSE ├── Makefile ├── README.md ├── api ├── client │ ├── agent_types_v1_alpha.go │ ├── agent_v1_alpha.go │ ├── base_client.go │ ├── dashbaards_v1_alpha.go │ ├── deployment_targets_v1_alpha.go │ ├── jobs_v1_alpha.go │ ├── logs_v1_alpha.go │ ├── notifications_v1_alpha.go │ ├── pipelines_v1_alpha.go │ ├── project_secrets_v1.go │ ├── projects_v1_alpha.go │ ├── secrets_v1_beta.go │ ├── troubleshoot_v1_alpha.go │ └── workflows_v1_alpha.go ├── models │ ├── agent_list_v1_alpha.go │ ├── agent_type_list_v1_alpha.go │ ├── agent_type_v1_alpha.go │ ├── agent_v1_alpha.go │ ├── dashboard_list_v1_alpha.go │ ├── dashboard_v1_alpha.go │ ├── debug_job_v1_alpha.go │ ├── debug_project_v1_alpha.go │ ├── deployment_target_v1_alpha.go │ ├── deployment_target_v1_alpha_test.go │ ├── job_debug_ssh_key_v1_alpha.go │ ├── job_list_v1_alpha.go │ ├── job_v1_alpha.go │ ├── logs_v1_alpha.go │ ├── notification_list_v1_alpha.go │ ├── notification_v1_alpha.go │ ├── pipeline_list_v1_alpha.go │ ├── pipeline_v1_alpha.go │ ├── project_list_v1_alpha.go │ ├── project_secret_list_v1.go │ ├── project_secret_v1.go │ ├── project_v1_alpha.go │ ├── secret_list_v1_beta.go │ ├── secret_v1_beta.go │ ├── troubleshoot_v1_alpha.go │ ├── workflow_list_v1_alpha.go │ └── workflow_v1_alpha.go └── uuid │ └── uuid.go ├── cmd ├── apply.go ├── apply_test.go ├── attach.go ├── config.go ├── connect.go ├── connect_test.go ├── context.go ├── create.go ├── create_job.go ├── create_notification.go ├── create_notification_test.go ├── create_secret.go ├── create_secret_test.go ├── create_target.go ├── create_target_test.go ├── create_test.go ├── debug.go ├── debug_job.go ├── debug_project.go ├── delete.go ├── delete_test.go ├── deployment_targets │ ├── delete.go │ ├── get.go │ ├── rebuild.go │ └── stop.go ├── edit.go ├── edit_test.go ├── get.go ├── get_notification.go ├── get_notification_test.go ├── get_test.go ├── init.go ├── init_test.go ├── jobs │ └── states.go ├── logs.go ├── logs_test.go ├── pipelines │ ├── get.go │ ├── rebuild.go │ └── stop.go ├── port_forward.go ├── rebuild.go ├── rebuild_test.go ├── root.go ├── ssh │ ├── connection.go │ └── session.go ├── stop.go ├── stop_test.go ├── troubleshoot.go ├── utils │ ├── ask_confirm.go │ ├── check.go │ ├── csv_flag.go │ ├── csv_flag_test.go │ ├── editor.go │ ├── file_flag.go │ ├── humanized_time.go │ ├── project.go │ ├── slices.go │ ├── slices_test.go │ ├── yaml_resource.go │ └── yaml_resource_test.go ├── version.go └── workflows │ ├── describe.go │ ├── get.go │ ├── rebuild.go │ ├── snapshot.go │ └── stop.go ├── config └── config.go ├── docker-compose.yml ├── examples ├── a.yml ├── b.yml └── p.yml ├── fixtures └── notification.yml ├── generators └── yaml.go ├── go.mod ├── go.sum ├── main.go ├── run └── scripts └── get.template.sh /.gitignore: -------------------------------------------------------------------------------- 1 | sem 2 | bin/ 3 | cli 4 | dist 5 | scripts/get.sh 6 | scripts/get-edge.sh 7 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: sem 2 | before: 3 | hooks: 4 | - go get ./... 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - darwin 11 | goarch: 12 | - 386 13 | - amd64 14 | - arm64 15 | archives: 16 | - name_template: >- 17 | {{ .ProjectName }}_ 18 | {{- title .Os }}_ 19 | {{- if eq .Arch "amd64" }}x86_64 20 | {{- else if eq .Arch "386" }}i386 21 | {{- else }}{{ .Arch }}{{ end }} 22 | checksum: 23 | name_template: '{{ .ProjectName }}_checksums.txt' 24 | changelog: 25 | sort: asc 26 | filters: 27 | exclude: 28 | - '^docs:' 29 | - '^test:' 30 | - Merge pull request 31 | - Merge branch 32 | brews: 33 | - 34 | tap: 35 | owner: semaphoreci 36 | name: homebrew-tap 37 | folder: Formula 38 | homepage: https://semaphoreci.com 39 | description: Semaphore 2.0 command line interface. 40 | test: | 41 | system "#{bin}/sem version" 42 | -------------------------------------------------------------------------------- /.semaphore/edge.yml: -------------------------------------------------------------------------------- 1 | version: "v1.0" 2 | name: Edge 3 | agent: 4 | machine: 5 | type: e2-standard-2 6 | os_image: ubuntu2004 7 | 8 | blocks: 9 | - name: "Edge Release" 10 | task: 11 | secrets: 12 | - name: container-registry-writer 13 | 14 | jobs: 15 | - name: "Release" 16 | commands: 17 | - checkout 18 | - make gsutil.configure 19 | - make release.edge.install.script 20 | -------------------------------------------------------------------------------- /.semaphore/release.yml: -------------------------------------------------------------------------------- 1 | version: "v1.0" 2 | name: "Github Release & Brew Tap Release" 3 | agent: 4 | machine: 5 | type: e2-standard-2 6 | os_image: ubuntu2004 7 | blocks: 8 | - name: "Github Release & Brew Tap Release" 9 | task: 10 | env_vars: 11 | - name: GO111MODULE 12 | value: "on" 13 | secrets: 14 | - name: sem-robot-ghtoken 15 | prologue: 16 | commands: 17 | - sem-version go 1.20 18 | - "export GOPATH=~/go" 19 | - "export PATH=/home/semaphore/go/bin:$PATH" 20 | - checkout 21 | jobs: 22 | - name: GoReleaser 23 | commands: 24 | - make install.goreleaser 25 | - goreleaser --rm-dist 26 | 27 | promotions: 28 | - name: Release Edge 29 | pipeline_file: "edge.yml" 30 | auto_promote_on: 31 | - result: passed 32 | 33 | - name: Release Stable 34 | pipeline_file: "stable.yml" 35 | -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | version: "v1.0" 2 | name: Pipeline 3 | 4 | agent: 5 | machine: 6 | type: e2-standard-2 7 | os_image: ubuntu2004 8 | 9 | blocks: 10 | - name: "Security checks" 11 | task: 12 | secrets: 13 | - name: security-toolbox-shared-read-access 14 | prologue: 15 | commands: 16 | - checkout 17 | - mv ~/.ssh/security-toolbox ~/.ssh/id_rsa 18 | - sudo chmod 600 ~/.ssh/id_rsa 19 | epilogue: 20 | always: 21 | commands: 22 | - if [ -f results.xml ]; then test-results publish results.xml; fi 23 | jobs: 24 | - name: Check dependencies 25 | commands: 26 | - make check.deps 27 | - name: Check code 28 | commands: 29 | - make check.static 30 | 31 | - name: "Test" 32 | task: 33 | env_vars: 34 | - name: GO111MODULE 35 | value: "on" 36 | epilogue: 37 | always: 38 | commands: 39 | - if [ -f results.xml ]; then test-results publish results.xml; fi 40 | jobs: 41 | - name: "Test" 42 | commands: 43 | - checkout 44 | - make go.get 45 | - make test 46 | - name: "Check release" 47 | commands: 48 | - checkout 49 | - make install.goreleaser 50 | - make go.get 51 | - goreleaser check 52 | - goreleaser release --snapshot --rm-dist 53 | 54 | promotions: 55 | - name: "Release on Github & Brew Tap" 56 | pipeline_file: "release.yml" 57 | auto_promote_on: 58 | - result: passed 59 | branch: 60 | - "^refs/tags/v*" 61 | -------------------------------------------------------------------------------- /.semaphore/stable.yml: -------------------------------------------------------------------------------- 1 | version: "v1.0" 2 | name: Stable 3 | agent: 4 | machine: 5 | type: e2-standard-2 6 | os_image: ubuntu2004 7 | 8 | blocks: 9 | - name: "Stable Release" 10 | task: 11 | secrets: 12 | - name: container-registry-writer 13 | 14 | jobs: 15 | - name: "Stable" 16 | commands: 17 | - checkout 18 | - make gsutil.configure 19 | - make release.stable.install.script 20 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM golang:1.20 2 | 3 | RUN go install gotest.tools/gotestsum@latest 4 | 5 | WORKDIR /app 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build release 2 | 3 | REL_VERSION=$(shell git rev-parse HEAD) 4 | REL_BUCKET=sem-cli-releases 5 | SECURITY_TOOLBOX_BRANCH ?= master 6 | SECURITY_TOOLBOX_TMP_DIR ?= /tmp/security-toolbox 7 | 8 | check.prepare: 9 | rm -rf $(SECURITY_TOOLBOX_TMP_DIR) 10 | git clone git@github.com:renderedtext/security-toolbox.git $(SECURITY_TOOLBOX_TMP_DIR) && (cd $(SECURITY_TOOLBOX_TMP_DIR) && git checkout $(SECURITY_TOOLBOX_BRANCH) && cd -) 11 | 12 | check.static: check.prepare 13 | docker run -it -v $$(pwd):/app \ 14 | -v $(SECURITY_TOOLBOX_TMP_DIR):$(SECURITY_TOOLBOX_TMP_DIR) \ 15 | registry.semaphoreci.com/ruby:2.7 \ 16 | bash -c 'cd /app && $(SECURITY_TOOLBOX_TMP_DIR)/code --language go -d' 17 | 18 | check.deps: check.prepare 19 | docker run -it -v $$(pwd):/app \ 20 | -v $(SECURITY_TOOLBOX_TMP_DIR):$(SECURITY_TOOLBOX_TMP_DIR) \ 21 | registry.semaphoreci.com/ruby:2.7 \ 22 | bash -c 'cd /app && $(SECURITY_TOOLBOX_TMP_DIR)/dependencies --language go -d' 23 | 24 | install.goreleaser: 25 | curl -L https://github.com/goreleaser/goreleaser/releases/download/v1.14.1/goreleaser_Linux_x86_64.tar.gz -o /tmp/goreleaser.tar.gz 26 | tar -xf /tmp/goreleaser.tar.gz -C /tmp 27 | sudo mv /tmp/goreleaser /usr/bin/goreleaser 28 | 29 | gsutil.configure: 30 | gcloud auth activate-service-account $(GCP_REGISTRY_WRITER_EMAIL) --key-file ~/gce-registry-writer-key.json 31 | gcloud --quiet auth configure-docker 32 | gcloud --quiet config set project semaphore2-prod 33 | 34 | go.get: 35 | docker-compose run --rm cli go get 36 | 37 | go.fmt: 38 | docker-compose run --rm cli go fmt ./... 39 | 40 | test: 41 | docker-compose run --rm cli gotestsum --format short-verbose --junitfile results.xml --packages="./..." -- -p 1 42 | 43 | build: 44 | docker-compose run --rm cli env GOOS=$(OS) GOARCH=$(ARCH) go build -ldflags "-s -w -X main.version=$(shell git describe --tags --abbrev=0)" -o sem 45 | tar -czvf /tmp/sem.tar.gz sem 46 | 47 | # Automation of CLI tagging. 48 | 49 | tag.major: 50 | git fetch --tags 51 | latest=$$(git tag | sort --version-sort | tail -n 1); new=$$(echo $$latest | cut -c 2- | awk -F '.' '{ print "v" $$1+1 ".0.0" }'); echo $$new; git tag $$new; git push origin $$new 52 | 53 | tag.minor: 54 | git fetch --tags 55 | latest=$$(git tag | sort --version-sort | tail -n 1); new=$$(echo $$latest | cut -c 2- | awk -F '.' '{ print "v" $$1 "." $$2 + 1 ".0" }'); echo $$new; git tag $$new; git push origin $$new 56 | 57 | tag.patch: 58 | git fetch --tags 59 | latest=$$(git tag | sort --version-sort | tail -n 1); new=$$(echo $$latest | cut -c 2- | awk -F '.' '{ print "v" $$1 "." $$2 "." $$3+1 }'); echo $$new; git tag $$new; git push origin $$new 60 | 61 | 62 | # 63 | # These two scripts update generate a new installation script based on the 64 | # current git tag on Semaphore. 65 | 66 | release.stable.install.script: 67 | sed 's/VERSION_PLACEHOLDER/$(shell git describe --tags --abbrev=0)/' scripts/get.template.sh > scripts/get.sh 68 | gsutil cp scripts/get.sh gs://$(REL_BUCKET)/get.sh 69 | gsutil acl -R ch -u AllUsers:R gs://$(REL_BUCKET)/get.sh 70 | gsutil setmeta -h "Cache-Control:private, max-age=0, no-transform" gs://$(REL_BUCKET)/get.sh 71 | 72 | release.edge.install.script: 73 | sed 's/VERSION_PLACEHOLDER/$(shell git describe --tags --abbrev=0)/' scripts/get.template.sh > scripts/get-edge.sh 74 | gsutil cp scripts/get-edge.sh gs://$(REL_BUCKET)/get-edge.sh 75 | gsutil acl -R ch -u AllUsers:R gs://$(REL_BUCKET)/get-edge.sh 76 | gsutil setmeta -h "Cache-Control:private, max-age=0, no-transform" gs://$(REL_BUCKET)/get-edge.sh 77 | echo "https://storage.googleapis.com/$(REL_BUCKET)/get-edge.sh" 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sem 2 | 3 | Semaphore 2.0 command line interface. 4 | 5 | ## Install 6 | 7 | Edge (latest build on master branch): 8 | 9 | ``` bash 10 | curl https://storage.googleapis.com/sem-cli-releases/get-edge.sh | bash 11 | ``` 12 | 13 | Stable (latest stable version, manually released): 14 | 15 | ``` bash 16 | curl https://storage.googleapis.com/sem-cli-releases/get.sh | bash 17 | ``` 18 | 19 | Homebrew (latest stable version) 20 | 21 | ```bash 22 | brew install semaphoreci/tap/sem 23 | ``` 24 | 25 | ## Development 26 | 27 | ### Releases 28 | 29 | We build a new release for every tag in this repository and upload it to Github. 30 | 31 | Apart from this, we have two installation scripts: 32 | - `get.sh` - gets the latest stable version of the CLI 33 | - `get-edge.sh` - gets the latest edge version of the CLI 34 | 35 | The `edge` script is updated every time we release a tag. The `stable` `get.sh` 36 | script needs to be manually approved and released from Semaphore. Follow the 37 | releasing new versions procedure to update and release. 38 | 39 | ### Releasing new versions 40 | 41 | 1. Prepare the changes as a PR -> Get a green build -> Merge to master 42 | 43 | 2. Checkout latest master code and run: `make tag.patch`, `make tag.minor` or 44 | `make tag.major` depending on the type of the change. We use semantic 45 | versioning for the CLI. 46 | 47 | 3. Semaphore will build the release, upload it to Github and our brew taps, and 48 | update the `get-edge` installation script. 49 | 50 | 4. The `stable` installation script needs to be updated by manually promoting 51 | the release on Semaphore. Find the workflow for the tag you want to promote 52 | to stable, and click on the "Stable" promotion. This will update the `get.sh` 53 | script. 54 | 55 | 5. Update the CHANGELOG.md file 56 | -------------------------------------------------------------------------------- /api/client/agent_types_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | models "github.com/semaphoreci/cli/api/models" 8 | ) 9 | 10 | type AgentTypeApiV1AlphaApi struct { 11 | BaseClient BaseClient 12 | ResourceNameSingular string 13 | ResourceNamePlural string 14 | } 15 | 16 | func NewAgentTypeApiV1AlphaApi() AgentTypeApiV1AlphaApi { 17 | baseClient := NewBaseClientFromConfig() 18 | baseClient.SetApiVersion("v1alpha") 19 | 20 | return AgentTypeApiV1AlphaApi{ 21 | BaseClient: baseClient, 22 | ResourceNamePlural: "self_hosted_agent_types", 23 | ResourceNameSingular: "self_hosted_agent_type", 24 | } 25 | } 26 | 27 | func (c *AgentTypeApiV1AlphaApi) ListAgentTypes() (*models.AgentTypeListV1Alpha, error) { 28 | body, status, err := c.BaseClient.List(c.ResourceNamePlural) 29 | 30 | if err != nil { 31 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 32 | } 33 | 34 | if status != 200 { 35 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 36 | } 37 | 38 | return models.NewAgentTypeListV1AlphaFromJson(body) 39 | } 40 | 41 | func (c *AgentTypeApiV1AlphaApi) GetAgentType(name string) (*models.AgentTypeV1Alpha, error) { 42 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 43 | 44 | if err != nil { 45 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 46 | } 47 | 48 | if status != 200 { 49 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 50 | } 51 | 52 | return models.NewAgentTypeV1AlphaFromJson(body) 53 | } 54 | 55 | func (c *AgentTypeApiV1AlphaApi) DeleteAgentType(name string) error { 56 | body, status, err := c.BaseClient.Delete(c.ResourceNamePlural, name) 57 | 58 | if err != nil { 59 | return err 60 | } 61 | 62 | if status != 200 { 63 | return fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func (c *AgentTypeApiV1AlphaApi) CreateAgentType(d *models.AgentTypeV1Alpha) (*models.AgentTypeV1Alpha, error) { 70 | json_body, err := d.ToJson() 71 | 72 | if err != nil { 73 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 74 | } 75 | 76 | body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body) 77 | 78 | if err != nil { 79 | return nil, errors.New(fmt.Sprintf("creating %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 80 | } 81 | 82 | if status != 200 { 83 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 84 | } 85 | 86 | return models.NewAgentTypeV1AlphaFromJson(body) 87 | } 88 | 89 | func (c *AgentTypeApiV1AlphaApi) UpdateAgentType(d *models.AgentTypeV1Alpha) (*models.AgentTypeV1Alpha, error) { 90 | json_body, err := d.ToJson() 91 | 92 | if err != nil { 93 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 94 | } 95 | 96 | body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, d.Metadata.Name, json_body) 97 | if err != nil { 98 | return nil, errors.New(fmt.Sprintf("updating %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 99 | } 100 | 101 | if status != 200 { 102 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 103 | } 104 | 105 | return models.NewAgentTypeV1AlphaFromJson(body) 106 | } 107 | -------------------------------------------------------------------------------- /api/client/agent_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | 7 | models "github.com/semaphoreci/cli/api/models" 8 | ) 9 | 10 | type AgentApiV1AlphaApi struct { 11 | BaseClient BaseClient 12 | ResourceNameSingular string 13 | ResourceNamePlural string 14 | } 15 | 16 | func NewAgentApiV1AlphaApi() AgentApiV1AlphaApi { 17 | baseClient := NewBaseClientFromConfig() 18 | baseClient.SetApiVersion("v1alpha") 19 | 20 | return AgentApiV1AlphaApi{ 21 | BaseClient: baseClient, 22 | ResourceNamePlural: "agents", 23 | ResourceNameSingular: "agent", 24 | } 25 | } 26 | 27 | func (c *AgentApiV1AlphaApi) ListAgents(agentType string, cursor string) (*models.AgentListV1Alpha, error) { 28 | query := url.Values{} 29 | query.Add("page_size", "200") 30 | 31 | if agentType != "" { 32 | query.Add("agent_type", agentType) 33 | } 34 | 35 | if cursor != "" { 36 | query.Add("cursor", cursor) 37 | } 38 | 39 | body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query) 40 | 41 | if err != nil { 42 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 43 | } 44 | 45 | if status != 200 { 46 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 47 | } 48 | 49 | return models.NewAgentListV1AlphaFromJson(body) 50 | } 51 | 52 | func (c *AgentApiV1AlphaApi) GetAgent(name string) (*models.AgentV1Alpha, error) { 53 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 54 | 55 | if err != nil { 56 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 57 | } 58 | 59 | if status != 200 { 60 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 61 | } 62 | 63 | return models.NewAgentV1AlphaFromJson(body) 64 | } 65 | -------------------------------------------------------------------------------- /api/client/dashbaards_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | models "github.com/semaphoreci/cli/api/models" 8 | ) 9 | 10 | type DashboardApiV1AlphaApi struct { 11 | BaseClient BaseClient 12 | ResourceNameSingular string 13 | ResourceNamePlural string 14 | } 15 | 16 | func NewDashboardV1AlphaApi() DashboardApiV1AlphaApi { 17 | baseClient := NewBaseClientFromConfig() 18 | baseClient.SetApiVersion("v1alpha") 19 | 20 | return DashboardApiV1AlphaApi{ 21 | BaseClient: baseClient, 22 | ResourceNamePlural: "dashboards", 23 | ResourceNameSingular: "dashboard", 24 | } 25 | } 26 | 27 | func (c *DashboardApiV1AlphaApi) ListDashboards() (*models.DashboardListV1Alpha, error) { 28 | body, status, err := c.BaseClient.List(c.ResourceNamePlural) 29 | 30 | if err != nil { 31 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 32 | } 33 | 34 | if status != 200 { 35 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 36 | } 37 | 38 | return models.NewDashboardListV1AlphaFromJson(body) 39 | } 40 | 41 | func (c *DashboardApiV1AlphaApi) GetDashboard(name string) (*models.DashboardV1Alpha, error) { 42 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 43 | 44 | if err != nil { 45 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 46 | } 47 | 48 | if status != 200 { 49 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 50 | } 51 | 52 | return models.NewDashboardV1AlphaFromJson(body) 53 | } 54 | 55 | func (c *DashboardApiV1AlphaApi) DeleteDashboard(name string) error { 56 | body, status, err := c.BaseClient.Delete(c.ResourceNamePlural, name) 57 | 58 | if err != nil { 59 | return err 60 | } 61 | 62 | if status != 200 { 63 | return fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func (c *DashboardApiV1AlphaApi) CreateDashboard(d *models.DashboardV1Alpha) (*models.DashboardV1Alpha, error) { 70 | json_body, err := d.ToJson() 71 | 72 | if err != nil { 73 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 74 | } 75 | 76 | body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body) 77 | 78 | if err != nil { 79 | return nil, errors.New(fmt.Sprintf("creating %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 80 | } 81 | 82 | if status != 200 { 83 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 84 | } 85 | 86 | return models.NewDashboardV1AlphaFromJson(body) 87 | } 88 | 89 | func (c *DashboardApiV1AlphaApi) UpdateDashboard(d *models.DashboardV1Alpha) (*models.DashboardV1Alpha, error) { 90 | json_body, err := d.ToJson() 91 | 92 | if err != nil { 93 | return nil, errors.New(fmt.Sprintf("failed to serialize %s object '%s'", c.ResourceNameSingular, err)) 94 | } 95 | 96 | identifier := "" 97 | 98 | if d.Metadata.Id != "" { 99 | identifier = d.Metadata.Id 100 | } else { 101 | identifier = d.Metadata.Name 102 | } 103 | 104 | body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, identifier, json_body) 105 | 106 | if err != nil { 107 | return nil, errors.New(fmt.Sprintf("updating %s on Semaphore failed '%s'", c.ResourceNamePlural, err)) 108 | } 109 | 110 | if status != 200 { 111 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 112 | } 113 | 114 | return models.NewDashboardV1AlphaFromJson(body) 115 | } 116 | -------------------------------------------------------------------------------- /api/client/jobs_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | 8 | models "github.com/semaphoreci/cli/api/models" 9 | ) 10 | 11 | type JobsApiV1AlphaApi struct { 12 | BaseClient BaseClient 13 | ResourceNameSingular string 14 | ResourceNamePlural string 15 | } 16 | 17 | func NewJobsV1AlphaApi() JobsApiV1AlphaApi { 18 | baseClient := NewBaseClientFromConfig() 19 | baseClient.SetApiVersion("v1alpha") 20 | 21 | return JobsApiV1AlphaApi{ 22 | BaseClient: baseClient, 23 | ResourceNamePlural: "jobs", 24 | ResourceNameSingular: "job", 25 | } 26 | } 27 | 28 | func (c *JobsApiV1AlphaApi) ListJobs(states []string) (*models.JobListV1Alpha, error) { 29 | query := url.Values{} 30 | 31 | for _, s := range states { 32 | query.Add("states", s) 33 | } 34 | 35 | body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query) 36 | 37 | if err != nil { 38 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 39 | } 40 | 41 | if status != 200 { 42 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 43 | } 44 | 45 | return models.NewJobListV1AlphaFromJson(body) 46 | } 47 | 48 | func (c *JobsApiV1AlphaApi) GetJob(name string) (*models.JobV1Alpha, error) { 49 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 50 | 51 | if err != nil { 52 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 53 | } 54 | 55 | if status != 200 { 56 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 57 | } 58 | 59 | return models.NewJobV1AlphaFromJson(body) 60 | } 61 | 62 | func (c *JobsApiV1AlphaApi) GetJobDebugSSHKey(id string) (*models.JobDebugSSHKeyV1Alpha, error) { 63 | path := fmt.Sprintf("%s/%s", id, "debug_ssh_key") 64 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, path) 65 | 66 | if err != nil { 67 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 68 | } 69 | 70 | if status != 200 { 71 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 72 | } 73 | 74 | return models.NewJobDebugSSHKeyV1AlphaFromJSON(body) 75 | } 76 | 77 | func (c *JobsApiV1AlphaApi) CreateJob(j *models.JobV1Alpha) (*models.JobV1Alpha, error) { 78 | json_body, err := j.ToJson() 79 | 80 | if err != nil { 81 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 82 | } 83 | 84 | body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body) 85 | 86 | if err != nil { 87 | return nil, errors.New(fmt.Sprintf("creating %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 88 | } 89 | 90 | if status != 200 { 91 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 92 | } 93 | 94 | return models.NewJobV1AlphaFromJson(body) 95 | } 96 | 97 | func (c *JobsApiV1AlphaApi) CreateDebugJob(j *models.DebugJobV1Alpha) (*models.JobV1Alpha, error) { 98 | json_body, err := j.ToJson() 99 | 100 | if err != nil { 101 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 102 | } 103 | 104 | path := fmt.Sprintf("%s/%s/%s", c.ResourceNamePlural, j.JobId, "debug") 105 | body, status, err := c.BaseClient.Post(path, json_body) 106 | 107 | if err != nil { 108 | return nil, errors.New(fmt.Sprintf("creating debug %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 109 | } 110 | 111 | if status != 200 { 112 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 113 | } 114 | 115 | return models.NewJobV1AlphaFromJson(body) 116 | } 117 | 118 | func (c *JobsApiV1AlphaApi) CreateDebugProject(j *models.DebugProjectV1Alpha) (*models.JobV1Alpha, error) { 119 | json_body, err := j.ToJson() 120 | 121 | if err != nil { 122 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 123 | } 124 | 125 | path := fmt.Sprintf("%s/%s/%s", c.ResourceNamePlural, "project_debug", j.ProjectIdOrName) 126 | body, status, err := c.BaseClient.Post(path, json_body) 127 | 128 | if err != nil { 129 | return nil, errors.New(fmt.Sprintf("creating debug %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 130 | } 131 | 132 | if status != 200 { 133 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 134 | } 135 | 136 | return models.NewJobV1AlphaFromJson(body) 137 | } 138 | 139 | func (c *JobsApiV1AlphaApi) StopJob(id string) error { 140 | path := fmt.Sprintf("%s/%s/%s", c.ResourceNamePlural, id, "stop") 141 | body, status, err := c.BaseClient.Post(path, []byte{}) 142 | 143 | if err != nil { 144 | return errors.New(fmt.Sprintf("stopping %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 145 | } 146 | 147 | if status != 200 { 148 | return errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 149 | } 150 | 151 | return nil 152 | } 153 | -------------------------------------------------------------------------------- /api/client/logs_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | 6 | models "github.com/semaphoreci/cli/api/models" 7 | ) 8 | 9 | type LogsApiV1AlphaApi struct { 10 | BaseClient BaseClient 11 | ResourceNameSingular string 12 | ResourceNamePlural string 13 | } 14 | 15 | func NewLogsV1AlphaApi() LogsApiV1AlphaApi { 16 | baseClient := NewBaseClientFromConfig() 17 | baseClient.SetApiVersion("v1alpha") 18 | 19 | return LogsApiV1AlphaApi{ 20 | BaseClient: baseClient, 21 | ResourceNamePlural: "logs", 22 | ResourceNameSingular: "logs", 23 | } 24 | } 25 | 26 | func (c *LogsApiV1AlphaApi) Get(jobID string) (*models.LogsV1Alpha, error) { 27 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, jobID) 28 | if err != nil { 29 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 30 | } 31 | 32 | if status != 200 { 33 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 34 | } 35 | 36 | return models.NewLogsV1AlphaFromJson(body) 37 | } 38 | -------------------------------------------------------------------------------- /api/client/notifications_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | 8 | models "github.com/semaphoreci/cli/api/models" 9 | ) 10 | 11 | type NotificationsV1AlphaApi struct { 12 | BaseClient BaseClient 13 | ResourceNameSingular string 14 | ResourceNamePlural string 15 | } 16 | 17 | func NewNotificationsV1AlphaApi() NotificationsV1AlphaApi { 18 | baseClient := NewBaseClientFromConfig() 19 | baseClient.SetApiVersion("v1alpha") 20 | 21 | return NotificationsV1AlphaApi{ 22 | BaseClient: baseClient, 23 | ResourceNamePlural: "notifications", 24 | ResourceNameSingular: "notification", 25 | } 26 | } 27 | 28 | func (c *NotificationsV1AlphaApi) ListNotifications(pageSize int32, pageToken string) (*models.NotificationListV1Alpha, error) { 29 | query := url.Values{} 30 | 31 | if pageSize > 0 { 32 | query.Add("page_size", fmt.Sprintf("%d", pageSize)) 33 | } 34 | 35 | if pageToken != "" { 36 | query.Add("page_token", pageToken) 37 | } 38 | 39 | body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query) 40 | 41 | if err != nil { 42 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 43 | } 44 | 45 | if status != 200 { 46 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 47 | } 48 | 49 | return models.NewNotificationListV1AlphaFromJson(body) 50 | } 51 | 52 | func (c *NotificationsV1AlphaApi) GetNotification(name string) (*models.NotificationV1Alpha, error) { 53 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 54 | 55 | if err != nil { 56 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 57 | } 58 | 59 | if status != 200 { 60 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 61 | } 62 | 63 | return models.NewNotificationV1AlphaFromJson(body) 64 | } 65 | 66 | func (c *NotificationsV1AlphaApi) DeleteNotification(name string) error { 67 | body, status, err := c.BaseClient.Delete(c.ResourceNamePlural, name) 68 | 69 | if err != nil { 70 | return err 71 | } 72 | 73 | if status != 200 { 74 | return fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 75 | } 76 | 77 | return nil 78 | } 79 | 80 | func (c *NotificationsV1AlphaApi) CreateNotification(n *models.NotificationV1Alpha) (*models.NotificationV1Alpha, error) { 81 | json_body, err := n.ToJson() 82 | 83 | if err != nil { 84 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 85 | } 86 | 87 | body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body) 88 | 89 | if err != nil { 90 | return nil, fmt.Errorf("creating %s on Semaphore failed '%s'", c.ResourceNameSingular, err) 91 | } 92 | 93 | if status != 200 { 94 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 95 | } 96 | 97 | return models.NewNotificationV1AlphaFromJson(body) 98 | } 99 | 100 | func (c *NotificationsV1AlphaApi) UpdateNotification(n *models.NotificationV1Alpha) (*models.NotificationV1Alpha, error) { 101 | json_body, err := n.ToJson() 102 | 103 | if err != nil { 104 | return nil, errors.New(fmt.Sprintf("failed to serialize %s object '%s'", c.ResourceNameSingular, err)) 105 | } 106 | 107 | identifier := "" 108 | 109 | if n.Metadata.Id != "" { 110 | identifier = n.Metadata.Id 111 | } else { 112 | identifier = n.Metadata.Name 113 | } 114 | 115 | body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, identifier, json_body) 116 | 117 | if err != nil { 118 | return nil, fmt.Errorf("updating %s on Semaphore failed '%s'", c.ResourceNamePlural, err) 119 | } 120 | 121 | if status != 200 { 122 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 123 | } 124 | 125 | return models.NewNotificationV1AlphaFromJson(body) 126 | } 127 | -------------------------------------------------------------------------------- /api/client/pipelines_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | 8 | models "github.com/semaphoreci/cli/api/models" 9 | "github.com/semaphoreci/cli/api/uuid" 10 | ) 11 | 12 | type PipelinesApiV1AlphaApi struct { 13 | BaseClient BaseClient 14 | ResourceNameSingular string 15 | ResourceNamePlural string 16 | } 17 | 18 | func NewPipelinesV1AlphaApi() PipelinesApiV1AlphaApi { 19 | baseClient := NewBaseClientFromConfig() 20 | baseClient.SetApiVersion("v1alpha") 21 | 22 | return PipelinesApiV1AlphaApi{ 23 | BaseClient: baseClient, 24 | ResourceNamePlural: "pipelines", 25 | ResourceNameSingular: "pipeline", 26 | } 27 | } 28 | 29 | func (c *PipelinesApiV1AlphaApi) DescribePpl(id string) (*models.PipelineV1Alpha, error) { 30 | detailed := fmt.Sprintf("%s?detailed=true", id) 31 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, detailed) 32 | 33 | if err != nil { 34 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 35 | } 36 | 37 | if status != 200 { 38 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 39 | } 40 | 41 | return models.NewPipelineV1AlphaFromJson(body) 42 | } 43 | 44 | func (c *PipelinesApiV1AlphaApi) StopPpl(id string) ([]byte, error) { 45 | request_body := []byte("{\"terminate_request\": true}") 46 | 47 | body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, id, request_body) 48 | 49 | if err != nil { 50 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 51 | } 52 | 53 | if status != 200 { 54 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 55 | } 56 | 57 | return body, nil 58 | } 59 | 60 | func (c *PipelinesApiV1AlphaApi) PartialRebuildPpl(id string) ([]byte, error) { 61 | requestToken, err := uuid.NewUUID() 62 | 63 | if err != nil { 64 | return nil, errors.New(fmt.Sprintf("request token generation failed '%s'", err)) 65 | } 66 | 67 | actionArgs := fmt.Sprintf("%s?%s=%s", "partial_rebuild", "request_token", requestToken.String()) 68 | body, status, err := c.BaseClient.PostAction(c.ResourceNamePlural, id, actionArgs, []byte("")) 69 | 70 | if err != nil { 71 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 72 | } 73 | 74 | if status != 200 { 75 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 76 | } 77 | 78 | return body, nil 79 | } 80 | 81 | func (c *PipelinesApiV1AlphaApi) ListPplByWfID(projectID, wfID string) ([]byte, error) { 82 | detailed := fmt.Sprintf("%s?project_id=%s&wf_id=%s", c.ResourceNamePlural, projectID, wfID) 83 | body, status, err := c.BaseClient.List(detailed) 84 | 85 | if err != nil { 86 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 87 | } 88 | 89 | if status != 200 { 90 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 91 | } 92 | 93 | return body, nil 94 | } 95 | 96 | type ListOptions struct { 97 | CreatedAfter int64 98 | CreatedBefore int64 99 | } 100 | 101 | func (c *PipelinesApiV1AlphaApi) ListPpl(projectID string) ([]byte, error) { 102 | detailed := fmt.Sprintf("%s?project_id=%s", c.ResourceNamePlural, projectID) 103 | body, status, err := c.BaseClient.List(detailed) 104 | 105 | if err != nil { 106 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 107 | } 108 | 109 | if status != 200 { 110 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 111 | } 112 | 113 | return body, nil 114 | } 115 | 116 | func (c *PipelinesApiV1AlphaApi) ListPplWithOptions(projectID string, options ListOptions) ([]byte, error) { 117 | query := url.Values{} 118 | query.Add("project_id", projectID) 119 | 120 | if options.CreatedAfter > 0 { 121 | query.Add("created_after", fmt.Sprintf("%d", options.CreatedAfter)) 122 | } 123 | 124 | if options.CreatedBefore > 0 { 125 | query.Add("created_before", fmt.Sprintf("%d", options.CreatedBefore)) 126 | } 127 | 128 | body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query) 129 | 130 | if err != nil { 131 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 132 | } 133 | 134 | if status != 200 { 135 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 136 | } 137 | 138 | return body, nil 139 | } 140 | -------------------------------------------------------------------------------- /api/client/project_secrets_v1.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | 8 | models "github.com/semaphoreci/cli/api/models" 9 | ) 10 | 11 | type ProjectSecretsApiV1Api struct { 12 | BaseClient BaseClient 13 | ResourceNameSingular string 14 | ResourceNamePlural string 15 | } 16 | 17 | func NewProjectSecretV1Api(projectID string) ProjectSecretsApiV1Api { 18 | baseClient := NewBaseClientFromConfig() 19 | baseClient.SetApiVersion("v1") 20 | 21 | return ProjectSecretsApiV1Api{ 22 | BaseClient: baseClient, 23 | ResourceNamePlural: fmt.Sprintf("projects/%s/secrets", projectID), 24 | ResourceNameSingular: fmt.Sprintf("projects/%s/secret", projectID), 25 | } 26 | } 27 | 28 | func (c *ProjectSecretsApiV1Api) ListSecrets() (*models.SecretListV1Beta, error) { 29 | body, status, err := c.BaseClient.List(c.ResourceNamePlural) 30 | 31 | if err != nil { 32 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 33 | } 34 | 35 | if status != 200 { 36 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 37 | } 38 | 39 | return models.NewSecretListV1BetaFromJson(body) 40 | } 41 | 42 | func (c *ProjectSecretsApiV1Api) GetSecret(name string) (*models.ProjectSecretV1, error) { 43 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 44 | 45 | if err != nil { 46 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 47 | } 48 | 49 | if status != 200 { 50 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 51 | } 52 | 53 | return models.NewProjectSecretV1FromJson(body) 54 | } 55 | 56 | func (c *ProjectSecretsApiV1Api) DeleteSecret(name string) error { 57 | body, status, err := c.BaseClient.Delete(c.ResourceNamePlural, name) 58 | 59 | if err != nil { 60 | return err 61 | } 62 | 63 | if status != 200 { 64 | return fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (c *ProjectSecretsApiV1Api) CreateSecret(d *models.ProjectSecretV1) (*models.ProjectSecretV1, error) { 71 | json_body, err := d.ToJson() 72 | 73 | if err != nil { 74 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 75 | } 76 | 77 | body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body) 78 | 79 | if err != nil { 80 | return nil, errors.New(fmt.Sprintf("creating %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 81 | } 82 | 83 | if status != 200 { 84 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 85 | } 86 | 87 | return models.NewProjectSecretV1FromJson(body) 88 | } 89 | 90 | func (c *ProjectSecretsApiV1Api) UpdateSecret(d *models.ProjectSecretV1) (*models.ProjectSecretV1, error) { 91 | json_body, err := d.ToJson() 92 | 93 | if err != nil { 94 | return nil, errors.New(fmt.Sprintf("failed to serialize %s object '%s'", c.ResourceNameSingular, err)) 95 | } 96 | 97 | identifier := "" 98 | 99 | if d.Metadata.Id != "" { 100 | identifier = d.Metadata.Id 101 | } else { 102 | identifier = d.Metadata.Name 103 | } 104 | 105 | body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, identifier, json_body) 106 | 107 | if err != nil { 108 | return nil, errors.New(fmt.Sprintf("updating %s on Semaphore failed '%s'", c.ResourceNamePlural, err)) 109 | } 110 | 111 | if status != 200 { 112 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 113 | } 114 | 115 | return models.NewProjectSecretV1FromJson(body) 116 | } 117 | 118 | func (c *ProjectSecretsApiV1Api) FallbackUpdate(d *models.ProjectSecretV1) (*models.ProjectSecretV1, error) { 119 | identifier := "" 120 | 121 | if d.Metadata.Id != "" { 122 | identifier = d.Metadata.Id 123 | } else { 124 | identifier = d.Metadata.Name 125 | } 126 | 127 | err := c.DeleteSecret(identifier) 128 | 129 | if err != nil { 130 | log.Println("fallbackUpdate:", err) 131 | return nil, fmt.Errorf("updating %s on Semaphore failed '%s'", c.ResourceNamePlural, err) 132 | } 133 | 134 | return c.CreateSecret(d) 135 | } 136 | -------------------------------------------------------------------------------- /api/client/projects_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | models "github.com/semaphoreci/cli/api/models" 8 | ) 9 | 10 | type ProjectApiV1AlphaApi struct { 11 | BaseClient BaseClient 12 | ResourceNameSingular string 13 | ResourceNamePlural string 14 | } 15 | 16 | func NewProjectV1AlphaApi() ProjectApiV1AlphaApi { 17 | baseClient := NewBaseClientFromConfig() 18 | baseClient.SetApiVersion("v1alpha") 19 | 20 | return ProjectApiV1AlphaApi{ 21 | BaseClient: baseClient, 22 | ResourceNamePlural: "projects", 23 | ResourceNameSingular: "project", 24 | } 25 | } 26 | 27 | func NewProjectV1AlphaApiWithCustomClient(client BaseClient) ProjectApiV1AlphaApi { 28 | client.SetApiVersion("v1alpha") 29 | 30 | return ProjectApiV1AlphaApi{ 31 | BaseClient: client, 32 | ResourceNamePlural: "projects", 33 | ResourceNameSingular: "project", 34 | } 35 | } 36 | 37 | func (c *ProjectApiV1AlphaApi) ListProjects() (*models.ProjectListV1Alpha, error) { 38 | body, status, err := c.BaseClient.List(c.ResourceNamePlural) 39 | if err != nil { 40 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 41 | } 42 | 43 | if status != 200 { 44 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 45 | } 46 | 47 | return models.NewProjectListV1AlphaFromJson(body) 48 | } 49 | 50 | func (c *ProjectApiV1AlphaApi) GetProject(name string) (*models.ProjectV1Alpha, error) { 51 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 52 | 53 | if err != nil { 54 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 55 | } 56 | 57 | if status != 200 { 58 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 59 | } 60 | 61 | return models.NewProjectV1AlphaFromJson(body) 62 | } 63 | 64 | func (c *ProjectApiV1AlphaApi) DeleteProject(name string) error { 65 | body, status, err := c.BaseClient.Delete(c.ResourceNamePlural, name) 66 | 67 | if err != nil { 68 | return err 69 | } 70 | 71 | if status != 200 { 72 | return fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (c *ProjectApiV1AlphaApi) CreateProject(d *models.ProjectV1Alpha) (*models.ProjectV1Alpha, error) { 79 | json_body, err := d.ToJson() 80 | 81 | if err != nil { 82 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 83 | } 84 | 85 | body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body) 86 | 87 | if err != nil { 88 | return nil, errors.New(fmt.Sprintf("creating %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 89 | } 90 | 91 | if status != 200 { 92 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 93 | } 94 | 95 | return models.NewProjectV1AlphaFromJson(body) 96 | } 97 | 98 | func (c *ProjectApiV1AlphaApi) UpdateProject(d *models.ProjectV1Alpha) (*models.ProjectV1Alpha, error) { 99 | json_body, err := d.ToJson() 100 | 101 | if err != nil { 102 | return nil, errors.New(fmt.Sprintf("failed to serialize %s object '%s'", c.ResourceNameSingular, err)) 103 | } 104 | 105 | identifier := d.Metadata.Id 106 | 107 | body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, identifier, json_body) 108 | 109 | if err != nil { 110 | return nil, errors.New(fmt.Sprintf("updating %s on Semaphore failed '%s'", c.ResourceNamePlural, err)) 111 | } 112 | 113 | if status != 200 { 114 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 115 | } 116 | 117 | return models.NewProjectV1AlphaFromJson(body) 118 | } 119 | -------------------------------------------------------------------------------- /api/client/secrets_v1_beta.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | 8 | models "github.com/semaphoreci/cli/api/models" 9 | ) 10 | 11 | type SecretApiV1BetaApi struct { 12 | BaseClient BaseClient 13 | ResourceNameSingular string 14 | ResourceNamePlural string 15 | } 16 | 17 | func NewSecretV1BetaApi() SecretApiV1BetaApi { 18 | baseClient := NewBaseClientFromConfig() 19 | baseClient.SetApiVersion("v1beta") 20 | 21 | return SecretApiV1BetaApi{ 22 | BaseClient: baseClient, 23 | ResourceNamePlural: "secrets", 24 | ResourceNameSingular: "secret", 25 | } 26 | } 27 | 28 | func (c *SecretApiV1BetaApi) ListSecrets() (*models.SecretListV1Beta, error) { 29 | body, status, err := c.BaseClient.List(c.ResourceNamePlural) 30 | 31 | if err != nil { 32 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 33 | } 34 | 35 | if status != 200 { 36 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 37 | } 38 | 39 | return models.NewSecretListV1BetaFromJson(body) 40 | } 41 | 42 | func (c *SecretApiV1BetaApi) GetSecret(name string) (*models.SecretV1Beta, error) { 43 | body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name) 44 | 45 | if err != nil { 46 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 47 | } 48 | 49 | if status != 200 { 50 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 51 | } 52 | 53 | return models.NewSecretV1BetaFromJson(body) 54 | } 55 | 56 | func (c *SecretApiV1BetaApi) DeleteSecret(name string) error { 57 | body, status, err := c.BaseClient.Delete(c.ResourceNamePlural, name) 58 | 59 | if err != nil { 60 | return err 61 | } 62 | 63 | if status != 200 { 64 | return fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (c *SecretApiV1BetaApi) CreateSecret(d *models.SecretV1Beta) (*models.SecretV1Beta, error) { 71 | json_body, err := d.ToJson() 72 | 73 | if err != nil { 74 | return nil, errors.New(fmt.Sprintf("failed to serialize object '%s'", err)) 75 | } 76 | 77 | body, status, err := c.BaseClient.Post(c.ResourceNamePlural, json_body) 78 | 79 | if err != nil { 80 | return nil, errors.New(fmt.Sprintf("creating %s on Semaphore failed '%s'", c.ResourceNameSingular, err)) 81 | } 82 | 83 | if status != 200 { 84 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 85 | } 86 | 87 | return models.NewSecretV1BetaFromJson(body) 88 | } 89 | 90 | func (c *SecretApiV1BetaApi) UpdateSecret(d *models.SecretV1Beta) (*models.SecretV1Beta, error) { 91 | json_body, err := d.ToJson() 92 | 93 | if err != nil { 94 | return nil, errors.New(fmt.Sprintf("failed to serialize %s object '%s'", c.ResourceNameSingular, err)) 95 | } 96 | 97 | identifier := "" 98 | 99 | if d.Metadata.Id != "" { 100 | identifier = d.Metadata.Id 101 | } else { 102 | identifier = d.Metadata.Name 103 | } 104 | 105 | body, status, err := c.BaseClient.Patch(c.ResourceNamePlural, identifier, json_body) 106 | 107 | if err != nil { 108 | return nil, errors.New(fmt.Sprintf("updating %s on Semaphore failed '%s'", c.ResourceNamePlural, err)) 109 | } 110 | 111 | if status != 200 { 112 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 113 | } 114 | 115 | return models.NewSecretV1BetaFromJson(body) 116 | } 117 | 118 | func (c *SecretApiV1BetaApi) FallbackUpdate(d *models.SecretV1Beta) (*models.SecretV1Beta, error) { 119 | identifier := "" 120 | 121 | if d.Metadata.Id != "" { 122 | identifier = d.Metadata.Id 123 | } else { 124 | identifier = d.Metadata.Name 125 | } 126 | 127 | err := c.DeleteSecret(identifier) 128 | 129 | if err != nil { 130 | log.Println("fallbackUpdate:", err) 131 | return nil, fmt.Errorf("updating %s on Semaphore failed '%s'", c.ResourceNamePlural, err) 132 | } 133 | 134 | return c.CreateSecret(d) 135 | } 136 | -------------------------------------------------------------------------------- /api/client/troubleshoot_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | 6 | models "github.com/semaphoreci/cli/api/models" 7 | ) 8 | 9 | type TroubleshootApiV1AlphaApi struct { 10 | BaseClient BaseClient 11 | ResourceNameSingular string 12 | ResourceNamePlural string 13 | } 14 | 15 | func NewTroubleshootV1AlphaApi() TroubleshootApiV1AlphaApi { 16 | baseClient := NewBaseClientFromConfig() 17 | baseClient.SetApiVersion("v1alpha") 18 | 19 | return TroubleshootApiV1AlphaApi{ 20 | BaseClient: baseClient, 21 | ResourceNamePlural: "troubleshoot", 22 | ResourceNameSingular: "troubleshoot", 23 | } 24 | } 25 | 26 | func (c *TroubleshootApiV1AlphaApi) TroubleshootWorkflow(workflowID string) (*models.TroubleshootV1Alpha, error) { 27 | urlEncode := fmt.Sprintf("%s/workflow", c.ResourceNamePlural) 28 | body, status, err := c.BaseClient.Get(urlEncode, workflowID) 29 | 30 | if err != nil { 31 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 32 | } 33 | 34 | if status != 200 { 35 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 36 | } 37 | 38 | return models.NewTroubleshootV1AlphaFromJson(body) 39 | } 40 | 41 | func (c *TroubleshootApiV1AlphaApi) TroubleshootJob(jobID string) (*models.TroubleshootV1Alpha, error) { 42 | urlEncode := fmt.Sprintf("%s/job", c.ResourceNamePlural) 43 | body, status, err := c.BaseClient.Get(urlEncode, jobID) 44 | 45 | if err != nil { 46 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 47 | } 48 | 49 | if status != 200 { 50 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 51 | } 52 | 53 | return models.NewTroubleshootV1AlphaFromJson(body) 54 | } 55 | 56 | func (c *TroubleshootApiV1AlphaApi) TroubleshootPipeline(pplID string) (*models.TroubleshootV1Alpha, error) { 57 | urlEncode := fmt.Sprintf("%s/pipeline/", c.ResourceNamePlural) 58 | body, status, err := c.BaseClient.Get(urlEncode, pplID) 59 | 60 | if err != nil { 61 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 62 | } 63 | 64 | if status != 200 { 65 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 66 | } 67 | 68 | return models.NewTroubleshootV1AlphaFromJson(body) 69 | } 70 | -------------------------------------------------------------------------------- /api/client/workflows_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | 8 | models "github.com/semaphoreci/cli/api/models" 9 | "github.com/semaphoreci/cli/api/uuid" 10 | ) 11 | 12 | type WorkflowApiV1AlphaApi struct { 13 | BaseClient BaseClient 14 | ResourceNameSingular string 15 | ResourceNamePlural string 16 | } 17 | 18 | func NewWorkflowV1AlphaApi() WorkflowApiV1AlphaApi { 19 | baseClient := NewBaseClientFromConfig() 20 | baseClient.SetApiVersion("v1alpha") 21 | 22 | return WorkflowApiV1AlphaApi{ 23 | BaseClient: baseClient, 24 | ResourceNamePlural: "plumber-workflows", 25 | ResourceNameSingular: "plumber-workflow", 26 | } 27 | } 28 | 29 | func (c *WorkflowApiV1AlphaApi) ListWorkflows(project_id string) (*models.WorkflowListV1Alpha, error) { 30 | urlEncode := fmt.Sprintf("%s?project_id=%s", c.ResourceNamePlural, project_id) 31 | body, status, err := c.BaseClient.List(urlEncode) 32 | 33 | if err != nil { 34 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 35 | } 36 | 37 | if status != 200 { 38 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 39 | } 40 | 41 | return models.NewWorkflowListV1AlphaFromJson(body) 42 | } 43 | 44 | func (c *WorkflowApiV1AlphaApi) ListWorkflowsWithOptions(projectID string, options ListOptions) (*models.WorkflowListV1Alpha, error) { 45 | query := url.Values{} 46 | query.Add("project_id", projectID) 47 | 48 | if options.CreatedAfter > 0 { 49 | query.Add("created_after", fmt.Sprintf("%d", options.CreatedAfter)) 50 | } 51 | 52 | if options.CreatedBefore > 0 { 53 | query.Add("created_before", fmt.Sprintf("%d", options.CreatedBefore)) 54 | } 55 | 56 | body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query) 57 | if err != nil { 58 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 59 | } 60 | 61 | if status != 200 { 62 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 63 | } 64 | 65 | return models.NewWorkflowListV1AlphaFromJson(body) 66 | } 67 | 68 | func (c *WorkflowApiV1AlphaApi) CreateSnapshotWf(project_id, label, archivePath string) ([]byte, error) { 69 | requestToken, err := uuid.NewUUID() 70 | 71 | if err != nil { 72 | return nil, fmt.Errorf("uuid creation failed '%s'", err) 73 | } 74 | 75 | args := make(map[string]string) 76 | args["project_id"] = project_id 77 | args["label"] = label 78 | args["request_token"] = requestToken.String() 79 | 80 | headers := make(map[string]string) 81 | headers["Content-Type"] = "application/x-www-form-urlencoded" 82 | 83 | body, status, err := c.BaseClient.PostMultipart(c.ResourceNamePlural, args, "snapshot_archive", archivePath) 84 | 85 | switch { 86 | case err != nil: 87 | return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err) 88 | case status != 200: 89 | return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body) 90 | } 91 | 92 | return body, nil 93 | } 94 | 95 | func (c *WorkflowApiV1AlphaApi) StopWf(id string) ([]byte, error) { 96 | requestToken, err := uuid.NewUUID() 97 | 98 | if err != nil { 99 | return nil, errors.New(fmt.Sprintf("request token generation failed '%s'", err)) 100 | } 101 | 102 | actionArgs := fmt.Sprintf("%s?%s=%s", "terminate", "request_token", requestToken.String()) 103 | body, status, err := c.BaseClient.PostAction(c.ResourceNamePlural, id, actionArgs, []byte("")) 104 | 105 | if err != nil { 106 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 107 | } 108 | 109 | if status != 200 { 110 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 111 | } 112 | 113 | return body, nil 114 | } 115 | 116 | func (c *WorkflowApiV1AlphaApi) Rebuild(id string) ([]byte, error) { 117 | requestToken, err := uuid.NewUUID() 118 | 119 | if err != nil { 120 | return nil, errors.New(fmt.Sprintf("request token generation failed '%s'", err)) 121 | } 122 | 123 | actionArgs := fmt.Sprintf("%s?%s=%s", "reschedule", "request_token", requestToken.String()) 124 | body, status, err := c.BaseClient.PostAction(c.ResourceNamePlural, id, actionArgs, []byte("")) 125 | 126 | if err != nil { 127 | return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) 128 | } 129 | 130 | if status != 200 { 131 | return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) 132 | } 133 | 134 | return body, nil 135 | } 136 | -------------------------------------------------------------------------------- /api/models/agent_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type AgentListV1Alpha struct { 6 | Agents []AgentV1Alpha `json:"agents" yaml:"agents"` 7 | Cursor string `json:"cursor" yaml:"cursor"` 8 | } 9 | 10 | func NewAgentListV1AlphaFromJson(data []byte) (*AgentListV1Alpha, error) { 11 | list := AgentListV1Alpha{} 12 | 13 | err := json.Unmarshal(data, &list) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | for _, s := range list.Agents { 19 | if s.ApiVersion == "" { 20 | s.ApiVersion = "v1alpha" 21 | } 22 | 23 | if s.Kind == "" { 24 | s.Kind = "SelfHostedAgent" 25 | } 26 | } 27 | 28 | return &list, nil 29 | } 30 | -------------------------------------------------------------------------------- /api/models/agent_type_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type AgentTypeListV1Alpha struct { 6 | AgentTypes []AgentTypeV1Alpha `json:"agent_types" yaml:"agent_types"` 7 | } 8 | 9 | func NewAgentTypeListV1AlphaFromJson(data []byte) (*AgentTypeListV1Alpha, error) { 10 | list := AgentTypeListV1Alpha{} 11 | 12 | err := json.Unmarshal(data, &list) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | for _, s := range list.AgentTypes { 18 | if s.ApiVersion == "" { 19 | s.ApiVersion = "v1alpha" 20 | } 21 | 22 | if s.Kind == "" { 23 | s.Kind = KindSelfHostedAgentType 24 | } 25 | } 26 | 27 | return &list, nil 28 | } 29 | -------------------------------------------------------------------------------- /api/models/agent_type_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | const KindSelfHostedAgentType = "SelfHostedAgentType" 11 | 12 | var SelfHostedAgentTypeAssignmentOrigins = []string{ 13 | "assignment_origin_agent", 14 | "assignment_origin_aws_sts", 15 | } 16 | 17 | type AgentTypeV1Alpha struct { 18 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 19 | Kind string `json:"kind,omitempty" yaml:"kind"` 20 | Metadata AgentTypeV1AlphaMetadata `json:"metadata" yaml:"metadata"` 21 | Spec AgentTypeV1AlphaSpec `json:"spec" yaml:"spec"` 22 | Status AgentTypeV1AlphaStatus `json:"status" yaml:"status"` 23 | } 24 | 25 | type AgentTypeV1AlphaMetadata struct { 26 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 27 | CreateTime json.Number `json:"create_time,omitempty" yaml:"create_time,omitempty"` 28 | UpdateTime json.Number `json:"update_time,omitempty" yaml:"update_time,omitempty"` 29 | } 30 | 31 | type AgentTypeV1AlphaSpec struct { 32 | AgentNameSettings AgentTypeV1AlphaAgentNameSettings `json:"agent_name_settings" yaml:"agent_name_settings"` 33 | } 34 | 35 | type AgentTypeV1AlphaAgentNameSettings struct { 36 | AssignmentOrigin string `json:"assignment_origin,omitempty" yaml:"assignment_origin,omitempty"` 37 | Aws AgentTypeV1AlphaAws `json:"aws,omitempty" yaml:"aws,omitempty"` 38 | ReleaseAfter int64 `json:"release_after" yaml:"release_after"` 39 | } 40 | 41 | type AgentTypeV1AlphaAws struct { 42 | AccountID string `json:"account_id,omitempty" yaml:"account_id,omitempty"` 43 | RoleNamePatterns string `json:"role_name_patterns,omitempty" yaml:"role_name_patterns,omitempty"` 44 | } 45 | 46 | type AgentTypeV1AlphaStatus struct { 47 | TotalAgentCount int `json:"total_agent_count,omitempty" yaml:"total_agent_count,omitempty"` 48 | RegistrationToken string `json:"registration_token,omitempty" yaml:"registration_token,omitempty"` 49 | } 50 | 51 | func NewAgentTypeV1Alpha(name string) AgentTypeV1Alpha { 52 | a := AgentTypeV1Alpha{} 53 | a.Metadata.Name = name 54 | a.setApiVersionAndKind() 55 | return a 56 | } 57 | 58 | func NewAgentTypeV1AlphaFromJson(data []byte) (*AgentTypeV1Alpha, error) { 59 | a := AgentTypeV1Alpha{} 60 | 61 | err := json.Unmarshal(data, &a) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | a.setApiVersionAndKind() 67 | return &a, nil 68 | } 69 | 70 | func NewAgentTypeV1AlphaFromYaml(data []byte) (*AgentTypeV1Alpha, error) { 71 | a := AgentTypeV1Alpha{} 72 | 73 | err := yaml.UnmarshalStrict(data, &a) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | a.setApiVersionAndKind() 79 | 80 | if a.Spec.AgentNameSettings.AssignmentOrigin == "" { 81 | a.Spec.AgentNameSettings.AssignmentOrigin = "assignment_origin_agent" 82 | } 83 | 84 | return &a, nil 85 | } 86 | 87 | func (s *AgentTypeV1Alpha) setApiVersionAndKind() { 88 | s.ApiVersion = "v1alpha" 89 | s.Kind = KindSelfHostedAgentType 90 | } 91 | 92 | func (s *AgentTypeV1Alpha) ObjectName() string { 93 | return fmt.Sprintf("%s/%s", KindSelfHostedAgentType, s.Metadata.Name) 94 | } 95 | 96 | func (s *AgentTypeV1Alpha) ToJson() ([]byte, error) { 97 | return json.Marshal(s) 98 | } 99 | 100 | func (s *AgentTypeV1Alpha) ToYaml() ([]byte, error) { 101 | return yaml.Marshal(s) 102 | } 103 | -------------------------------------------------------------------------------- /api/models/agent_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type AgentV1Alpha struct { 11 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 12 | Kind string `json:"kind,omitempty" yaml:"kind"` 13 | Metadata AgentV1AlphaMetadata `json:"metadata" yaml:"metadata"` 14 | Status AgentV1AlphaStatus `json:"status" yaml:"status"` 15 | } 16 | 17 | type AgentV1AlphaMetadata struct { 18 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 19 | Type string `json:"type,omitempty" yaml:"type,omitempty"` 20 | ConnectedAt json.Number `json:"connected_at,omitempty" yaml:"connected_at,omitempty"` 21 | DisabledAt json.Number `json:"disabled_at,omitempty" yaml:"disabled_at,omitempty"` 22 | Version string `json:"version,omitempty" yaml:"version,omitempty"` 23 | OS string `json:"os,omitempty" yaml:"os,omitempty"` 24 | Arch string `json:"arch,omitempty" yaml:"arch,omitempty"` 25 | Hostname string `json:"hostname,omitempty" yaml:"hostname,omitempty"` 26 | IPAddress string `json:"ip_address,omitempty" yaml:"ip_address,omitempty"` 27 | PID json.Number `json:"pid,omitempty" yaml:"pid,omitempty"` 28 | } 29 | 30 | type AgentV1AlphaStatus struct { 31 | State string `json:"state,omitempty" yaml:"state,omitempty"` 32 | } 33 | 34 | func NewAgentV1Alpha(name string) AgentV1Alpha { 35 | a := AgentV1Alpha{} 36 | a.Metadata.Name = name 37 | a.setApiVersionAndKind() 38 | return a 39 | } 40 | 41 | func NewAgentV1AlphaFromJson(data []byte) (*AgentV1Alpha, error) { 42 | a := AgentV1Alpha{} 43 | 44 | err := json.Unmarshal(data, &a) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | a.setApiVersionAndKind() 50 | return &a, nil 51 | } 52 | 53 | func NewAgentV1AlphaFromYaml(data []byte) (*AgentV1Alpha, error) { 54 | a := AgentV1Alpha{} 55 | 56 | err := yaml.UnmarshalStrict(data, &a) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | a.setApiVersionAndKind() 62 | return &a, nil 63 | } 64 | 65 | func (s *AgentV1Alpha) setApiVersionAndKind() { 66 | s.ApiVersion = "v1alpha" 67 | s.Kind = "SelfHostedAgent" 68 | } 69 | 70 | func (s *AgentV1Alpha) ObjectName() string { 71 | return fmt.Sprintf("SelfHostedAgent/%s", s.Metadata.Name) 72 | } 73 | 74 | func (s *AgentV1Alpha) ToJson() ([]byte, error) { 75 | return json.Marshal(s) 76 | } 77 | 78 | func (s *AgentV1Alpha) ToYaml() ([]byte, error) { 79 | return yaml.Marshal(s) 80 | } 81 | -------------------------------------------------------------------------------- /api/models/dashboard_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type DashboardListV1Alpha struct { 8 | Dashboards []DashboardV1Alpha `json:"dashboards" yaml:"dashboards"` 9 | } 10 | 11 | func NewDashboardListV1AlphaFromJson(data []byte) (*DashboardListV1Alpha, error) { 12 | list := DashboardListV1Alpha{} 13 | 14 | err := json.Unmarshal(data, &list) 15 | 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | for _, s := range list.Dashboards { 21 | if s.ApiVersion == "" { 22 | s.ApiVersion = "v1alpha" 23 | } 24 | 25 | if s.Kind == "" { 26 | s.Kind = "Dashboard" 27 | } 28 | } 29 | 30 | return &list, nil 31 | } 32 | -------------------------------------------------------------------------------- /api/models/dashboard_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type DashboardV1Alpha struct { 11 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 12 | Kind string `json:"kind,omitempty" yaml:"kind"` 13 | Metadata struct { 14 | Name string `json:"name,omitempty"` 15 | Title string `json:"title,omitempty"` 16 | Id string `json:"id,omitempty"` 17 | CreateTime json.Number `json:"create_time,omitempty,string" yaml:"create_time,omitempty"` 18 | UpdateTime json.Number `json:"update_time,omitempty,string" yaml:"update_time,omitempty"` 19 | } `json:"metadata,omitempty"` 20 | 21 | Spec struct { 22 | Widgets []struct { 23 | Name string `json:"name,omitempty"` 24 | Type string `json:"type,omitempty"` 25 | Filters map[string]string `json:"filters,omitempty"` 26 | } `json:"widgets,omitempty"` 27 | } `json:"spec,omitempty"` 28 | } 29 | 30 | func NewDashboardV1Alpha(name string) DashboardV1Alpha { 31 | d := DashboardV1Alpha{} 32 | 33 | d.Metadata.Name = name 34 | d.setApiVersionAndKind() 35 | 36 | return d 37 | } 38 | 39 | func NewDashboardV1AlphaFromJson(data []byte) (*DashboardV1Alpha, error) { 40 | d := DashboardV1Alpha{} 41 | 42 | err := json.Unmarshal(data, &d) 43 | 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | d.setApiVersionAndKind() 49 | 50 | return &d, nil 51 | } 52 | 53 | func NewDashboardV1AlphaFromYaml(data []byte) (*DashboardV1Alpha, error) { 54 | d := DashboardV1Alpha{} 55 | 56 | err := yaml.UnmarshalStrict(data, &d) 57 | 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | d.setApiVersionAndKind() 63 | 64 | return &d, nil 65 | } 66 | 67 | func (d *DashboardV1Alpha) setApiVersionAndKind() { 68 | d.ApiVersion = "v1alpha" 69 | d.Kind = "Dashboard" 70 | } 71 | 72 | func (d *DashboardV1Alpha) ObjectName() string { 73 | return fmt.Sprintf("Dashboards/%s", d.Metadata.Name) 74 | } 75 | 76 | func (d *DashboardV1Alpha) ToJson() ([]byte, error) { 77 | return json.Marshal(d) 78 | } 79 | 80 | func (d *DashboardV1Alpha) ToYaml() ([]byte, error) { 81 | return yaml.Marshal(d) 82 | } 83 | -------------------------------------------------------------------------------- /api/models/debug_job_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type DebugJobV1Alpha struct { 6 | JobId string `json:"job_id,omitempty" yaml:"job_id"` 7 | Duration int `json:"duration,omitempty,string" yaml:"duration,omitempty"` 8 | } 9 | 10 | func NewDebugJobV1Alpha(job_id string, duration int) (*DebugJobV1Alpha) { 11 | j := DebugJobV1Alpha{} 12 | 13 | j.JobId = job_id 14 | j.Duration = duration 15 | 16 | return &j 17 | } 18 | 19 | func (j *DebugJobV1Alpha) ToJson() ([]byte, error) { 20 | return json.Marshal(j) 21 | } 22 | -------------------------------------------------------------------------------- /api/models/debug_project_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type DebugProjectV1Alpha struct { 6 | ProjectIdOrName string `json:"project_id_or_name,omitempty" yaml:"project_id_or_name"` 7 | Duration int `json:"duration,omitempty,string" yaml:"duration,omitempty"` 8 | MachineType string `json:"machine_type,omitempty" yaml:"machine_type,omitempty"` 9 | } 10 | 11 | func NewDebugProjectV1Alpha(project string, duration int, machine string) *DebugProjectV1Alpha { 12 | j := DebugProjectV1Alpha{} 13 | 14 | j.ProjectIdOrName = project 15 | j.Duration = duration 16 | j.MachineType = machine 17 | 18 | return &j 19 | } 20 | 21 | func (j *DebugProjectV1Alpha) ToJson() ([]byte, error) { 22 | return json.Marshal(j) 23 | } 24 | -------------------------------------------------------------------------------- /api/models/deployment_target_v1_alpha_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDeploymentTargetsToJson(t *testing.T) { 10 | dt := DeploymentTargetV1Alpha{ 11 | ApiVersion: "v1alpha", 12 | Kind: DeploymentTargetKindV1Alpha, 13 | DeploymentTargetMetadataV1Alpha: DeploymentTargetMetadataV1Alpha{ 14 | Id: "1234-5678-id", 15 | Name: "dt-name", 16 | OrganizationId: "org-id", 17 | ProjectId: "prj-id", 18 | Description: "dt-description", 19 | Url: "www.semaphore.xyz", 20 | }, 21 | DeploymentTargetSpecV1Alpha: DeploymentTargetSpecV1Alpha{ 22 | Active: false, 23 | BookmarkParameter1: "book1", 24 | SubjectRules: []*SubjectRuleV1Alpha{{ 25 | Type: "USER", 26 | SubjectId: "00000000-0000-0000-0000-000000000000", 27 | }}, 28 | ObjectRules: []*ObjectRuleV1Alpha{ 29 | {Type: "BRANCH", MatchMode: "EXACT", Pattern: ".*main.*"}, 30 | }, 31 | State: "CORDONED", 32 | }, 33 | } 34 | 35 | received_json, err := dt.ToJson() 36 | assert.Nil(t, err) 37 | 38 | expected_json := `{"id":"1234-5678-id","name":"dt-name","project_id":"prj-id","organization_id":"org-id","description":"dt-description","url":"www.semaphore.xyz","state":"CORDONED","state_message":"","subject_rules":[{"type":"USER","subject_id":"00000000-0000-0000-0000-000000000000"}],"object_rules":[{"type":"BRANCH","match_mode":"EXACT","pattern":".*main.*"}],"active":false,"bookmark_parameter1":"book1","bookmark_parameter2":"","bookmark_parameter3":""}` 39 | assert.Equal(t, expected_json, string(received_json)) 40 | } 41 | 42 | func TestDeploymentTargetsToYaml(t *testing.T) { 43 | dt := DeploymentTargetV1Alpha{ 44 | ApiVersion: "v1alpha", 45 | Kind: DeploymentTargetKindV1Alpha, 46 | DeploymentTargetMetadataV1Alpha: DeploymentTargetMetadataV1Alpha{ 47 | Id: "1234-5678-id", 48 | Name: "dt-name", 49 | OrganizationId: "org-id", 50 | ProjectId: "prj-id", 51 | Description: "dt-description", 52 | Url: "www.semaphore.xyz", 53 | }, 54 | DeploymentTargetSpecV1Alpha: DeploymentTargetSpecV1Alpha{ 55 | Active: true, 56 | BookmarkParameter1: "book1", 57 | SubjectRules: []*SubjectRuleV1Alpha{{ 58 | Type: "USER", 59 | SubjectId: "subjId1", 60 | }}, 61 | ObjectRules: []*ObjectRuleV1Alpha{ 62 | {Type: "BRANCH", MatchMode: "EXACT", Pattern: ".*main.*"}, 63 | }, 64 | State: "USABLE", 65 | }, 66 | } 67 | received_yaml, err := dt.ToYaml() 68 | assert.Nil(t, err) 69 | 70 | expected_yaml := `apiVersion: v1alpha 71 | kind: DeploymentTarget 72 | metadata: 73 | id: 1234-5678-id 74 | name: dt-name 75 | project_id: prj-id 76 | organization_id: org-id 77 | description: dt-description 78 | url: www.semaphore.xyz 79 | spec: 80 | state: USABLE 81 | state_message: "" 82 | subject_rules: 83 | - type: USER 84 | subject_id: subjId1 85 | object_rules: 86 | - type: BRANCH 87 | match_mode: EXACT 88 | pattern: .*main.* 89 | active: true 90 | bookmark_parameter1: book1 91 | bookmark_parameter2: "" 92 | bookmark_parameter3: "" 93 | ` 94 | assert.Equal(t, expected_yaml, string(received_yaml)) 95 | } 96 | 97 | func TestDeploymentTargetsFromYaml(t *testing.T) { 98 | content := `apiVersion: v1alpha 99 | kind: DeploymentTarget 100 | metadata: 101 | id: 1234-5678-id 102 | name: dt-name 103 | organization_id: org-id 104 | project_id: prj-id 105 | url: www.semaphore.xyz 106 | description: dt-description 107 | spec: 108 | active: true 109 | bookmark_parameter1: book1 110 | ` 111 | dt, err := NewDeploymentTargetV1AlphaFromYaml([]byte(content)) 112 | assert.Nil(t, err) 113 | 114 | assert.Equal(t, dt.Id, "1234-5678-id") 115 | assert.Equal(t, dt.Name, "dt-name") 116 | assert.Equal(t, dt.ProjectId, "prj-id") 117 | assert.Equal(t, dt.OrganizationId, "org-id") 118 | assert.Equal(t, dt.Url, "www.semaphore.xyz") 119 | assert.Equal(t, dt.Description, "dt-description") 120 | assert.True(t, dt.Active) 121 | assert.Equal(t, dt.BookmarkParameter1, "book1") 122 | } 123 | 124 | func TestDeploymentTargetsFromJSON(t *testing.T) { 125 | content := `{"id":"1234-5678-id","name":"dt-name","project_id":"prj-id","organization_id":"org-id","description":"dt-description","url":"www.semaphore.xyz","state":"USABLE","subject_rules":[{"type":"USER","subject_id":"00000000-0000-0000-0000-000000000000"}],"object_rules":[{"type":"BRANCH","match_mode":"REGEX","pattern":".*main.*"}],"active":true,"bookmark_parameter1":"book1","env_vars":[{"name":"Var1","value":"Val1"}],"files":[{"path":"/etc/config.yml","content":"abcdefgh"}]}` 126 | dt, err := NewDeploymentTargetV1AlphaFromJson([]byte(content)) 127 | assert.Nil(t, err) 128 | 129 | assert.Equal(t, dt.Id, "1234-5678-id") 130 | assert.Equal(t, dt.Name, "dt-name") 131 | assert.Equal(t, dt.ProjectId, "prj-id") 132 | assert.Equal(t, dt.OrganizationId, "org-id") 133 | assert.Equal(t, dt.Url, "www.semaphore.xyz") 134 | assert.Equal(t, dt.Description, "dt-description") 135 | assert.True(t, dt.Active) 136 | assert.Equal(t, dt.BookmarkParameter1, "book1") 137 | } 138 | -------------------------------------------------------------------------------- /api/models/job_debug_ssh_key_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type JobDebugSSHKeyV1Alpha struct { 6 | Key string `json:"key,omitempty" yaml:"key"` 7 | } 8 | 9 | func NewJobDebugSSHKeyV1AlphaFromJSON(data []byte) (*JobDebugSSHKeyV1Alpha, error) { 10 | key := JobDebugSSHKeyV1Alpha{} 11 | 12 | err := json.Unmarshal(data, &key) 13 | 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | return &key, nil 19 | } 20 | -------------------------------------------------------------------------------- /api/models/job_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type JobListV1Alpha struct { 8 | Jobs []JobV1Alpha `json:"jobs" yaml:"jobs"` 9 | } 10 | 11 | func NewJobListV1AlphaFromJson(data []byte) (*JobListV1Alpha, error) { 12 | list := JobListV1Alpha{} 13 | 14 | err := json.Unmarshal(data, &list) 15 | 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | for _, j := range list.Jobs { 21 | if j.ApiVersion == "" { 22 | j.ApiVersion = "v1alpha" 23 | } 24 | 25 | if j.Kind == "" { 26 | j.Kind = "Job" 27 | } 28 | } 29 | 30 | return &list, nil 31 | } 32 | -------------------------------------------------------------------------------- /api/models/job_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/semaphoreci/cli/cmd/jobs" 8 | yaml "gopkg.in/yaml.v2" 9 | ) 10 | 11 | type JobV1AlphaMetadata struct { 12 | Name string `json:"name,omitempty"` 13 | Id string `json:"id,omitempty"` 14 | CreateTime json.Number `json:"create_time,omitempty,string" yaml:"create_time,omitempty"` 15 | UpdateTime json.Number `json:"update_time,omitempty,string" yaml:"update_time,omitempty"` 16 | StartTime json.Number `json:"start_time,omitempty,string" yaml:"start_time,omitempty"` 17 | FinishTime json.Number `json:"finish_time,omitempty,string" yaml:"finish_time,omitempty"` 18 | } 19 | 20 | type JobV1AlphaSpecSecret struct { 21 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 22 | } 23 | 24 | type JobV1AlphaSpecFile struct { 25 | Path string `json:"path,omitempty" yaml:"path,omitempty"` 26 | Content string `json:"content,omitempty" yaml:"content,omitempty"` 27 | } 28 | 29 | type JobV1AlphaSpecEnvVar struct { 30 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 31 | Value string `json:"value,omitempty" yaml:"value,omitempty"` 32 | } 33 | 34 | type JobV1AlphaAgentImagePullSecret struct { 35 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 36 | } 37 | 38 | type JobV1AlphaAgentMachine struct { 39 | Type string `json:"type,omitempty" yaml:"type,omitempty"` 40 | OsImage string `json:"os_image,omitempty" yaml:"os_image,omitempty"` 41 | } 42 | 43 | type JobV1AlphaAgentContainer struct { 44 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 45 | Image string `json:"image,omitempty" yaml:"image,omitempty"` 46 | Command string `json:"command,omitempty" yaml:"command,omitempty"` 47 | EnvVars []JobV1AlphaSpecEnvVar `json:"env_vars,omitempty" yaml:"env_vars,omitempty"` 48 | Secrets []JobV1AlphaSpecSecret `json:"secrets,omitempty" yaml:"secrets,omitempty"` 49 | } 50 | 51 | type JobV1AlphaAgent struct { 52 | Machine JobV1AlphaAgentMachine `json:"machine,omitempty" yaml:"machine,omitempty"` 53 | Containers []JobV1AlphaAgentContainer `json:"containers,omitempty" yaml:"containers,omitempty"` 54 | ImagePullSecrets []JobV1AlphaAgentImagePullSecret `json:"image_pull_secrets,omitempty" yaml:"image_pull_secrets,omitempty"` 55 | } 56 | 57 | type JobV1AlphaSpec struct { 58 | Agent JobV1AlphaAgent `json:"agent,omitempty" yaml:"agent,omitempty"` 59 | Files []JobV1AlphaSpecFile `json:"files,omitempty" yaml:"files,omitempty"` 60 | EnvVars []JobV1AlphaSpecEnvVar `json:"env_vars,omitempty" yaml:"env_vars,omitempty"` 61 | Secrets []JobV1AlphaSpecSecret `json:"secrets,omitempty" yaml:"secrets,omitempty"` 62 | Commands []string `json:"commands,omitempty" yaml:"commands,omitempty"` 63 | EpilogueAlwaysCommands []string `json:"epilogue_always_commands,omitempty" yaml:"epilogue_always_commands,omitempty"` 64 | EpilogueOnPassCommands []string `json:"epilogue_on_pass_commands,omitempty" yaml:"epilogue_on_pass_commands,omitempty"` 65 | EpilogueOnFailCommands []string `json:"epilogue_on_fail_commands,omitempty" yaml:"epilogue_on_fail_commands,omitempty"` 66 | ProjectId string `json:"project_id,omitempty" yaml:"project_id,omitempty"` 67 | } 68 | 69 | type JobV1AlphaStatus struct { 70 | State string `json:"state" yaml:"state"` 71 | Result string `json:"result" yaml:"result"` 72 | Agent struct { 73 | Ip string `json:"ip" yaml:"ip"` 74 | Name string `json:"name" yaml:"name"` 75 | Ports []struct { 76 | Name string `json:"name" yaml:"name"` 77 | Number int32 `json:"number" yaml:"number"` 78 | } `json:"ports,omitempty"` 79 | } `json:"agent,omitempty"` 80 | } 81 | 82 | type JobV1Alpha struct { 83 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 84 | Kind string `json:"kind,omitempty" yaml:"kind"` 85 | 86 | Metadata *JobV1AlphaMetadata `json:"metadata,omitempty"` 87 | Spec *JobV1AlphaSpec `json:"spec,omitempty"` 88 | Status *JobV1AlphaStatus `json:"status,omitempty"` 89 | } 90 | 91 | func NewJobV1Alpha(name string) *JobV1Alpha { 92 | j := JobV1Alpha{} 93 | 94 | j.Metadata = &JobV1AlphaMetadata{} 95 | j.Metadata.Name = name 96 | j.setApiVersionAndKind() 97 | 98 | return &j 99 | } 100 | 101 | func NewJobV1AlphaFromJson(data []byte) (*JobV1Alpha, error) { 102 | j := JobV1Alpha{} 103 | 104 | err := json.Unmarshal(data, &j) 105 | 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | j.setApiVersionAndKind() 111 | 112 | return &j, nil 113 | } 114 | 115 | func NewJobV1AlphaFromYaml(data []byte) (*JobV1Alpha, error) { 116 | j := JobV1Alpha{} 117 | 118 | err := yaml.UnmarshalStrict(data, &j) 119 | 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | j.setApiVersionAndKind() 125 | 126 | return &j, nil 127 | } 128 | 129 | func (j *JobV1Alpha) setApiVersionAndKind() { 130 | j.ApiVersion = "v1alpha" 131 | j.Kind = "Job" 132 | } 133 | 134 | func (j *JobV1Alpha) ObjectName() string { 135 | return fmt.Sprintf("Jobs/%s", j.Metadata.Id) 136 | } 137 | 138 | func (j *JobV1Alpha) ToJson() ([]byte, error) { 139 | return json.Marshal(j) 140 | } 141 | 142 | func (j *JobV1Alpha) ToYaml() ([]byte, error) { 143 | return yaml.Marshal(j) 144 | } 145 | 146 | func (j *JobV1Alpha) IsSelfHosted() bool { 147 | return jobs.IsSelfHosted(j.Spec.Agent.Machine.Type) 148 | } 149 | 150 | func (j *JobV1Alpha) AgentName() string { 151 | return j.Status.Agent.Name 152 | } 153 | -------------------------------------------------------------------------------- /api/models/logs_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type LogsV1Alpha struct { 6 | Events []Event `json:"events"` 7 | } 8 | 9 | type Event struct { 10 | Timestamp int32 `json:"timestamp"` 11 | Type string `json:"event"` 12 | Output string `json:"output"` 13 | Directive string `json:"directive"` 14 | ExitCode int32 `json:"exit_code"` 15 | JobResult string `json:"job_result"` 16 | } 17 | 18 | func NewLogsV1AlphaFromJson(data []byte) (*LogsV1Alpha, error) { 19 | t := LogsV1Alpha{} 20 | err := json.Unmarshal(data, &t) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | return &t, nil 26 | } 27 | -------------------------------------------------------------------------------- /api/models/notification_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type NotificationListV1Alpha struct { 8 | Notifications []NotificationV1Alpha `json:"notifications" yaml:"notifications"` 9 | NextPageToken string `json:"next_page_token,omitempty" yaml:"next_page_token,omitempty"` 10 | } 11 | 12 | func NewNotificationListV1AlphaFromJson(data []byte) (*NotificationListV1Alpha, error) { 13 | list := NotificationListV1Alpha{} 14 | 15 | err := json.Unmarshal(data, &list) 16 | 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | for _, s := range list.Notifications { 22 | if s.ApiVersion == "" { 23 | s.ApiVersion = "v1alpha" 24 | } 25 | 26 | if s.Kind == "" { 27 | s.Kind = "Notification" 28 | } 29 | } 30 | 31 | return &list, nil 32 | } 33 | -------------------------------------------------------------------------------- /api/models/notification_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type NotificationV1AlphaMetadata struct { 11 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 12 | Id string `json:"id,omitempty" yaml:"id,omitempty"` 13 | CreateTime json.Number `json:"create_time,omitempty,string" yaml:"create_time,omitempty"` 14 | UpdateTime json.Number `json:"update_time,omitempty,string" yaml:"update_time,omitempty"` 15 | } 16 | 17 | type NotificationV1AlphaSpecRuleFilter struct { 18 | Projects []string `json:"projects,omitempty" yaml:"projects,omitempty"` 19 | Branches []string `json:"branches,omitempty" yaml:"branches,omitempty"` 20 | Pipelines []string `json:"pipelines,omitempty" yaml:"pipelines,omitempty"` 21 | Blocks []string `json:"blocks,omitempty" yaml:"blocks,omitempty"` 22 | States []string `json:"states,omitempty" yaml:"states,omitempty"` 23 | Results []string `json:"results,omitempty" yaml:"results,omitempty"` 24 | } 25 | 26 | type NotificationV1AlphaSpecRuleNotify struct { 27 | Slack struct { 28 | Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"` 29 | Channels []string `json:"channels,omitempty" yaml:"channels,omitempty"` 30 | Message string `json:"message,omitempty" yaml:"message,omitempty"` 31 | } `json:"slack,omitempty" yaml:"slack,omitempty"` 32 | 33 | Email struct { 34 | Subject string `json:"subject,omitempty" yaml:"subject,omitempty"` 35 | CC []string `json:"cc,omitempty" yaml:"cc,omitempty"` 36 | BCC []string `json:"bcc,omitempty" yaml:"bcc,omitempty"` 37 | Content string `json:"content,omitempty" yaml:"content,omitempty"` 38 | } `json:"email,omitempty" yaml:"email,omitempty"` 39 | 40 | Webhook struct { 41 | Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"` 42 | Timeout int32 `json:"timeout,omitempty" yaml:"timeout,omitempty"` 43 | Action string `json:"action,omitempty" yaml:"action,omitempty"` 44 | Retries int32 `json:"retries,omitempty" yaml:"retries,omitempty"` 45 | Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` 46 | } `json:"webhook,omitempty" yaml:"webhook,omitempty"` 47 | } 48 | 49 | type NotificationV1AlphaSpecRule struct { 50 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 51 | Filter NotificationV1AlphaSpecRuleFilter `json:"filter,omitempty" yaml:"filter,omitempty"` 52 | Notify NotificationV1AlphaSpecRuleNotify `json:"notify,omitempty" yaml:"notify,omitempty"` 53 | } 54 | 55 | type NotificationV1AlphaSpec struct { 56 | Rules []NotificationV1AlphaSpecRule `json:"rules,omitempty" yaml:"rules,omitempty"` 57 | } 58 | 59 | type NotificationV1AlphaStatusFailure struct { 60 | Time json.Number `json:"time,omitempty,string" yaml:"time,omitempty"` 61 | Message string `json:"message,omitempty,string" yaml:"message,omitempty"` 62 | } 63 | 64 | type NotificationV1AlphaStatus struct { 65 | Failures []NotificationV1AlphaStatusFailure `json:"failures,omitempty" yaml:"failures,omitempty"` 66 | } 67 | 68 | type NotificationV1Alpha struct { 69 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 70 | Kind string `json:"kind,omitempty" yaml:"kind"` 71 | 72 | Metadata NotificationV1AlphaMetadata `json:"metadata" yaml:"metadata"` 73 | Spec NotificationV1AlphaSpec `json:"spec" yaml:"spec"` 74 | Status NotificationV1AlphaStatus `json:"status" yaml:"status"` 75 | } 76 | 77 | func NewNotificationV1Alpha(name string) *NotificationV1Alpha { 78 | n := NotificationV1Alpha{} 79 | 80 | n.setApiVersionAndKind() 81 | n.Metadata.Name = name 82 | 83 | return &n 84 | } 85 | 86 | func NewNotificationV1AlphaFromJson(data []byte) (*NotificationV1Alpha, error) { 87 | n := NotificationV1Alpha{} 88 | 89 | err := json.Unmarshal(data, &n) 90 | 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | n.setApiVersionAndKind() 96 | 97 | return &n, nil 98 | } 99 | 100 | func NewNotificationV1AlphaFromYaml(data []byte) (*NotificationV1Alpha, error) { 101 | n := NotificationV1Alpha{} 102 | 103 | err := yaml.UnmarshalStrict(data, &n) 104 | 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | n.setApiVersionAndKind() 110 | 111 | return &n, nil 112 | } 113 | 114 | func (n *NotificationV1Alpha) setApiVersionAndKind() { 115 | n.ApiVersion = "v1alpha" 116 | n.Kind = "Notification" 117 | } 118 | 119 | func (n *NotificationV1Alpha) ObjectName() string { 120 | return fmt.Sprintf("Notifications/%s", n.Metadata.Name) 121 | } 122 | 123 | func (n *NotificationV1Alpha) ToJson() ([]byte, error) { 124 | return json.Marshal(n) 125 | } 126 | 127 | func (n *NotificationV1Alpha) ToYaml() ([]byte, error) { 128 | return yaml.Marshal(n) 129 | } 130 | -------------------------------------------------------------------------------- /api/models/pipeline_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type PipelinesListV1Alpha []PplListElemV1Alpha 4 | 5 | type PplListElemV1Alpha struct { 6 | Id string `json:"ppl_id"` 7 | Name string `json:"name"` 8 | State string `json:"state"` 9 | CreatedAt struct { 10 | Seconds int64 `json:"seconds"` 11 | } `json:"created_at"` 12 | Label string `json:"branch_name"` 13 | } 14 | -------------------------------------------------------------------------------- /api/models/pipeline_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | yaml "gopkg.in/yaml.v2" 7 | ) 8 | 9 | type PipelineV1Alpha struct { 10 | Pipeline struct { 11 | ID string `json:"ppl_id"` 12 | Name string `json:"name,omitempty"` 13 | State string `json:"state,omitempty"` 14 | Result string `json:"result,omitempty" yaml:"result,omitempty"` 15 | Reason string `json:"result_reason,omitempty" yaml:"result_reason,omitempty"` 16 | Error string `json:"error_description,omitempty" yaml:"error_description,omitempty"` 17 | } `json:"pipeline,omitempty"` 18 | Blocks []PipelineV1AlphaBlock `json:"blocks,omitempty"` 19 | } 20 | 21 | type PipelineV1AlphaBlock struct { 22 | Name string `json:"name"` 23 | State string `json:"state"` 24 | Result string `json:"result,omitempty" yaml:"result,omitempty"` 25 | Reason string `json:"result_reason,omitempty" yaml:"result_reason,omitempty"` 26 | Error string `json:"error_description,omitempty" yaml:"error_description,omitempty"` 27 | Jobs []PipelineV1AlphaBlockJobs `json:"jobs"` 28 | } 29 | 30 | type PipelineV1AlphaBlockJobs struct { 31 | Name string `json:"name"` 32 | JobID string `json:"job_id"` 33 | } 34 | 35 | func NewPipelineV1AlphaFromJson(data []byte) (*PipelineV1Alpha, error) { 36 | j := PipelineV1Alpha{} 37 | 38 | err := json.Unmarshal(data, &j) 39 | 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return &j, nil 45 | } 46 | 47 | func (j *PipelineV1Alpha) ToYaml() ([]byte, error) { 48 | return yaml.Marshal(j) 49 | } 50 | 51 | func (j *PipelineV1Alpha) IsDone() bool { 52 | return j.Pipeline.State == "done" 53 | } 54 | -------------------------------------------------------------------------------- /api/models/project_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type ProjectListV1Alpha struct { 6 | Projects []ProjectV1Alpha `json:"projects" yaml:"projects"` 7 | } 8 | 9 | func NewProjectListV1AlphaFromJson(data []byte) (*ProjectListV1Alpha, error) { 10 | list := []ProjectV1Alpha{} 11 | 12 | err := json.Unmarshal(data, &list) 13 | 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | for _, p := range list { 19 | if p.ApiVersion == "" { 20 | p.ApiVersion = "v1alpha" 21 | } 22 | 23 | if p.Kind == "" { 24 | p.Kind = "Project" 25 | } 26 | } 27 | 28 | return &ProjectListV1Alpha{Projects: list}, nil 29 | } 30 | -------------------------------------------------------------------------------- /api/models/project_secret_list_v1.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type ProjectSecretListV1 struct { 6 | Secrets []ProjectSecretV1 `json:"secrets" yaml:"secrets"` 7 | } 8 | 9 | func NewProjectSecretListV1FromJson(data []byte) (*ProjectSecretListV1, error) { 10 | list := ProjectSecretListV1{} 11 | 12 | err := json.Unmarshal(data, &list) 13 | 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | for _, s := range list.Secrets { 19 | if s.ApiVersion == "" { 20 | s.ApiVersion = "v1" 21 | } 22 | 23 | if s.Kind == "" { 24 | s.Kind = "Secret" 25 | } 26 | } 27 | 28 | return &list, nil 29 | } 30 | -------------------------------------------------------------------------------- /api/models/project_secret_v1.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type ProjectSecretV1 struct { 11 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 12 | Kind string `json:"kind,omitempty" yaml:"kind"` 13 | 14 | Metadata ProjectSecretV1Metadata `json:"metadata" yaml:"metadata"` 15 | Data ProjectSecretV1Data `json:"data" yaml:"data"` 16 | } 17 | 18 | type ProjectSecretV1EnvVar struct { 19 | Name string `json:"name" yaml:"name"` 20 | Value string `json:"value" yaml:"value,omitempty"` 21 | } 22 | 23 | type ProjectSecretV1File struct { 24 | Path string `json:"path" yaml:"path"` 25 | Content string `json:"content" yaml:"content,omitempty"` 26 | } 27 | 28 | type ProjectSecretV1Data struct { 29 | EnvVars []ProjectSecretV1EnvVar `json:"env_vars" yaml:"env_vars"` 30 | Files []ProjectSecretV1File `json:"files" yaml:"files"` 31 | } 32 | 33 | type ProjectSecretV1Metadata struct { 34 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 35 | Id string `json:"id,omitempty" yaml:"id,omitempty"` 36 | CreateTime json.Number `json:"create_time,omitempty,string" yaml:"create_time,omitempty"` 37 | UpdateTime json.Number `json:"update_time,omitempty,string" yaml:"update_time,omitempty"` 38 | ProjectIdOrName string `json:"project_id_or_name,omitempty" yaml:"project_id_or_name,omitempty"` 39 | ContentIncluded bool `json:"content_included,omitempty" yaml:"content_included"` 40 | } 41 | 42 | func NewProjectSecretV1(name string, envVars []ProjectSecretV1EnvVar, files []ProjectSecretV1File) ProjectSecretV1 { 43 | s := ProjectSecretV1{} 44 | 45 | s.setApiVersionAndKind() 46 | s.Metadata.Name = name 47 | s.Data.EnvVars = envVars 48 | s.Data.Files = files 49 | 50 | return s 51 | } 52 | 53 | func NewProjectSecretV1FromJson(data []byte) (*ProjectSecretV1, error) { 54 | s := ProjectSecretV1{} 55 | 56 | err := json.Unmarshal(data, &s) 57 | 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | s.setApiVersionAndKind() 63 | 64 | return &s, nil 65 | } 66 | 67 | func NewProjectSecretV1FromYaml(data []byte) (*ProjectSecretV1, error) { 68 | s := ProjectSecretV1{} 69 | 70 | err := yaml.UnmarshalStrict(data, &s) 71 | 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | s.setApiVersionAndKind() 77 | 78 | return &s, nil 79 | } 80 | 81 | func (s *ProjectSecretV1) setApiVersionAndKind() { 82 | s.ApiVersion = "v1" 83 | s.Kind = "ProjectSecret" 84 | } 85 | 86 | func (s *ProjectSecretV1) Editable() bool { 87 | return s.Metadata.ContentIncluded 88 | } 89 | 90 | func (s *ProjectSecretV1) ObjectName() string { 91 | return fmt.Sprintf("Project/%s/Secrets/%s", s.Metadata.ProjectIdOrName, s.Metadata.Name) 92 | } 93 | 94 | func (s *ProjectSecretV1) ToJson() ([]byte, error) { 95 | return json.Marshal(s) 96 | } 97 | 98 | func (s *ProjectSecretV1) ToYaml() ([]byte, error) { 99 | return yaml.Marshal(s) 100 | } 101 | -------------------------------------------------------------------------------- /api/models/project_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type Scheduler struct { 11 | Name string `json:"name"` 12 | Id string `json:"id,omitempty"` 13 | Branch string `json:"branch"` 14 | At string `json:"at"` 15 | PipelineFile string `json:"pipeline_file" yaml:"pipeline_file"` 16 | Status string `json:"status,omitempty" yaml:"status,omitempty"` 17 | } 18 | 19 | type Task struct { 20 | Name string `json:"name"` 21 | Description string `json:"description,omitempty"` 22 | Scheduled bool `json:"scheduled"` 23 | Id string `json:"id,omitempty"` 24 | Branch string `json:"branch,omitempty"` 25 | At string `json:"at,omitempty"` 26 | PipelineFile string `json:"pipeline_file" yaml:"pipeline_file,omitempty"` 27 | Status string `json:"status,omitempty" yaml:"status,omitempty"` 28 | Parameters []TaskParameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` 29 | } 30 | 31 | type TaskParameter struct { 32 | Name string `json:"name"` 33 | Required bool `json:"required"` 34 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 35 | DefaultValue string `json:"default_value,omitempty" yaml:"default_value,omitempty"` 36 | Options []string `json:"options,omitempty" yaml:"options,omitempty"` 37 | } 38 | 39 | type ForkedPullRequests struct { 40 | AllowedSecrets []string `json:"allowed_secrets,omitempty" yaml:"allowed_secrets,omitempty"` 41 | AllowedContributors []string `json:"allowed_contributors,omitempty" yaml:"allowed_contributors,omitempty"` 42 | } 43 | 44 | type Status struct { 45 | PipelineFiles []PipelineFile `json:"pipeline_files" yaml:"pipeline_files"` 46 | } 47 | 48 | type PipelineFile struct { 49 | Path string `json:"path"` 50 | Level string `json:"level"` 51 | } 52 | 53 | type Whitelist struct { 54 | Branches []string `json:"branches,omitempty"` 55 | Tags []string `json:"tags,omitempty"` 56 | } 57 | 58 | type ProjectV1Alpha struct { 59 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 60 | Kind string `json:"kind,omitempty" yaml:"kind"` 61 | Metadata struct { 62 | Name string `json:"name,omitempty"` 63 | Id string `json:"id,omitempty"` 64 | Description string `json:"description,omitempty"` 65 | } `json:"metadata,omitempty"` 66 | 67 | Spec struct { 68 | Visibility string `json:"visibility,omitempty" yaml:"visibility,omitempty"` 69 | Repository struct { 70 | Url string `json:"url,omitempty"` 71 | RunOn []string `json:"run_on,omitempty" yaml:"run_on"` 72 | ForkedPullRequests ForkedPullRequests `json:"forked_pull_requests,omitempty" yaml:"forked_pull_requests,omitempty"` 73 | PipelineFile string `json:"pipeline_file" yaml:"pipeline_file"` 74 | Status *Status `json:"status,omitempty" yaml:"status"` 75 | Whitelist Whitelist `json:"whitelist" yaml:"whitelist"` 76 | IntegrationType string `json:"integration_type" yaml:"integration_type"` 77 | } `json:"repository,omitempty"` 78 | Schedulers []Scheduler `json:"schedulers,omitempty" yaml:"schedulers,omitempty"` 79 | Tasks []Task `json:"tasks,omitempty" yaml:"tasks,omitempty"` 80 | CustomPermissions *bool `json:"custom_permissions,omitempty" yaml:"custom_permissions,omitempty"` 81 | DebugPermissions []string `json:"debug_permissions,omitempty" yaml:"debug_permissions,omitempty"` 82 | AttachPermissions []string `json:"attach_permissions,omitempty" yaml:"attach_permissions,omitempty"` 83 | } `json:"spec,omitempty"` 84 | } 85 | 86 | func NewProjectV1Alpha(name string) ProjectV1Alpha { 87 | p := ProjectV1Alpha{} 88 | 89 | p.Metadata.Name = name 90 | p.setApiVersionAndKind() 91 | 92 | return p 93 | } 94 | 95 | func NewProjectV1AlphaFromJson(data []byte) (*ProjectV1Alpha, error) { 96 | p := ProjectV1Alpha{} 97 | 98 | err := json.Unmarshal(data, &p) 99 | 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | p.setApiVersionAndKind() 105 | 106 | return &p, nil 107 | } 108 | 109 | func NewProjectV1AlphaFromYaml(data []byte) (*ProjectV1Alpha, error) { 110 | p := ProjectV1Alpha{} 111 | 112 | err := yaml.UnmarshalStrict(data, &p) 113 | 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | p.setApiVersionAndKind() 119 | 120 | return &p, nil 121 | } 122 | 123 | func (p *ProjectV1Alpha) setApiVersionAndKind() { 124 | p.ApiVersion = "v1alpha" 125 | p.Kind = "Project" 126 | } 127 | 128 | func (p *ProjectV1Alpha) ObjectName() string { 129 | return fmt.Sprintf("Projects/%s", p.Metadata.Name) 130 | } 131 | 132 | func (p *ProjectV1Alpha) ToJson() ([]byte, error) { 133 | return json.Marshal(p) 134 | } 135 | 136 | func (p *ProjectV1Alpha) ToYaml() ([]byte, error) { 137 | return yaml.Marshal(p) 138 | } 139 | -------------------------------------------------------------------------------- /api/models/secret_list_v1_beta.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type SecretListV1Beta struct { 6 | Secrets []SecretV1Beta `json:"secrets" yaml:"secrets"` 7 | } 8 | 9 | func NewSecretListV1BetaFromJson(data []byte) (*SecretListV1Beta, error) { 10 | list := SecretListV1Beta{} 11 | 12 | err := json.Unmarshal(data, &list) 13 | 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | for _, s := range list.Secrets { 19 | if s.ApiVersion == "" { 20 | s.ApiVersion = "v1beta" 21 | } 22 | 23 | if s.Kind == "" { 24 | s.Kind = "Secret" 25 | } 26 | } 27 | 28 | return &list, nil 29 | } 30 | -------------------------------------------------------------------------------- /api/models/secret_v1_beta.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type SecretV1Beta struct { 11 | ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` 12 | Kind string `json:"kind,omitempty" yaml:"kind"` 13 | 14 | Metadata SecretV1BetaMetadata `json:"metadata" yaml:"metadata"` 15 | Data SecretV1BetaData `json:"data" yaml:"data"` 16 | OrgConfig *SecretV1BetaOrgConfig `json:"org_config,omitempty" yaml:"org_config,omitempty"` 17 | } 18 | 19 | type SecretV1BetaEnvVar struct { 20 | Name string `json:"name" yaml:"name"` 21 | Value string `json:"value" yaml:"value,omitempty"` 22 | } 23 | 24 | type SecretV1BetaFile struct { 25 | Path string `json:"path" yaml:"path"` 26 | Content string `json:"content" yaml:"content,omitempty"` 27 | } 28 | 29 | type SecretV1BetaData struct { 30 | EnvVars []SecretV1BetaEnvVar `json:"env_vars" yaml:"env_vars"` 31 | Files []SecretV1BetaFile `json:"files" yaml:"files"` 32 | } 33 | 34 | type SecretV1BetaMetadata struct { 35 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 36 | Id string `json:"id,omitempty" yaml:"id,omitempty"` 37 | CreateTime json.Number `json:"create_time,omitempty,string" yaml:"create_time,omitempty"` 38 | UpdateTime json.Number `json:"update_time,omitempty,string" yaml:"update_time,omitempty"` 39 | ContentIncluded bool `json:"content_included,omitempty" yaml:"content_included"` 40 | } 41 | 42 | type SecretV1BetaOrgConfig struct { 43 | Projects_access string `json:"projects_access,omitempty" yaml:"projects_access,omitempty"` 44 | Project_ids []string `json:"project_ids,omitempty" yaml:"project_ids,omitempty"` 45 | Debug_access string `json:"debug_access,omitempty" yaml:"debug_access,omitempty"` 46 | Attach_access string `json:"attach_access,omitempty" yaml:"attach_access,omitempty"` 47 | } 48 | 49 | func NewSecretV1Beta(name string, envVars []SecretV1BetaEnvVar, files []SecretV1BetaFile) SecretV1Beta { 50 | s := SecretV1Beta{} 51 | 52 | s.setApiVersionAndKind() 53 | s.Metadata.Name = name 54 | s.Data.EnvVars = envVars 55 | s.Data.Files = files 56 | 57 | return s 58 | } 59 | 60 | func NewSecretV1BetaFromJson(data []byte) (*SecretV1Beta, error) { 61 | s := SecretV1Beta{} 62 | 63 | err := json.Unmarshal(data, &s) 64 | 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | s.setApiVersionAndKind() 70 | 71 | return &s, nil 72 | } 73 | 74 | func NewSecretV1BetaFromYaml(data []byte) (*SecretV1Beta, error) { 75 | s := SecretV1Beta{} 76 | 77 | err := yaml.UnmarshalStrict(data, &s) 78 | 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | s.setApiVersionAndKind() 84 | 85 | return &s, nil 86 | } 87 | 88 | func (s *SecretV1Beta) setApiVersionAndKind() { 89 | s.ApiVersion = "v1beta" 90 | s.Kind = "Secret" 91 | } 92 | 93 | func (s *SecretV1Beta) ObjectName() string { 94 | return fmt.Sprintf("Secrets/%s", s.Metadata.Name) 95 | } 96 | 97 | func (s *SecretV1Beta) Editable() bool { 98 | return s.Metadata.ContentIncluded 99 | } 100 | 101 | func (s *SecretV1Beta) ToJson() ([]byte, error) { 102 | return json.Marshal(s) 103 | } 104 | 105 | func (s *SecretV1Beta) ToYaml() ([]byte, error) { 106 | return yaml.Marshal(s) 107 | } 108 | -------------------------------------------------------------------------------- /api/models/troubleshoot_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | yaml "gopkg.in/yaml.v2" 7 | ) 8 | 9 | type TroubleshootV1Alpha struct { 10 | Workflow map[string]interface{} `json:"workflow,omitempty" yaml:"workflow,omitempty"` 11 | Project map[string]interface{} `json:"project,omitempty" yaml:"project,omitempty"` 12 | Pipeline map[string]interface{} `json:"pipeline,omitempty" yaml:"pipeline,omitempty"` 13 | Job map[string]interface{} `json:"job,omitempty" yaml:"job,omitempty"` 14 | Block map[string]interface{} `json:"block,omitempty" yaml:"block,omitempty"` 15 | } 16 | 17 | func NewTroubleshootV1AlphaFromJson(data []byte) (*TroubleshootV1Alpha, error) { 18 | t := TroubleshootV1Alpha{} 19 | err := json.Unmarshal(data, &t) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return &t, nil 25 | } 26 | 27 | func (j *TroubleshootV1Alpha) ToJson() ([]byte, error) { 28 | return json.Marshal(j) 29 | } 30 | 31 | func (j *TroubleshootV1Alpha) ToYaml() ([]byte, error) { 32 | return yaml.Marshal(j) 33 | } 34 | -------------------------------------------------------------------------------- /api/models/workflow_list_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type WorkflowListV1Alpha struct { 6 | Workflow []WorkflowV1Alpha `json:"workflows" yaml:"projects"` 7 | } 8 | 9 | func NewWorkflowListV1AlphaFromJson(data []byte) (*WorkflowListV1Alpha, error) { 10 | list := []WorkflowV1Alpha{} 11 | 12 | err := json.Unmarshal(data, &list) 13 | 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | return &WorkflowListV1Alpha{Workflow: list}, nil 19 | } 20 | -------------------------------------------------------------------------------- /api/models/workflow_v1_alpha.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | type WorkflowV1Alpha struct { 6 | Id string `json:"wf_id,omitempty" yaml:"id,omitempty"` 7 | InitialPplId string `json:"initial_ppl_id,omitempty" yaml:"initial_ppl_id,omitempty"` 8 | BranchName string `json:"branch_name,omitempty" yaml:"branch_name,omitempty"` 9 | CreatedAt struct { 10 | Seconds int64 `json:"seconds"` 11 | } `json:"created_at"` 12 | } 13 | 14 | type WorkflowSnapshotResponseV1Alpha struct { 15 | WfID string `json:"wf_id,omitempty" yaml:"id,omitempty"` 16 | PplID string `json:"ppl_id,omitempty" yaml:"initial_ppl_id,omitempty"` 17 | } 18 | 19 | func NewWorkflowSnapshotResponseV1AlphaFromJson(data []byte) (*WorkflowSnapshotResponseV1Alpha, error) { 20 | j := WorkflowSnapshotResponseV1Alpha{} 21 | 22 | err := json.Unmarshal(data, &j) 23 | 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return &j, nil 29 | } 30 | -------------------------------------------------------------------------------- /api/uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import "github.com/google/uuid" 4 | 5 | type generator func() (uuid.UUID, error) 6 | 7 | var googleGenerator = uuid.NewUUID 8 | var googleGeneratorV4 = uuid.NewRandom 9 | var currentGenerator generator 10 | var currentGeneratorV4 generator 11 | 12 | func NewUUID() (uuid.UUID, error) { 13 | return currentGenerator() 14 | } 15 | 16 | func NewUUIDv4() (uuid.UUID, error) { 17 | return currentGeneratorV4() 18 | } 19 | 20 | func mockGenerator() (uuid.UUID, error) { 21 | return [16]byte{0, 2, 4, 6, 9, 11, 78, 16, 147, 21, 24, 26, 28, 30, 32, 34}, nil 22 | } 23 | 24 | func IsValid(s string) bool { 25 | _, err := uuid.Parse(s) 26 | return err == nil 27 | } 28 | 29 | func Mock() { 30 | currentGenerator = mockGenerator 31 | currentGeneratorV4 = mockGenerator 32 | } 33 | 34 | func Unmock() { 35 | currentGenerator = googleGenerator 36 | currentGeneratorV4 = googleGeneratorV4 37 | } 38 | 39 | func init() { 40 | currentGenerator = googleGenerator 41 | currentGeneratorV4 = googleGeneratorV4 42 | } 43 | -------------------------------------------------------------------------------- /cmd/apply.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | 8 | client "github.com/semaphoreci/cli/api/client" 9 | models "github.com/semaphoreci/cli/api/models" 10 | "github.com/semaphoreci/cli/cmd/utils" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var applyCmd = &cobra.Command{ 15 | Use: "apply", 16 | Short: "Updates resource based on file.", 17 | Long: ``, 18 | 19 | Run: func(cmd *cobra.Command, args []string) { 20 | RunApply(cmd, args) 21 | }, 22 | } 23 | 24 | func init() { 25 | RootCmd.AddCommand(applyCmd) 26 | 27 | desc := "Filename, directory, or URL to files to use to update the resource" 28 | applyCmd.Flags().StringP("file", "f", "", desc) 29 | } 30 | 31 | func RunApply(cmd *cobra.Command, args []string) { 32 | path, err := cmd.Flags().GetString("file") 33 | 34 | utils.CheckWithMessage(err, "Path not provided") 35 | 36 | // #nosec 37 | data, err := ioutil.ReadFile(path) 38 | 39 | utils.CheckWithMessage(err, "Failed to read from resource file.") 40 | 41 | _, kind, err := utils.ParseYamlResourceHeaders(data) 42 | 43 | utils.Check(err) 44 | 45 | switch kind { 46 | case "Secret": 47 | secret, err := models.NewSecretV1BetaFromYaml(data) 48 | 49 | utils.Check(err) 50 | 51 | c := client.NewSecretV1BetaApi() 52 | 53 | if secret.Editable() { 54 | secret, err = c.UpdateSecret(secret) 55 | } else { 56 | cmd.Println(secretAskConfirmationMessage) 57 | err = utils.Ask(cmd.InOrStdin(), secret.Metadata.Name) 58 | if err == nil { 59 | secret, err = c.FallbackUpdate(secret) 60 | } 61 | } 62 | 63 | utils.Check(err) 64 | 65 | fmt.Printf("Secret '%s' updated.\n", secret.Metadata.Name) 66 | case "ProjectSecret": 67 | secret, err := models.NewProjectSecretV1FromYaml(data) 68 | 69 | utils.Check(err) 70 | 71 | c := client.NewProjectSecretV1Api(secret.Metadata.ProjectIdOrName) 72 | 73 | if secret.Editable() { 74 | secret, err = c.UpdateSecret(secret) 75 | } else { 76 | cmd.Println(secretAskConfirmationMessage) 77 | err = utils.Ask(cmd.InOrStdin(), secret.Metadata.Name) 78 | if err == nil { 79 | secret, err = c.FallbackUpdate(secret) 80 | } 81 | } 82 | 83 | utils.Check(err) 84 | 85 | fmt.Printf("Secret '%s' created in project '%s'.\n", secret.Metadata.Name, secret.Metadata.ProjectIdOrName) 86 | case "Dashboard": 87 | dash, err := models.NewDashboardV1AlphaFromYaml(data) 88 | 89 | utils.Check(err) 90 | 91 | c := client.NewDashboardV1AlphaApi() 92 | 93 | dash, err = c.UpdateDashboard(dash) 94 | 95 | utils.Check(err) 96 | 97 | fmt.Printf("Dashboard '%s' updated.\n", dash.Metadata.Name) 98 | case "Notification": 99 | notif, err := models.NewNotificationV1AlphaFromYaml(data) 100 | 101 | utils.Check(err) 102 | 103 | c := client.NewNotificationsV1AlphaApi() 104 | 105 | notif, err = c.UpdateNotification(notif) 106 | 107 | utils.Check(err) 108 | 109 | fmt.Printf("Notification '%s' updated.\n", notif.Metadata.Name) 110 | case "Project": 111 | proj, err := models.NewProjectV1AlphaFromYaml(data) 112 | 113 | utils.Check(err) 114 | 115 | c := client.NewProjectV1AlphaApi() 116 | 117 | proj, err = c.UpdateProject(proj) 118 | 119 | utils.Check(err) 120 | 121 | fmt.Printf("Project '%s' updated.\n", proj.Metadata.Name) 122 | case models.KindSelfHostedAgentType: 123 | at, err := models.NewAgentTypeV1AlphaFromYaml(data) 124 | utils.Check(err) 125 | 126 | c := client.NewAgentTypeApiV1AlphaApi() 127 | newAgentType, err := c.UpdateAgentType(at) 128 | utils.Check(err) 129 | 130 | fmt.Printf("%s '%s' updated.\n", models.KindSelfHostedAgentType, newAgentType.Metadata.Name) 131 | case models.DeploymentTargetKindV1Alpha: 132 | target, err := models.NewDeploymentTargetV1AlphaFromYaml(data) 133 | utils.Check(err) 134 | if target == nil { 135 | utils.Check(errors.New("deployment target in the file is empty")) 136 | return 137 | } 138 | updateRequest := &models.DeploymentTargetUpdateRequestV1Alpha{ 139 | DeploymentTargetV1Alpha: *target, 140 | } 141 | utils.Check(updateRequest.LoadFiles()) 142 | 143 | c := client.NewDeploymentTargetsV1AlphaApi() 144 | updatedDeploymentTarget, err := c.Update(updateRequest) 145 | utils.Check(err) 146 | 147 | fmt.Printf("Deployment target '%s' ('%s') updated.\n", updatedDeploymentTarget.Id, updatedDeploymentTarget.Name) 148 | default: 149 | utils.Fail(fmt.Sprintf("Unsuported resource kind '%s'", kind)) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /cmd/attach.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | client "github.com/semaphoreci/cli/api/client" 8 | "github.com/semaphoreci/cli/cmd/ssh" 9 | "github.com/semaphoreci/cli/cmd/utils" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var attachCmd = &cobra.Command{ 14 | Use: "attach [JOB ID]", 15 | Short: "Attach to a running job.", 16 | Long: ``, 17 | Args: cobra.ExactArgs(1), 18 | 19 | Run: func(cmd *cobra.Command, args []string) { 20 | id := args[0] 21 | 22 | c := client.NewJobsV1AlphaApi() 23 | job, err := c.GetJob(id) 24 | 25 | utils.Check(err) 26 | 27 | if job.Status.State == "FINISHED" { 28 | fmt.Printf("Job %s has already finished.\n", job.Metadata.Id) 29 | os.Exit(1) 30 | } 31 | 32 | if job.Status.State != "RUNNING" { 33 | fmt.Printf("Job %s has not yet started.\n", job.Metadata.Id) 34 | os.Exit(1) 35 | } 36 | 37 | /* 38 | * If we want to attach to a machine where a self-hosted job is running, no SSH key will be available. 39 | * We just give the agent name back to the user and return. 40 | */ 41 | if job.IsSelfHosted() { 42 | fmt.Printf("* Job '%s' is running in the self-hosted agent named '%s'.\n", id, job.AgentName()) 43 | fmt.Printf("* Once you access the machine where that agent is running, make sure you are logged in as the same user the Semaphore agent is using.\n") 44 | fmt.Printf("* You can source the '/tmp/.env-*' file where the agent keeps all the environment variables exposed to the job.\n") 45 | return 46 | } 47 | 48 | /* 49 | * If this is for a cloud job, we go for the SSH key. 50 | */ 51 | sshKey, err := c.GetJobDebugSSHKey(job.Metadata.Id) 52 | utils.Check(err) 53 | 54 | conn, err := ssh.NewConnectionForJob(job, sshKey.Key) 55 | utils.Check(err) 56 | defer conn.Close() 57 | 58 | err = conn.Session() 59 | utils.Check(err) 60 | }, 61 | } 62 | 63 | func init() { 64 | RootCmd.AddCommand(attachCmd) 65 | } 66 | -------------------------------------------------------------------------------- /cmd/config.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/semaphoreci/cli/config" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var configCmd = &cobra.Command{ 12 | Use: "config", 13 | Short: "Get and set configuration options.", 14 | Long: ``, 15 | } 16 | 17 | var configGetCmd = &cobra.Command{ 18 | Use: "get [NAME]", 19 | Short: "Display a configuration options", 20 | Long: ``, 21 | Args: cobra.ExactArgs(1), 22 | Run: func(cmd *cobra.Command, args []string) { 23 | name := args[0] 24 | 25 | if config.IsSet(name) { 26 | value := config.Get(name) 27 | 28 | fmt.Println(value) 29 | } else { 30 | fmt.Printf("configuration \"%s\" not found\n", name) 31 | 32 | os.Exit(1) 33 | } 34 | }, 35 | } 36 | 37 | var configSetCmd = &cobra.Command{ 38 | Use: "set [NAME] [VALUE]", 39 | Short: "Set a configuration option", 40 | Long: ``, 41 | Args: cobra.ExactArgs(2), 42 | Run: func(cmd *cobra.Command, args []string) { 43 | name := args[0] 44 | value := args[1] 45 | 46 | config.Set(name, value) 47 | }, 48 | } 49 | 50 | func init() { 51 | RootCmd.AddCommand(configCmd) 52 | 53 | configCmd.AddCommand(configGetCmd) 54 | configCmd.AddCommand(configSetCmd) 55 | } 56 | -------------------------------------------------------------------------------- /cmd/connect.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | "github.com/semaphoreci/cli/config" 9 | "github.com/spf13/cobra" 10 | 11 | client "github.com/semaphoreci/cli/api/client" 12 | ) 13 | 14 | var connectCmd = &cobra.Command{ 15 | Use: "connect", 16 | Short: "Connect to a Semaphore endpoint", 17 | Args: cobra.ExactArgs(2), 18 | Long: ``, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | host := args[0] 21 | token := args[1] 22 | 23 | baseClient := client.NewBaseClient(token, host, "") 24 | client := client.NewProjectV1AlphaApiWithCustomClient(baseClient) 25 | 26 | _, err := client.ListProjects() 27 | 28 | if err != nil { 29 | fmt.Fprintf(cmd.OutOrStderr(), "%s", err) 30 | utils.Exit(1) 31 | } 32 | 33 | name := strings.Replace(host, ".", "_", -1) 34 | 35 | config.SetActiveContext(name) 36 | config.SetAuth(token) 37 | config.SetHost(host) 38 | 39 | cmd.Printf("connected to %s\n", host) 40 | }, 41 | } 42 | 43 | func init() { 44 | RootCmd.AddCommand(connectCmd) 45 | } 46 | -------------------------------------------------------------------------------- /cmd/connect_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "testing" 7 | 8 | httpmock "github.com/jarcoal/httpmock" 9 | assert "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test__Connect__Response200(t *testing.T) { 13 | httpmock.Activate() 14 | defer httpmock.DeactivateAndReset() 15 | 16 | var outputBuffer bytes.Buffer 17 | 18 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/projects", 19 | func(req *http.Request) (*http.Response, error) { 20 | return httpmock.NewStringResponse(200, "[]"), nil 21 | }, 22 | ) 23 | 24 | RootCmd.SetArgs([]string{"connect", "org.semaphoretext.xyz", "abc"}) 25 | RootCmd.SetOutput(&outputBuffer) 26 | RootCmd.Execute() 27 | 28 | assert.Equal(t, outputBuffer.String(), "connected to org.semaphoretext.xyz\n") 29 | } 30 | 31 | func Test__Connect__Response401(t *testing.T) { 32 | httpmock.Activate() 33 | defer httpmock.DeactivateAndReset() 34 | 35 | var outputBuffer bytes.Buffer 36 | 37 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/projects", 38 | func(req *http.Request) (*http.Response, error) { 39 | return httpmock.NewStringResponse(401, "Unauthorized"), nil 40 | }, 41 | ) 42 | 43 | RootCmd.SetArgs([]string{"connect", "org.semaphoretext.xyz", "abc"}) 44 | RootCmd.SetOutput(&outputBuffer) 45 | 46 | // handle exit 47 | assert.Panics(t, func() { 48 | RootCmd.Execute() 49 | }, "exit 1") 50 | 51 | assert.Equal( 52 | t, 53 | outputBuffer.String(), 54 | "http status 401 with message \"Unauthorized\" received from upstream") 55 | } 56 | -------------------------------------------------------------------------------- /cmd/context.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/semaphoreci/cli/cmd/utils" 7 | "github.com/semaphoreci/cli/config" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // contextCmd represents the context command 12 | var contextCmd = &cobra.Command{ 13 | Use: "context", 14 | Short: "Manage contexts for connecting to Semaphore", 15 | Long: ``, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | switch len(args) { 18 | case 0: 19 | listContexts() 20 | case 1: 21 | setContext(args[0]) 22 | default: 23 | panic("") 24 | } 25 | }, 26 | } 27 | 28 | func init() { 29 | RootCmd.AddCommand(contextCmd) 30 | } 31 | 32 | func listContexts() { 33 | contexts, err := config.ContextList() 34 | 35 | utils.Check(err) 36 | 37 | active := config.GetActiveContext() 38 | 39 | for _, ctx := range contexts { 40 | if ctx == active { 41 | fmt.Print("* ") 42 | fmt.Println(ctx) 43 | } else { 44 | fmt.Print(" ") 45 | fmt.Println(ctx) 46 | } 47 | } 48 | } 49 | 50 | func setContext(name string) { 51 | contexts, err := config.ContextList() 52 | 53 | utils.Check(err) 54 | 55 | for _, ctx := range contexts { 56 | if ctx == name { 57 | config.SetActiveContext(name) 58 | 59 | fmt.Printf("switched to context \"%s\"\n", name) 60 | return 61 | } 62 | } 63 | 64 | utils.Fail(fmt.Sprintf("context \"%s\" does not exists", name)) 65 | } 66 | -------------------------------------------------------------------------------- /cmd/create_job.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | models "github.com/semaphoreci/cli/api/models" 8 | "github.com/semaphoreci/cli/cmd/utils" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | type CreateJobCmd struct { 13 | Cmd *cobra.Command 14 | } 15 | 16 | const jobCreateCommandNotSpecifiedMsg = `Command not specified 17 | 18 | Job can't be created without a command. Example: 19 | 20 | $ sem create job hello-world --project hello-world --command 'echo "Hello World"' 21 | ` 22 | 23 | const jobCreateProjectNotSpecifiedMsg = `Project not specified 24 | 25 | Example: 26 | 27 | $ sem create job hello-world --project hello-world --command 'echo "Hello World"' 28 | ` 29 | 30 | func NewCreateJobCmd() *CreateJobCmd { 31 | c := &CreateJobCmd{} 32 | 33 | c.Cmd = &cobra.Command{ 34 | Use: "job [NAME]", 35 | Short: "Create a job.", 36 | Long: ``, 37 | Aliases: []string{"jobs"}, 38 | Args: cobra.ExactArgs(1), 39 | Run: func(cmd *cobra.Command, args []string) { 40 | c.Run(args) 41 | }, 42 | } 43 | 44 | desc := "File mapping :" 45 | c.Cmd.Flags().StringArrayP("file", "f", []string{}, desc) 46 | 47 | // desc = "Specify environment variable NAME=VALUE" 48 | // CreateJobCmd.Flags().StringArrayP("e", "env", []string{}, desc) 49 | 50 | desc = "Command to execute in the job" 51 | c.Cmd.Flags().StringP("command", "c", "", desc) 52 | 53 | desc = "Project name" 54 | c.Cmd.Flags().StringP("project", "p", "", desc) 55 | 56 | return c 57 | } 58 | 59 | func (c *CreateJobCmd) Run(args []string) { 60 | name := args[0] 61 | 62 | job := models.NewJobV1Alpha(name) 63 | job.Spec = &models.JobV1AlphaSpec{ 64 | ProjectId: c.FindProjectId(), 65 | Files: c.ParseFileFlags(), 66 | Secrets: []models.JobV1AlphaSpecSecret{}, 67 | Commands: c.ParseCommandFlag(), 68 | } 69 | 70 | jobClient := client.NewJobsV1AlphaApi() 71 | job, err := jobClient.CreateJob(job) 72 | 73 | utils.Check(err) 74 | 75 | fmt.Printf("Job '%s' created.\n", job.Metadata.Id) 76 | } 77 | 78 | func (c *CreateJobCmd) FindProjectId() string { 79 | projectName, err := c.Cmd.Flags().GetString("project") 80 | utils.Check(err) 81 | 82 | if projectName == "" { 83 | utils.Fail(jobCreateProjectNotSpecifiedMsg) 84 | } 85 | 86 | pc := client.NewProjectV1AlphaApi() 87 | project, err := pc.GetProject(projectName) 88 | 89 | utils.Check(err) 90 | 91 | return project.Metadata.Id 92 | } 93 | 94 | func (c *CreateJobCmd) ParseCommandFlag() []string { 95 | command, err := c.Cmd.Flags().GetString("command") 96 | utils.Check(err) 97 | 98 | if command == "" { 99 | utils.Fail(jobCreateCommandNotSpecifiedMsg) 100 | } 101 | 102 | return []string{command} 103 | } 104 | 105 | func (c *CreateJobCmd) ParseFileFlags() []models.JobV1AlphaSpecFile { 106 | fileFlags, err := c.Cmd.Flags().GetStringArray("file") 107 | utils.Check(err) 108 | 109 | var files []models.JobV1AlphaSpecFile 110 | 111 | for _, fileFlag := range fileFlags { 112 | remotePath, content, err := utils.ParseFileFlag(fileFlag) 113 | 114 | utils.Check(err) 115 | 116 | files = append(files, models.JobV1AlphaSpecFile{ 117 | Path: remotePath, 118 | Content: content, 119 | }) 120 | } 121 | 122 | return files 123 | } 124 | -------------------------------------------------------------------------------- /cmd/create_notification.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | client "github.com/semaphoreci/cli/api/client" 8 | models "github.com/semaphoreci/cli/api/models" 9 | utils "github.com/semaphoreci/cli/cmd/utils" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCreateNotificationCmd() *cobra.Command { 15 | cmd := &cobra.Command{} 16 | 17 | cmd.Use = "notification [NAME]" 18 | cmd.Short = "Create a notification." 19 | cmd.Long = `` 20 | cmd.Aliases = []string{"notifications", "notifs", "notif"} 21 | cmd.Args = cobra.ExactArgs(1) 22 | cmd.Run = RunCreateNotification 23 | 24 | cmd.Flags().String("projects", "", "Filter for project names") 25 | cmd.Flags().String("pipelines", "", "Filter for pipeline file") 26 | cmd.Flags().String("branches", "", "Filter for branch names") 27 | 28 | cmd.Flags().String("slack-channels", "", "Slack channels where to send notifications") 29 | cmd.Flags().String("slack-endpoint", "", "Slack webhook endpoint") 30 | 31 | cmd.Flags().String("webhook-endpoint", "", "Webhook endpoint") 32 | cmd.Flags().String("webhook-secret", "", "Webhook secret") 33 | 34 | return cmd 35 | } 36 | 37 | const errNotificationWithoutProject = `Specify at least one project that sends notifications. 38 | 39 | Example: 40 | 41 | sem create notification my-notif --projects "cli,webapp" 42 | ` 43 | 44 | const errNotificationWithoutSlackChannels = `Specify at least one slack channel where to send notifications. 45 | 46 | Example: 47 | 48 | sem create notification my-notif --slack-channels "#general,#devops" 49 | ` 50 | 51 | const errNotificationWithoutEndpoint = `Specify the slack and/or webhook endpoint where to send notificaitons. 52 | 53 | Example: 54 | 55 | sem create notification my-notif \ 56 | --slack-endpoint "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX" \ 57 | --webhook-endpoint "https://example.com/postrequest" 58 | ` 59 | 60 | func RunCreateNotification(cmd *cobra.Command, args []string) { 61 | name := args[0] 62 | 63 | projects, err := utils.CSVFlag(cmd, "projects") 64 | utils.Check(err) 65 | 66 | branches, err := utils.CSVFlag(cmd, "branches") 67 | utils.Check(err) 68 | 69 | pipelines, err := utils.CSVFlag(cmd, "pipelines") 70 | utils.Check(err) 71 | 72 | slackChannels, err := utils.CSVFlag(cmd, "slack-channels") 73 | utils.Check(err) 74 | 75 | slackEndpoint, err := cmd.Flags().GetString("slack-endpoint") 76 | utils.Check(err) 77 | 78 | webhookEndpoint, err := cmd.Flags().GetString("webhook-endpoint") 79 | utils.Check(err) 80 | 81 | webhookSecret, err := cmd.Flags().GetString("webhook-secret") 82 | utils.Check(err) 83 | 84 | if len(projects) == 0 { 85 | utils.Fail(errNotificationWithoutProject) 86 | } 87 | 88 | if slackEndpoint == "" && webhookEndpoint == "" { 89 | utils.Fail(errNotificationWithoutEndpoint) 90 | } 91 | 92 | filter := models.NotificationV1AlphaSpecRuleFilter{} 93 | filter.Projects = projects 94 | filter.Branches = branches 95 | filter.Pipelines = pipelines 96 | 97 | notify := models.NotificationV1AlphaSpecRuleNotify{} 98 | notify.Slack.Channels = slackChannels 99 | notify.Slack.Endpoint = slackEndpoint 100 | notify.Webhook.Endpoint = webhookEndpoint 101 | notify.Webhook.Secret = webhookSecret 102 | 103 | ruleName := fmt.Sprintf( 104 | "Send notifications for %s", strings.Join(projects, ", ")) 105 | 106 | rule := models.NotificationV1AlphaSpecRule{ 107 | Name: ruleName, 108 | Filter: filter, 109 | Notify: notify, 110 | } 111 | 112 | notif := models.NewNotificationV1Alpha(name) 113 | notif.Spec.Rules = append(notif.Spec.Rules, rule) 114 | 115 | notifApi := client.NewNotificationsV1AlphaApi() 116 | notif, err = notifApi.CreateNotification(notif) 117 | 118 | utils.Check(err) 119 | 120 | fmt.Printf("Notification '%s' created.\n", notif.Metadata.Name) 121 | } 122 | -------------------------------------------------------------------------------- /cmd/create_notification_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "testing" 7 | 8 | httpmock "github.com/jarcoal/httpmock" 9 | models "github.com/semaphoreci/cli/api/models" 10 | 11 | assert "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func Test__CreateNotification(t *testing.T) { 15 | httpmock.Activate() 16 | defer httpmock.DeactivateAndReset() 17 | 18 | var received *models.NotificationV1Alpha 19 | 20 | endpoint := "https://org.semaphoretext.xyz/api/v1alpha/notifications" 21 | 22 | httpmock.RegisterResponder("POST", endpoint, 23 | func(req *http.Request) (*http.Response, error) { 24 | body, _ := ioutil.ReadAll(req.Body) 25 | 26 | received, _ = models.NewNotificationV1AlphaFromJson(body) 27 | 28 | return httpmock.NewStringResponse(200, "{}"), nil 29 | }, 30 | ) 31 | 32 | RootCmd.SetArgs([]string{ 33 | "create", 34 | "notification", 35 | "aaa", 36 | "--projects", "cli, test1", 37 | "--branches", "master,staging", 38 | "--pipelines", ".semaphore/semaphore.yml", 39 | "--slack-channels", "#product", 40 | "--slack-endpoint", "https://dasdasdasd/sa/das/da/sdas", 41 | "--webhook-endpoint", "https://dasdasdasd/sa", 42 | "--webhook-secret", "aaa-webhook-secret", 43 | }) 44 | 45 | RootCmd.Execute() 46 | 47 | assert.Equal(t, received.Metadata.Name, "aaa") 48 | 49 | assert.Equal(t, received.Spec.Rules[0].Filter.Projects, []string{ 50 | "cli", 51 | "test1", 52 | }) 53 | 54 | assert.Equal(t, received.Spec.Rules[0].Filter.Branches, []string{ 55 | "master", 56 | "staging", 57 | }) 58 | 59 | assert.Equal(t, received.Spec.Rules[0].Filter.Pipelines, []string{ 60 | ".semaphore/semaphore.yml", 61 | }) 62 | 63 | assert.Equal(t, received.Spec.Rules[0].Notify.Slack.Channels, []string{ 64 | "#product", 65 | }) 66 | 67 | assert.Equal(t, received.Spec.Rules[0].Notify.Slack.Endpoint, 68 | "https://dasdasdasd/sa/das/da/sdas") 69 | 70 | assert.Equal(t, received.Spec.Rules[0].Notify.Webhook.Endpoint, 71 | "https://dasdasdasd/sa") 72 | 73 | assert.Equal(t, received.Spec.Rules[0].Notify.Webhook.Secret, 74 | "aaa-webhook-secret") 75 | } 76 | -------------------------------------------------------------------------------- /cmd/create_secret.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | client "github.com/semaphoreci/cli/api/client" 9 | models "github.com/semaphoreci/cli/api/models" 10 | "github.com/semaphoreci/cli/cmd/utils" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCreateSecretCmd() *cobra.Command { 15 | cmd := &cobra.Command{} 16 | 17 | cmd.Use = "secret [NAME]" 18 | cmd.Short = "Create a secret. If project is specified (via -p or -i), create a project secret." 19 | cmd.Long = `` 20 | cmd.Aliases = []string{"secrets"} 21 | cmd.Args = cobra.ExactArgs(1) 22 | 23 | cmd.Flags().StringArrayP( 24 | "file", 25 | "f", 26 | []string{}, 27 | "File mapping :, used to create a secret with file", 28 | ) 29 | 30 | cmd.Flags().StringArrayP( 31 | "env", 32 | "e", 33 | []string{}, 34 | "Environment Variables", 35 | ) 36 | 37 | cmd.Flags().StringP("project-name", "p", "", 38 | "project name; if specified will edit project level secret, otherwise organization secret") 39 | cmd.Flags().StringP("project-id", "i", "", 40 | "project id; if specified will edit project level secret, otherwise organization secret") 41 | cmd.MarkFlagsMutuallyExclusive("project-name", "project-id") 42 | 43 | cmd.Run = func(cmd *cobra.Command, args []string) { 44 | projectID := GetProjectID(cmd) 45 | 46 | name := args[0] 47 | 48 | fileFlags, err := cmd.Flags().GetStringArray("file") 49 | utils.Check(err) 50 | envFlags, err := cmd.Flags().GetStringArray("env") 51 | utils.Check(err) 52 | 53 | if projectID == "" { 54 | files := parseSecretFiles(fileFlags) 55 | envVars := parseSecretEnvVars(envFlags) 56 | 57 | secret := models.NewSecretV1Beta(name, envVars, files) 58 | 59 | c := client.NewSecretV1BetaApi() 60 | 61 | _, err = c.CreateSecret(&secret) 62 | 63 | utils.Check(err) 64 | 65 | fmt.Printf("Secret '%s' created.\n", secret.Metadata.Name) 66 | } else { 67 | files := parseProjectSecretFiles(fileFlags) 68 | envVars := parseProjectSecretEnvVars(envFlags) 69 | 70 | secret := models.NewProjectSecretV1(name, envVars, files) 71 | 72 | c := client.NewProjectSecretV1Api(projectID) 73 | 74 | _, err = c.CreateSecret(&secret) 75 | 76 | utils.Check(err) 77 | 78 | fmt.Printf("Project secret '%s' created.\n", secret.Metadata.Name) 79 | } 80 | } 81 | 82 | return cmd 83 | } 84 | 85 | func parseProjectSecretFiles(fileFlags []string) []models.ProjectSecretV1File { 86 | var files []models.ProjectSecretV1File 87 | 88 | for _, fileFlag := range fileFlags { 89 | p, c := parseFile(fileFlag) 90 | 91 | var file models.ProjectSecretV1File 92 | file.Path = p 93 | file.Content = encodeFromFileAt(c) 94 | files = append(files, file) 95 | } 96 | return files 97 | } 98 | 99 | func parseSecretFiles(fileFlags []string) []models.SecretV1BetaFile { 100 | var files []models.SecretV1BetaFile 101 | 102 | for _, fileFlag := range fileFlags { 103 | p, c := parseFile(fileFlag) 104 | 105 | var file models.SecretV1BetaFile 106 | file.Path = p 107 | file.Content = encodeFromFileAt(c) 108 | files = append(files, file) 109 | } 110 | return files 111 | } 112 | 113 | func parseFile(fileFlag string) (path, content string) { 114 | matchFormat, err := regexp.MatchString(`^[^: ]+:[^: ]+$`, fileFlag) 115 | utils.Check(err) 116 | 117 | if !matchFormat { 118 | utils.Fail("The format of --file flag must be: :") 119 | } 120 | 121 | flagPaths := strings.Split(fileFlag, ":") 122 | 123 | return flagPaths[1], flagPaths[0] 124 | } 125 | 126 | func parseProjectSecretEnvVars(envFlags []string) []models.ProjectSecretV1EnvVar { 127 | var envVars []models.ProjectSecretV1EnvVar 128 | 129 | for _, envFlag := range envFlags { 130 | n, v := parseEnvVar(envFlag) 131 | 132 | var envVar models.ProjectSecretV1EnvVar 133 | envVar.Name = n 134 | envVar.Value = v 135 | envVars = append(envVars, envVar) 136 | } 137 | 138 | return envVars 139 | } 140 | 141 | func parseSecretEnvVars(envFlags []string) []models.SecretV1BetaEnvVar { 142 | var envVars []models.SecretV1BetaEnvVar 143 | 144 | for _, envFlag := range envFlags { 145 | n, v := parseEnvVar(envFlag) 146 | 147 | var envVar models.SecretV1BetaEnvVar 148 | envVar.Name = n 149 | envVar.Value = v 150 | envVars = append(envVars, envVar) 151 | } 152 | 153 | return envVars 154 | } 155 | 156 | func parseEnvVar(envFlag string) (name, value string) { 157 | matchFormat, err := regexp.MatchString(`^.+=.+$`, envFlag) 158 | utils.Check(err) 159 | 160 | if !matchFormat { 161 | utils.Fail("The format of -e flag must be: =") 162 | } 163 | 164 | parts := strings.SplitN(envFlag, "=", 2) 165 | 166 | return parts[0], parts[1] 167 | } -------------------------------------------------------------------------------- /cmd/create_secret_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "testing" 9 | 10 | httpmock "github.com/jarcoal/httpmock" 11 | assert "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func Test__CreateSecret__WithSubcommand__Response200(t *testing.T) { 15 | httpmock.Activate() 16 | defer httpmock.DeactivateAndReset() 17 | 18 | content1 := "This is some docker config" 19 | content2 := "This is some gcloud config" 20 | 21 | ioutil.WriteFile("/tmp/docker", []byte(content1), 0644) 22 | ioutil.WriteFile("/tmp/gcloud", []byte(content2), 0644) 23 | 24 | received := "" 25 | 26 | httpmock.RegisterResponder("POST", "https://org.semaphoretext.xyz/api/v1beta/secrets", 27 | func(req *http.Request) (*http.Response, error) { 28 | body, _ := ioutil.ReadAll(req.Body) 29 | 30 | received = string(body) 31 | 32 | return httpmock.NewStringResponse(200, received), nil 33 | }, 34 | ) 35 | 36 | RootCmd.SetArgs([]string{ 37 | "create", 38 | "secret", 39 | "-e", "FOO=BAR", 40 | "-e", "ZEZ=Hello World", 41 | "--file", "/tmp/docker:.config/docker", 42 | "--file", "/tmp/gcloud:.config/gcloud", 43 | "abc", 44 | }) 45 | 46 | RootCmd.Execute() 47 | 48 | file1 := base64.StdEncoding.EncodeToString([]byte(content1)) 49 | file2 := base64.StdEncoding.EncodeToString([]byte(content2)) 50 | 51 | expected := fmt.Sprintf(`{"apiVersion":"v1beta","kind":"Secret","metadata":{"name":"abc"},"data":{"env_vars":[{"name":"FOO","value":"BAR"},{"name":"ZEZ","value":"Hello World"}],"files":[{"path":".config/docker","content":"%s"},{"path":".config/gcloud","content":"%s"}]}}`, file1, file2) 52 | 53 | assert.Equal(t, received, expected) 54 | } 55 | 56 | func Test__CreateSecret__WithProjectID__Response200(t *testing.T) { 57 | httpmock.Activate() 58 | defer httpmock.DeactivateAndReset() 59 | 60 | received := "" 61 | 62 | dash := `{ 63 | "metadata":{ 64 | "name":"hello", 65 | "id":"bb2ba294-d4b3-48bc-90a7-12dd56e9424b", 66 | } 67 | }` 68 | 69 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/projects/hello", 70 | func(req *http.Request) (*http.Response, error) { 71 | fmt.Println("GET /api/v1alpha/projects/hello") 72 | return httpmock.NewStringResponse(200, dash), nil 73 | }, 74 | ) 75 | 76 | httpmock.RegisterResponder("POST", "https://org.semaphoretext.xyz/api/v1/projects/hello/secrets", 77 | func(req *http.Request) (*http.Response, error) { 78 | body, _ := ioutil.ReadAll(req.Body) 79 | 80 | received = string(body) 81 | 82 | return httpmock.NewStringResponse(200, received), nil 83 | }, 84 | ) 85 | 86 | content1 := "This is some docker config" 87 | content2 := "This is some gcloud config" 88 | 89 | ioutil.WriteFile("/tmp/docker", []byte(content1), 0644) 90 | ioutil.WriteFile("/tmp/gcloud", []byte(content2), 0644) 91 | 92 | // flags for env vars and projects stay the same as in the previous test 93 | 94 | RootCmd.SetArgs([]string{ 95 | "create", 96 | "secret", 97 | "-i", "hello", 98 | "projectABC", 99 | }) 100 | 101 | 102 | RootCmd.Execute() 103 | 104 | 105 | file1 := base64.StdEncoding.EncodeToString([]byte(content1)) 106 | file2 := base64.StdEncoding.EncodeToString([]byte(content2)) // We do not expect project_id_or_name to be received in the body of the request, as it's set by the grpc-gateway from the URL 107 | expected := fmt.Sprintf(`{"apiVersion":"v1","kind":"ProjectSecret","metadata":{"name":"projectABC"},"data":{"env_vars":[{"name":"FOO","value":"BAR"},{"name":"ZEZ","value":"Hello World"}],"files":[{"path":".config/docker","content":"%s"},{"path":".config/gcloud","content":"%s"}]}}`, file1, file2) 108 | 109 | assert.Equal(t, expected, received) 110 | } 111 | -------------------------------------------------------------------------------- /cmd/create_target_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "testing" 9 | 10 | httpmock "github.com/jarcoal/httpmock" 11 | "github.com/semaphoreci/cli/api/uuid" 12 | assert "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func Test__CreateDeploymentTarget__WithSubcommand__Response200(t *testing.T) { 16 | httpmock.Activate() 17 | defer httpmock.DeactivateAndReset() 18 | uuid.Mock() 19 | defer uuid.Unmock() 20 | 21 | content1 := "This is some docker config" 22 | content2 := "This is some gcloud config" 23 | 24 | ioutil.WriteFile("/tmp/docker", []byte(content1), 0644) 25 | ioutil.WriteFile("/tmp/gcloud", []byte(content2), 0644) 26 | 27 | received := "" 28 | 29 | httpmock.RegisterResponder("POST", "https://org.semaphoretext.xyz/api/v1alpha/deployment_targets", 30 | func(req *http.Request) (*http.Response, error) { 31 | body, _ := ioutil.ReadAll(req.Body) 32 | 33 | received = string(body) 34 | 35 | return httpmock.NewStringResponse(200, received), nil 36 | }, 37 | ) 38 | 39 | RootCmd.SetArgs([]string{ 40 | "create", 41 | "dt", 42 | "-i", "00000000-0000-0000-000000000000", 43 | "-e", "FOO=BAR", 44 | "--env", "ZEZ=Hello World", 45 | "--file", "/tmp/docker:.config/docker", 46 | "-f", "/tmp/gcloud:.config/gcloud", 47 | "-s", "any", 48 | "--subject-rule", "user,mock_user_321", 49 | "-s", "role,contributor", 50 | "--object-rule", "branch,exact,main", 51 | "-o", `tag,regex,.*feat.*`, 52 | "-b", "book 1", 53 | "--url", "mock_url_321.zyx", 54 | "abc", 55 | }) 56 | 57 | RootCmd.Execute() 58 | 59 | file1 := base64.StdEncoding.EncodeToString([]byte(content1)) 60 | file2 := base64.StdEncoding.EncodeToString([]byte(content2)) 61 | 62 | expected := fmt.Sprintf(`{"id":"","name":"abc","project_id":"00000000-0000-0000-000000000000","organization_id":"","description":"","url":"mock_url_321.zyx","state":"","state_message":"","subject_rules":[{"type":"ANY"},{"type":"USER","git_login":"mock_user_321"},{"type":"ROLE","subject_id":"contributor"}],"object_rules":[{"type":"BRANCH","match_mode":"EXACT","pattern":"main"},{"type":"TAG","match_mode":"REGEX","pattern":".*feat.*"}],"active":false,"bookmark_parameter1":"book 1","bookmark_parameter2":"","bookmark_parameter3":"","env_vars":[{"name":"FOO","value":"BAR"},{"name":"ZEZ","value":"Hello World"}],"files":[{"path":".config/docker","content":"%s"},{"path":".config/gcloud","content":"%s"}],"unique_token":"00020406-090b-4e10-9315-181a1c1e2022"}`, file1, file2) 63 | 64 | assert.Equal(t, received, expected) 65 | } 66 | -------------------------------------------------------------------------------- /cmd/debug.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var debugCmd = &cobra.Command{ 8 | Use: "debug", 9 | Short: "Debug a resource.", 10 | Long: ``, 11 | } 12 | 13 | func init() { 14 | debugCmd.AddCommand(NewDebugProjectCmd()) 15 | debugCmd.AddCommand(NewDebugJobCmd()) 16 | 17 | RootCmd.AddCommand(debugCmd) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/debug_job.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | models "github.com/semaphoreci/cli/api/models" 8 | 9 | "github.com/semaphoreci/cli/cmd/ssh" 10 | "github.com/semaphoreci/cli/cmd/utils" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewDebugJobCmd() *cobra.Command { 15 | var DebugJobCmd = &cobra.Command{ 16 | Use: "job [ID]", 17 | Short: "Debug a job", 18 | Long: ``, 19 | Aliases: []string{"job", "jobs"}, 20 | Args: cobra.ExactArgs(1), 21 | Run: RunDebugJobCmd, 22 | } 23 | 24 | DebugJobCmd.Flags().Duration( 25 | "duration", 26 | 60*time.Minute, 27 | "duration of the debug session in seconds") 28 | 29 | return DebugJobCmd 30 | } 31 | 32 | func RunDebugJobCmd(cmd *cobra.Command, args []string) { 33 | duration, err := cmd.Flags().GetDuration("duration") 34 | utils.Check(err) 35 | 36 | jobId := args[0] 37 | 38 | debug := models.NewDebugJobV1Alpha(jobId, int(duration.Seconds())) 39 | 40 | fmt.Printf("* Creating debug session for job '%s'\n", jobId) 41 | fmt.Printf("* Setting duration to %d minutes\n", int(duration.Minutes())) 42 | 43 | sshIntroMessage := ` 44 | Semaphore CI Debug Session. 45 | 46 | - Checkout your code with ` + "`checkout`" + ` 47 | - Run your CI commands with ` + "`source ~/commands.sh`" + ` 48 | - Leave the session with ` + "`exit`" + ` 49 | 50 | Documentation: https://docs.semaphoreci.com/essentials/debugging-with-ssh-access/. 51 | ` 52 | 53 | err = ssh.StartDebugJobSession(debug, sshIntroMessage) 54 | utils.Check(err) 55 | } 56 | -------------------------------------------------------------------------------- /cmd/debug_project.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/semaphoreci/cli/api/models" 9 | 10 | "github.com/semaphoreci/cli/cmd/jobs" 11 | "github.com/semaphoreci/cli/cmd/ssh" 12 | "github.com/semaphoreci/cli/cmd/utils" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewDebugProjectCmd() *cobra.Command { 17 | var DebugProjectCmd = &cobra.Command{ 18 | Use: "project [NAME]", 19 | Short: "Debug a project", 20 | Long: ``, 21 | Aliases: []string{"prj", "projects"}, 22 | Args: cobra.ExactArgs(1), 23 | Run: RunDebugProjectCmd, 24 | } 25 | 26 | DebugProjectCmd.Flags().Duration( 27 | "duration", 28 | 60*time.Minute, 29 | "duration of the debug session in seconds") 30 | 31 | DebugProjectCmd.Flags().String( 32 | "machine-type", 33 | "e1-standard-2", 34 | "machine type to use for debugging; default: e1-standard-2") 35 | 36 | return DebugProjectCmd 37 | } 38 | 39 | func RunDebugProjectCmd(cmd *cobra.Command, args []string) { 40 | machineType, err := cmd.Flags().GetString("machine-type") 41 | utils.Check(err) 42 | 43 | if jobs.IsSelfHosted(machineType) { 44 | fmt.Printf("Self-hosted agent type '%s' can't be used to debug a project. Only cloud agent types are allowed.\n", machineType) 45 | os.Exit(1) 46 | } 47 | 48 | duration, err := cmd.Flags().GetDuration("duration") 49 | 50 | utils.Check(err) 51 | 52 | projectNameOrId := args[0] 53 | 54 | utils.Check(err) 55 | 56 | debugPrj := models.NewDebugProjectV1Alpha(projectNameOrId, int(duration.Seconds()), machineType) 57 | 58 | fmt.Printf("* Creating debug session for project '%s'\n", projectNameOrId) 59 | fmt.Printf("* Setting duration to %d minutes\n", int(duration.Minutes())) 60 | 61 | sshIntroMessage := ` 62 | Semaphore CI Debug Session. 63 | 64 | - Checkout your code with ` + "`checkout`" + ` 65 | - Leave the session with ` + "`exit`" + ` 66 | 67 | Documentation: https://docs.semaphoreci.com/essentials/debugging-with-ssh-access/. 68 | ` 69 | 70 | err = ssh.StartDebugProjectSession(debugPrj, sshIntroMessage) 71 | utils.Check(err) 72 | } 73 | -------------------------------------------------------------------------------- /cmd/delete.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | models "github.com/semaphoreci/cli/api/models" 8 | "github.com/semaphoreci/cli/cmd/deployment_targets" 9 | "github.com/semaphoreci/cli/cmd/utils" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var deleteCmd = &cobra.Command{ 14 | Use: "delete [KIND] [NAME]", 15 | Short: "Delete a resource.", 16 | Long: ``, 17 | Args: cobra.ExactArgs(2), 18 | } 19 | 20 | var DeleteDashboardCmd = &cobra.Command{ 21 | Use: "dashboard [NAME]", 22 | Short: "Delete a dashboard.", 23 | Long: ``, 24 | Aliases: []string{"dashboards", "dash"}, 25 | Args: cobra.ExactArgs(1), 26 | 27 | Run: func(cmd *cobra.Command, args []string) { 28 | name := args[0] 29 | 30 | c := client.NewDashboardV1AlphaApi() 31 | 32 | err := c.DeleteDashboard(name) 33 | 34 | utils.Check(err) 35 | 36 | fmt.Printf("Dashboard '%s' deleted.\n", name) 37 | }, 38 | } 39 | 40 | var DeleteSecretCmd = &cobra.Command{ 41 | Use: "secret [NAME]", 42 | Short: "Delete a secret.", 43 | Long: `Delete a organization or project secret. To delete a project secret, use the --project-name or --project-id flag.`, 44 | Aliases: []string{"secrets"}, 45 | Args: cobra.ExactArgs(1), 46 | 47 | Run: func(cmd *cobra.Command, args []string) { 48 | projectID := GetProjectID(cmd) 49 | 50 | if projectID == "" { 51 | name := args[0] 52 | 53 | c := client.NewSecretV1BetaApi() 54 | 55 | err := c.DeleteSecret(name) 56 | 57 | utils.Check(err) 58 | 59 | fmt.Printf("Secret '%s' deleted.\n", name) 60 | } else { 61 | name := args[0] 62 | c := client.NewProjectSecretV1Api(projectID) 63 | 64 | err := c.DeleteSecret(name) 65 | 66 | utils.Check(err) 67 | 68 | fmt.Printf("Secret '%s' deleted.\n", name) 69 | } 70 | }, 71 | } 72 | 73 | var DeleteAgentTypeCmd = &cobra.Command{ 74 | Use: "agent_type [NAME]", 75 | Short: "Delete a self-hosted agent type.", 76 | Long: ``, 77 | Aliases: []string{"agenttype", "agentType"}, 78 | Args: cobra.ExactArgs(1), 79 | 80 | Run: func(cmd *cobra.Command, args []string) { 81 | name := args[0] 82 | c := client.NewAgentTypeApiV1AlphaApi() 83 | err := c.DeleteAgentType(name) 84 | utils.Check(err) 85 | 86 | fmt.Printf("Self-hosted agent type '%s' deleted.\n", name) 87 | }, 88 | } 89 | 90 | var DeleteProjectCmd = &cobra.Command{ 91 | Use: "project [NAME]", 92 | Short: "Delete a project.", 93 | Long: ``, 94 | Aliases: []string{"projects", "prj"}, 95 | Args: cobra.ExactArgs(1), 96 | 97 | Run: func(cmd *cobra.Command, args []string) { 98 | name := args[0] 99 | 100 | c := client.NewProjectV1AlphaApi() 101 | 102 | err := c.DeleteProject(name) 103 | 104 | utils.Check(err) 105 | 106 | fmt.Printf("Project '%s' deleted.\n", name) 107 | }, 108 | } 109 | 110 | var DeleteNotificationCmd = &cobra.Command{ 111 | Use: "notification [NAME]", 112 | Short: "Delete a notification.", 113 | Long: ``, 114 | Aliases: []string{"notifications", "notif", "notifs"}, 115 | Args: cobra.ExactArgs(1), 116 | 117 | Run: func(cmd *cobra.Command, args []string) { 118 | name := args[0] 119 | 120 | c := client.NewNotificationsV1AlphaApi() 121 | 122 | err := c.DeleteNotification(name) 123 | 124 | utils.Check(err) 125 | 126 | fmt.Printf("Notification '%s' deleted.\n", name) 127 | }, 128 | } 129 | 130 | var deleteTargetCmd = &cobra.Command{ 131 | Use: "deployment_target [ID]", 132 | Short: "Delete a deployment target.", 133 | Long: ``, 134 | Aliases: models.DeploymentTargetCmdAliases, 135 | Args: cobra.ExactArgs(1), 136 | 137 | Run: func(cmd *cobra.Command, args []string) { 138 | targetId := args[0] 139 | deployment_targets.Delete(targetId) 140 | }, 141 | } 142 | 143 | func init() { 144 | RootCmd.AddCommand(deleteCmd) 145 | 146 | deleteCmd.AddCommand(DeleteDashboardCmd) 147 | deleteCmd.AddCommand(DeleteProjectCmd) 148 | deleteCmd.AddCommand(DeleteNotificationCmd) 149 | deleteCmd.AddCommand(DeleteAgentTypeCmd) 150 | 151 | DeleteSecretCmd.Flags().StringP("project-name", "p", "", 152 | "project name; if specified will delete project secret, otherwise organization secret") 153 | DeleteSecretCmd.Flags().StringP("project-id", "i", "", 154 | "project id; if specified will delete project secret, otherwise organization secret") 155 | deleteCmd.AddCommand(DeleteSecretCmd) 156 | 157 | deleteCmd.AddCommand(deleteTargetCmd) 158 | } 159 | -------------------------------------------------------------------------------- /cmd/delete_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | httpmock "github.com/jarcoal/httpmock" 9 | "github.com/semaphoreci/cli/api/uuid" 10 | assert "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestDeleteProject__Response200(t *testing.T) { 14 | httpmock.Activate() 15 | defer httpmock.DeactivateAndReset() 16 | 17 | received := false 18 | 19 | httpmock.RegisterResponder("DELETE", "https://org.semaphoretext.xyz/api/v1alpha/projects/test-prj", 20 | func(req *http.Request) (*http.Response, error) { 21 | received = true 22 | 23 | return httpmock.NewStringResponse(200, ""), nil 24 | }, 25 | ) 26 | 27 | RootCmd.SetArgs([]string{"delete", "project", "test-prj"}) 28 | RootCmd.Execute() 29 | 30 | if received == false { 31 | t.Error("Expected the API to receive DELETE test-prj") 32 | } 33 | } 34 | 35 | func TestDeleteSecret__Response200(t *testing.T) { 36 | httpmock.Activate() 37 | defer httpmock.DeactivateAndReset() 38 | 39 | received := false 40 | 41 | httpmock.RegisterResponder("DELETE", "https://org.semaphoretext.xyz/api/v1beta/secrets/test-secret", 42 | func(req *http.Request) (*http.Response, error) { 43 | received = true 44 | 45 | return httpmock.NewStringResponse(200, ""), nil 46 | }, 47 | ) 48 | 49 | RootCmd.SetArgs([]string{"delete", "secret", "test-secret"}) 50 | RootCmd.Execute() 51 | 52 | if received == false { 53 | t.Error("Expected the API to receive DELETE test-prj") 54 | } 55 | } 56 | 57 | func TestDeleteDashboardCmd__Response200(t *testing.T) { 58 | httpmock.Activate() 59 | defer httpmock.DeactivateAndReset() 60 | 61 | received := false 62 | 63 | httpmock.RegisterResponder("DELETE", "https://org.semaphoretext.xyz/api/v1alpha/dashboards/test-dash", 64 | func(req *http.Request) (*http.Response, error) { 65 | received = true 66 | 67 | return httpmock.NewStringResponse(200, ""), nil 68 | }, 69 | ) 70 | 71 | RootCmd.SetArgs([]string{"delete", "dash", "test-dash"}) 72 | RootCmd.Execute() 73 | 74 | if received == false { 75 | t.Error("Expected the API to receive DELETE test dash") 76 | } 77 | } 78 | 79 | func TestDeleteAgentTypeCmd__Response200(t *testing.T) { 80 | httpmock.Activate() 81 | defer httpmock.DeactivateAndReset() 82 | 83 | received := false 84 | 85 | httpmock.RegisterResponder("DELETE", "https://org.semaphoretext.xyz/api/v1alpha/self_hosted_agent_types/s1-testing", 86 | func(req *http.Request) (*http.Response, error) { 87 | received = true 88 | 89 | return httpmock.NewStringResponse(200, ""), nil 90 | }, 91 | ) 92 | 93 | RootCmd.SetArgs([]string{"delete", "agent_type", "s1-testing"}) 94 | RootCmd.Execute() 95 | 96 | if received == false { 97 | t.Error("Expected the API to receive DELETE agent_type s1-testing") 98 | } 99 | } 100 | 101 | func Test__DeleteDeploymentTarget__Response200(t *testing.T) { 102 | httpmock.Activate() 103 | defer httpmock.DeactivateAndReset() 104 | uuid.Mock() 105 | defer uuid.Unmock() 106 | 107 | received := false 108 | 109 | targetId := "494b76aa-f3f0-4ecf-b5ef-c389591a01be" 110 | unique_token, _ := uuid.NewUUID() 111 | deleteURL := fmt.Sprintf("https://org.semaphoretext.xyz/api/v1alpha/deployment_targets/%s?unique_token=%s", targetId, unique_token) 112 | httpmock.RegisterResponder(http.MethodDelete, deleteURL, 113 | func(req *http.Request) (*http.Response, error) { 114 | received = true 115 | 116 | p := `{ 117 | "id": "494b76aa-f3f0-4ecf-b5ef-c389591a01be", 118 | "name": "dep target test", 119 | "url": "https://semaphoreci.xyz/target", 120 | "project_id": "proj_id" 121 | } 122 | ` 123 | 124 | return httpmock.NewStringResponse(200, p), nil 125 | }, 126 | ) 127 | 128 | RootCmd.SetArgs([]string{"delete", "dt", targetId}) 129 | RootCmd.Execute() 130 | 131 | assert.True(t, received, "Expected the API to receive DELETE deployment_targets/:id") 132 | } 133 | -------------------------------------------------------------------------------- /cmd/deployment_targets/delete.go: -------------------------------------------------------------------------------- 1 | package deployment_targets 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | ) 9 | 10 | func Delete(targetId string) { 11 | c := client.NewDeploymentTargetsV1AlphaApi() 12 | 13 | err := c.Delete(targetId) 14 | utils.Check(err) 15 | 16 | fmt.Printf("Deployment target '%s' deleted.\n", targetId) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/deployment_targets/get.go: -------------------------------------------------------------------------------- 1 | package deployment_targets 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "text/tabwriter" 10 | 11 | client "github.com/semaphoreci/cli/api/client" 12 | "github.com/semaphoreci/cli/api/models" 13 | "github.com/semaphoreci/cli/cmd/utils" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func Describe(targetId string) { 18 | c := client.NewDeploymentTargetsV1AlphaApi() 19 | 20 | var deploymentTarget *models.DeploymentTargetV1Alpha 21 | var err error 22 | if targetId != "" { 23 | deploymentTarget, err = c.Describe(targetId) 24 | utils.Check(err) 25 | } else { 26 | utils.Check(errors.New("target id or name must be provided")) 27 | } 28 | deploymentTargetYaml, err := deploymentTarget.ToYaml() 29 | utils.Check(err) 30 | 31 | fmt.Printf("%s\n", deploymentTargetYaml) 32 | } 33 | 34 | func DescribeByName(targetName, projectId string) { 35 | c := client.NewDeploymentTargetsV1AlphaApi() 36 | 37 | deploymentTarget, err := c.DescribeByName(targetName, projectId) 38 | utils.Check(err) 39 | 40 | deploymentTargetYaml, err := deploymentTarget.ToYaml() 41 | utils.Check(err) 42 | 43 | fmt.Printf("%s\n", deploymentTargetYaml) 44 | } 45 | 46 | func History(targetId string, cmd *cobra.Command) { 47 | historyRequest := models.HistoryRequestFiltersV1Alpha{ 48 | CursorType: models.HistoryRequestCursorTypeFirstV1Alpha, 49 | } 50 | var err error 51 | historyRequest.GitRefType, err = cmd.Flags().GetString("git-ref-type") 52 | utils.Check(err) 53 | historyRequest.GitRefType = strings.ToLower(strings.TrimSpace(historyRequest.GitRefType)) 54 | 55 | historyRequest.GitRefLabel, err = cmd.Flags().GetString("git-ref-label") 56 | utils.Check(err) 57 | historyRequest.TriggeredBy, err = cmd.Flags().GetString("triggered-by") 58 | utils.Check(err) 59 | 60 | parameters, err := cmd.Flags().GetStringArray("parameter") 61 | utils.Check(err) 62 | for i, parameter := range parameters { 63 | switch i { 64 | case 0: 65 | historyRequest.Parameter1 = parameter 66 | case 1: 67 | historyRequest.Parameter2 = parameter 68 | case 2: 69 | historyRequest.Parameter3 = parameter 70 | } 71 | } 72 | afterTimestamp, err := cmd.Flags().GetString("after") 73 | utils.Check(err) 74 | if afterTimestamp != "" { 75 | _, err = strconv.ParseInt(afterTimestamp, 10, 64) 76 | if err != nil { 77 | utils.Check(errors.New("after timestamp must be valid UNIX time in microseconds")) 78 | } 79 | historyRequest.CursorType = models.HistoryRequestCursorTypeAfterV1Alpha 80 | historyRequest.CursorValue = afterTimestamp 81 | } 82 | 83 | beforeTimestamp, err := cmd.Flags().GetString("before") 84 | utils.Check(err) 85 | if beforeTimestamp != "" { 86 | _, err = strconv.ParseInt(beforeTimestamp, 10, 64) 87 | if err != nil { 88 | utils.Check(errors.New("before timestamp must be valid UNIX time in microseconds")) 89 | } 90 | historyRequest.CursorType = models.HistoryRequestCursorTypeBeforeV1Alpha 91 | historyRequest.CursorValue = beforeTimestamp 92 | } 93 | if afterTimestamp != "" && beforeTimestamp != "" { 94 | utils.Check(errors.New("you can't use both after and before timestamps")) 95 | } 96 | c := client.NewDeploymentTargetsV1AlphaApi() 97 | 98 | deployments, err := c.History(targetId, historyRequest) 99 | utils.Check(err) 100 | 101 | deploymentsYaml, err := deployments.ToYaml() 102 | utils.Check(err) 103 | 104 | fmt.Printf("%s\n", deploymentsYaml) 105 | } 106 | 107 | func HistoryByName(targetName, projectId string, cmd *cobra.Command) { 108 | c := client.NewDeploymentTargetsV1AlphaApi() 109 | 110 | deploymentTarget, err := c.DescribeByName(targetName, projectId) 111 | utils.Check(err) 112 | 113 | History(deploymentTarget.Id, cmd) 114 | } 115 | 116 | func List(projectId string) { 117 | c := client.NewDeploymentTargetsV1AlphaApi() 118 | 119 | deploymentTargetsList, err := c.List(projectId) 120 | utils.Check(err) 121 | 122 | const padding = 3 123 | w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0) 124 | defer w.Flush() 125 | 126 | fmt.Fprintln(w, "DEPLOYMENT TARGET ID\tNAME\tCREATION TIME (UTC)\tSTATE\tSTATUS") 127 | if deploymentTargetsList == nil { 128 | return 129 | } 130 | for _, t := range *deploymentTargetsList { 131 | createdAt := "N/A" 132 | if t.CreatedAt != nil { 133 | createdAt = t.CreatedAt.Format("2006-01-02 15:04:05") 134 | } 135 | stateName := t.State 136 | status := "inactive" 137 | if t.Active { 138 | status = "active" 139 | } 140 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", t.Id, t.Name, createdAt, stateName, status) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /cmd/deployment_targets/rebuild.go: -------------------------------------------------------------------------------- 1 | package deployment_targets 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | ) 9 | 10 | func Rebuild(targetId string) { 11 | client := client.NewDeploymentTargetsV1AlphaApi() 12 | successful, err := client.Activate(targetId) 13 | utils.Check(err) 14 | if successful { 15 | fmt.Printf("Target [%s] was rebuilt successfully\n", targetId) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/deployment_targets/stop.go: -------------------------------------------------------------------------------- 1 | package deployment_targets 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | ) 9 | 10 | func Stop(targetId string) { 11 | client := client.NewDeploymentTargetsV1AlphaApi() 12 | successful, err := client.Deactivate(targetId) 13 | utils.Check(err) 14 | if successful { 15 | fmt.Printf("Target [%s] was stopped successfully\n", targetId) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/get_notification.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "text/tabwriter" 7 | 8 | client "github.com/semaphoreci/cli/api/client" 9 | models "github.com/semaphoreci/cli/api/models" 10 | 11 | "github.com/semaphoreci/cli/cmd/utils" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func NewGetNotificationCmd() *cobra.Command { 16 | cmd := &cobra.Command{} 17 | 18 | cmd.Use = "notifications [NAME]" 19 | cmd.Short = "Get notifications." 20 | cmd.Long = `` 21 | cmd.Aliases = []string{"notification", "notifs", "notif"} 22 | cmd.Args = cobra.RangeArgs(0, 1) 23 | 24 | cmd.Run = func(cmd *cobra.Command, args []string) { 25 | if len(args) == 0 { 26 | RunListNotifications(cmd, args) 27 | } else { 28 | RunGetNotification(cmd, args) 29 | } 30 | } 31 | 32 | return cmd 33 | } 34 | 35 | func RunGetNotification(cmd *cobra.Command, args []string) { 36 | name := args[0] 37 | 38 | c := client.NewNotificationsV1AlphaApi() 39 | notif, err := c.GetNotification(name) 40 | 41 | utils.Check(err) 42 | 43 | y, err := notif.ToYaml() 44 | 45 | utils.Check(err) 46 | 47 | fmt.Printf("%s", y) 48 | } 49 | 50 | func RunListNotifications(cmd *cobra.Command, args []string) { 51 | c := client.NewNotificationsV1AlphaApi() 52 | 53 | pageSize, _ := cmd.Flags().GetInt32("page-size") 54 | pageToken, _ := cmd.Flags().GetString("page-token") 55 | fetchAll := (pageSize == 0) && (pageToken == "") 56 | 57 | var allNotifications []models.NotificationV1Alpha 58 | 59 | for { 60 | notifList, err := c.ListNotifications(pageSize, pageToken) 61 | utils.Check(err) 62 | 63 | allNotifications = append(allNotifications, notifList.Notifications...) 64 | pageToken = notifList.NextPageToken 65 | 66 | if !fetchAll || notifList.NextPageToken == "" { 67 | break 68 | } 69 | } 70 | 71 | const padding = 3 72 | w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0) 73 | 74 | fmt.Fprintln(w, "NAME\tAGE") 75 | 76 | for _, n := range allNotifications { 77 | updateTime, err := n.Metadata.UpdateTime.Int64() 78 | utils.Check(err) 79 | 80 | fmt.Fprintf(w, "%s\t%s\n", n.Metadata.Name, utils.RelativeAgeForHumans(updateTime)) 81 | } 82 | if !fetchAll && pageToken != "" { 83 | fmt.Fprintf(w, "\nNext page token: %s\n", pageToken) 84 | fmt.Fprintf(w, "To view next page, run: sem get notifications --page-token %s\n", pageToken) 85 | } 86 | 87 | if err := w.Flush(); err != nil { 88 | fmt.Printf("Error flushing when pretty printing notifications: %v\n", err) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /cmd/get_notification_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | httpmock "github.com/jarcoal/httpmock" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test__ListNotifications__Response200(t *testing.T) { 13 | httpmock.Activate() 14 | defer httpmock.DeactivateAndReset() 15 | 16 | received := false 17 | 18 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications", 19 | func(req *http.Request) (*http.Response, error) { 20 | received = true 21 | 22 | n1 := `{ 23 | "metadata": { 24 | "name": "notif1", 25 | "update_time": "124" 26 | } 27 | }` 28 | 29 | n2 := `{ 30 | "metadata": { 31 | "name": "notif2", 32 | "update_time": "125" 33 | } 34 | }` 35 | 36 | notifications := fmt.Sprintf(`{"notifications":[%s,%s]}`, n1, n2) 37 | 38 | return httpmock.NewStringResponse(200, notifications), nil 39 | }, 40 | ) 41 | 42 | RootCmd.SetArgs([]string{"get", "notifications"}) 43 | RootCmd.Execute() 44 | 45 | assert.True(t, received, "Expected the API to receive GET notifications") 46 | } 47 | 48 | func Test__GetNotification__Response200(t *testing.T) { 49 | httpmock.Activate() 50 | defer httpmock.DeactivateAndReset() 51 | 52 | received := false 53 | 54 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications/test-notif", 55 | func(req *http.Request) (*http.Response, error) { 56 | received = true 57 | 58 | notification := `{ 59 | "metadata": { 60 | "name": "test-notif", 61 | "update_time": "124" 62 | } 63 | }` 64 | 65 | return httpmock.NewStringResponse(200, notification), nil 66 | }, 67 | ) 68 | 69 | RootCmd.SetArgs([]string{"get", "notifications", "test-notif"}) 70 | RootCmd.Execute() 71 | 72 | assert.True(t, received, "Expected the API to receive GET notifications/test-notif") 73 | } 74 | 75 | func Test__ListNotifications__WithAllFlag(t *testing.T) { 76 | httpmock.Activate() 77 | defer httpmock.DeactivateAndReset() 78 | 79 | page1Called := false 80 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications", 81 | func(req *http.Request) (*http.Response, error) { 82 | page1Called = true 83 | 84 | n1 := `{ 85 | "metadata": { 86 | "name": "notif1", 87 | "update_time": "124" 88 | } 89 | }` 90 | 91 | n2 := `{ 92 | "metadata": { 93 | "name": "notif2", 94 | "update_time": "125" 95 | } 96 | }` 97 | 98 | response := fmt.Sprintf(`{ 99 | "notifications": [%s,%s], 100 | "next_page_token": "page2" 101 | }`, n1, n2) 102 | 103 | return httpmock.NewStringResponse(200, response), nil 104 | }, 105 | ) 106 | 107 | page2Called := false 108 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications?page_token=page2", 109 | func(req *http.Request) (*http.Response, error) { 110 | page2Called = true 111 | 112 | n3 := `{ 113 | "metadata": { 114 | "name": "notif3", 115 | "update_time": "126" 116 | } 117 | }` 118 | 119 | response := fmt.Sprintf(`{ 120 | "notifications": [%s], 121 | "next_page_token": "" 122 | }`, n3) 123 | 124 | return httpmock.NewStringResponse(200, response), nil 125 | }, 126 | ) 127 | 128 | RootCmd.SetArgs([]string{"get", "notifications"}) 129 | RootCmd.Execute() 130 | 131 | assert.True(t, page1Called, "Expected the API to receive GET notifications for first page") 132 | assert.True(t, page2Called, "Expected the API to receive GET notifications for second page") 133 | } 134 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "regexp" 9 | 10 | "github.com/semaphoreci/cli/cmd/utils" 11 | "github.com/semaphoreci/cli/config" 12 | "github.com/semaphoreci/cli/generators" 13 | "github.com/spf13/cobra" 14 | 15 | client "github.com/semaphoreci/cli/api/client" 16 | models "github.com/semaphoreci/cli/api/models" 17 | gitconfig "github.com/tcnksm/go-gitconfig" 18 | ) 19 | 20 | const ( 21 | GithubIntegrationOAuthToken = "github_token" 22 | GithubIntegrationApp = "github_app" 23 | ) 24 | 25 | var flagProjectName string 26 | var flagRepoUrl string 27 | var flagGithubIntegration string 28 | 29 | func InitCmd() cobra.Command { 30 | cmd := cobra.Command{ 31 | Use: "init", 32 | Short: "Initialize a project", 33 | Long: ``, 34 | 35 | Run: func(cmd *cobra.Command, args []string) { 36 | RunInit(cmd, args) 37 | }, 38 | } 39 | 40 | cmd.Flags().StringVar(&flagRepoUrl, "repo-url", "", "explicitly set the repository url, if not set it is extracted from local git repository") 41 | cmd.Flags().StringVar(&flagProjectName, "project-name", "", "explicitly set the project name, if not set it is extracted from the repo-url") 42 | 43 | cmd.Flags().StringVar( 44 | &flagGithubIntegration, 45 | "github-integration", 46 | GithubIntegrationOAuthToken, 47 | fmt.Sprintf("github integration for the project. Possible values are: %s", validIntegrationTypes()), 48 | ) 49 | 50 | return cmd 51 | } 52 | 53 | func init() { 54 | cmd := InitCmd() 55 | 56 | RootCmd.AddCommand(&cmd) 57 | } 58 | 59 | func RunInit(cmd *cobra.Command, args []string) { 60 | var err error 61 | var name string 62 | var repoUrl string 63 | 64 | if flagRepoUrl != "" { 65 | repoUrl = flagRepoUrl 66 | } else { 67 | repoUrl, err = getGitOriginUrl() 68 | 69 | utils.Check(err) 70 | } 71 | 72 | if flagProjectName != "" { 73 | name = flagProjectName 74 | } else { 75 | name, err = ConstructProjectName(repoUrl) 76 | 77 | utils.Check(err) 78 | } 79 | 80 | if flagGithubIntegration != GithubIntegrationOAuthToken && flagGithubIntegration != GithubIntegrationApp { 81 | utils.Fail(fmt.Sprintf( 82 | "Invalid GitHub integration '%s' for project. Possible values are %s", 83 | flagGithubIntegration, 84 | validIntegrationTypes(), 85 | )) 86 | } 87 | 88 | c := client.NewProjectV1AlphaApi() 89 | projectModel := models.NewProjectV1Alpha(name) 90 | projectModel.Spec.Repository.Url = repoUrl 91 | projectModel.Spec.Repository.RunOn = []string{"branches", "tags"} 92 | projectModel.Spec.Repository.IntegrationType = flagGithubIntegration 93 | 94 | project, err := c.CreateProject(&projectModel) 95 | 96 | utils.Check(err) 97 | 98 | if generators.PipelineFileExists() { 99 | fmt.Printf("[info] skipping .semaphore/semaphore.yml generation. It is already present in the repository.\n\n") 100 | } else { 101 | err = generators.GeneratePipelineYaml() 102 | 103 | utils.Check(err) 104 | } 105 | 106 | fmt.Printf("Project is created. You can find it at https://%s/projects/%s.\n", config.GetHost(), project.Metadata.Name) 107 | fmt.Println("") 108 | fmt.Printf("To run your first pipeline execute:\n") 109 | fmt.Println("") 110 | fmt.Printf(" git add .semaphore/semaphore.yml && git commit -m \"First pipeline\" && git push\n") 111 | fmt.Println("") 112 | } 113 | 114 | func ConstructProjectName(repoUrl string) (string, error) { 115 | formats := []*regexp.Regexp{ 116 | regexp.MustCompile(`.+[:|\/].*\/([^.]*)`), 117 | } 118 | 119 | for _, r := range formats { 120 | match := r.FindStringSubmatch(repoUrl) 121 | 122 | if len(match) >= 2 { 123 | return match[1], nil 124 | } 125 | } 126 | 127 | errTemplate := "unsupported git remote format '%s'.\n" 128 | errTemplate += "\n" 129 | errTemplate += "Format must be one of the following:\n" 130 | errTemplate += " - git@HOST:/.git\n" 131 | errTemplate += " - git@HOST:/\n" 132 | errTemplate += " - https://HOST//\n" 133 | errTemplate += " - https://HOST//.git\n" 134 | errTemplate += "\n" 135 | errTemplate += "To add a project with an alternative git url, use the --repo-url flag:\n" 136 | errTemplate += " - sem init --repo-url git@github.com:/.git\n" 137 | 138 | return "", errors.New(fmt.Sprintf(errTemplate, repoUrl)) 139 | } 140 | 141 | func getGitOriginUrl() (string, error) { 142 | if flag.Lookup("test.v") == nil { 143 | if _, err := os.Stat(".git"); os.IsNotExist(err) { 144 | return "", errors.New("not a git repository") 145 | } 146 | 147 | return gitconfig.OriginURL() 148 | } else { 149 | return "git@github.com:/renderedtext/something.git", nil 150 | } 151 | } 152 | 153 | func validIntegrationTypes() string { 154 | return fmt.Sprintf("\"%s\" (OAuth token), \"%s\"", GithubIntegrationOAuthToken, GithubIntegrationApp) 155 | } 156 | -------------------------------------------------------------------------------- /cmd/jobs/states.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import "strings" 4 | 5 | func IsSelfHosted(machineType string) bool { 6 | return strings.HasPrefix(machineType, "s1-") 7 | } 8 | -------------------------------------------------------------------------------- /cmd/logs.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var logsCmd = &cobra.Command{ 12 | Use: "logs [JOB ID]", 13 | Short: "Display logs generated by a job.", 14 | Long: ``, 15 | Args: cobra.ExactArgs(1), 16 | 17 | Run: func(cmd *cobra.Command, args []string) { 18 | id := args[0] 19 | 20 | c := client.NewLogsV1AlphaApi() 21 | logs, err := c.Get(id) 22 | utils.Check(err) 23 | 24 | for _, e := range logs.Events { 25 | if e.Type == "cmd_output" { 26 | fmt.Println(e.Output) 27 | } 28 | 29 | if e.Type == "cmd_started" { 30 | fmt.Printf("\n\x1b[33m✻ %s\x1b[0m\n", e.Directive) 31 | } 32 | 33 | if e.Type == "cmd_finished" { 34 | fmt.Printf("\x1b[33mexit status: %d\x1b[0m\n", e.ExitCode) 35 | } 36 | 37 | if e.Type == "job_finished" { 38 | if e.JobResult == "passed" { 39 | fmt.Printf("\n\n\x1b[32mJob %s.\x1b[0m\n", e.JobResult) 40 | } 41 | 42 | if e.JobResult == "failed" { 43 | fmt.Printf("\n\n\x1b[31mJob %s.\x1b[0m\n", e.JobResult) 44 | } 45 | } 46 | } 47 | }, 48 | } 49 | 50 | func init() { 51 | RootCmd.AddCommand(logsCmd) 52 | } 53 | -------------------------------------------------------------------------------- /cmd/logs_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | httpmock "github.com/jarcoal/httpmock" 8 | ) 9 | 10 | const Events = `{ 11 | "events": [ 12 | {"event": "job_started", "timestamp": 1624541916}, 13 | {"event": "cmd_started", "timestamp": 1624541916, "directive": "Exporting environment variables"}, 14 | {"event": "cmd_output", "timestamp": 1624541916, "output": "Exporting VAR1"}, 15 | {"event": "cmd_output", "timestamp": 1624541916, "output": "Exporting VAR2"}, 16 | {"event": "cmd_finished", "timestamp": 1624541916, "directive": "Exporting environment variables", "exit_code": 0}, 17 | {"event": "job_finished", "timestamp": 1624541916, "result": "passed"} 18 | ] 19 | } 20 | ` 21 | 22 | func TestLogs__Response200(t *testing.T) { 23 | httpmock.Activate() 24 | defer httpmock.DeactivateAndReset() 25 | 26 | received := false 27 | 28 | httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/logs/job-123", 29 | func(req *http.Request) (*http.Response, error) { 30 | received = true 31 | 32 | return httpmock.NewStringResponse(200, Events), nil 33 | }, 34 | ) 35 | 36 | RootCmd.SetArgs([]string{"logs", "job-123", "-v"}) 37 | RootCmd.Execute() 38 | 39 | if received == false { 40 | t.Error("Expected the API to receive GET /logs/job-123") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cmd/pipelines/get.go: -------------------------------------------------------------------------------- 1 | package pipelines 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "text/tabwriter" 8 | "time" 9 | 10 | client "github.com/semaphoreci/cli/api/client" 11 | "github.com/semaphoreci/cli/api/models" 12 | "github.com/semaphoreci/cli/cmd/utils" 13 | ) 14 | 15 | func Describe(id string, follow bool) { 16 | c := client.NewPipelinesV1AlphaApi() 17 | 18 | for { 19 | ppl, isDone := describe(c, id) 20 | 21 | fmt.Printf("%s\n", ppl) 22 | 23 | if follow == false || isDone { 24 | return 25 | } 26 | 27 | time.Sleep(3 * time.Second) 28 | } 29 | } 30 | 31 | func describe(c client.PipelinesApiV1AlphaApi, id string) ([]byte, bool) { 32 | pplJ, err := c.DescribePpl(id) 33 | utils.Check(err) 34 | pplY, err := pplJ.ToYaml() 35 | utils.Check(err) 36 | 37 | return pplY, pplJ.IsDone() 38 | } 39 | 40 | func List(projectID string, options client.ListOptions) { 41 | fmt.Printf("%s\n", projectID) 42 | c := client.NewPipelinesV1AlphaApi() 43 | body, err := c.ListPplWithOptions(projectID, options) 44 | utils.Check(err) 45 | 46 | prettyPrintPipelineList(body) 47 | } 48 | 49 | func prettyPrintPipelineList(jsonList []byte) { 50 | j := models.PipelinesListV1Alpha{} 51 | err := json.Unmarshal(jsonList, &j) 52 | utils.Check(err) 53 | 54 | const padding = 3 55 | w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0) 56 | 57 | fmt.Fprintln(w, "PIPELINE ID\tPIPELINE NAME\tCREATION TIME\tSTATE\tLABEL") 58 | 59 | for _, p := range j { 60 | createdAt := time.Unix(p.CreatedAt.Seconds, 0).Format("2006-01-02 15:04:05") 61 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", p.Id, p.Name, createdAt, p.State, p.Label) 62 | } 63 | 64 | if err := w.Flush(); err != nil { 65 | fmt.Printf("Error flushing when pretty printing pipelines: %v\n", err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cmd/pipelines/rebuild.go: -------------------------------------------------------------------------------- 1 | package pipelines 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | ) 9 | 10 | func Rebuild(id string) { 11 | c := client.NewPipelinesV1AlphaApi() 12 | body, err := c.PartialRebuildPpl(id) 13 | utils.Check(err) 14 | fmt.Printf("%s\n", string(body)) 15 | } 16 | -------------------------------------------------------------------------------- /cmd/pipelines/stop.go: -------------------------------------------------------------------------------- 1 | package pipelines 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | ) 9 | 10 | func Stop(id string) { 11 | c := client.NewPipelinesV1AlphaApi() 12 | body, err := c.StopPpl(id) 13 | utils.Check(err) 14 | fmt.Printf("%s\n", string(body)) 15 | } 16 | -------------------------------------------------------------------------------- /cmd/port_forward.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | 9 | client "github.com/semaphoreci/cli/api/client" 10 | "github.com/semaphoreci/cli/cmd/utils" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var portForwardCmd = &cobra.Command{ 15 | Use: "port-forward [JOB ID] [LOCAL PORT] [JOB PORT]", 16 | Short: "Port forward a local port to a remote port on the job.", 17 | Long: ``, 18 | Args: cobra.ExactArgs(3), 19 | 20 | Run: func(cmd *cobra.Command, args []string) { 21 | id := args[0] 22 | localPort := args[1] 23 | remotePort := args[2] 24 | 25 | c := client.NewJobsV1AlphaApi() 26 | job, err := c.GetJob(id) 27 | utils.Check(err) 28 | 29 | sshKey, err := c.GetJobDebugSSHKey(job.Metadata.Id) 30 | 31 | utils.Check(err) 32 | 33 | if job.Status.State == "FINISHED" { 34 | fmt.Printf("Job %s has already finished.\n", job.Metadata.Id) 35 | os.Exit(1) 36 | } 37 | 38 | if job.Status.State != "RUNNING" { 39 | fmt.Printf("Job %s has not yet started.\n", job.Metadata.Id) 40 | os.Exit(1) 41 | } 42 | 43 | ip := job.Status.Agent.Ip 44 | 45 | var sshPort int32 46 | sshPort = 0 47 | 48 | for _, p := range job.Status.Agent.Ports { 49 | if p.Name == "ssh" { 50 | sshPort = p.Number 51 | } 52 | } 53 | 54 | if ip != "" && sshPort != 0 { 55 | sshAndPortForward(ip, sshPort, "semaphore", localPort, remotePort, sshKey.Key) 56 | } else { 57 | fmt.Printf("Port forwarding is not possible for job %s.\n", job.Metadata.Id) 58 | os.Exit(1) 59 | } 60 | }, 61 | } 62 | 63 | func sshAndPortForward(ip string, sshPort int32, username string, localPort string, remotePort string, sshKey string) { 64 | sshPath, err := exec.LookPath("ssh") 65 | 66 | utils.Check(err) 67 | 68 | portFlag := fmt.Sprintf("-p %d", sshPort) 69 | userAndIP := fmt.Sprintf("%s@%s", username, ip) 70 | portForwardRule := fmt.Sprintf("%s:0.0.0.0:%s", localPort, remotePort) 71 | noStrictRule := "-oStrictHostKeyChecking=no" 72 | identityOnlyrule := "-oIdentitiesOnly=yes" 73 | 74 | sshKeyFile, _ := ioutil.TempFile("", "sem-cli-debug-private-key") 75 | defer os.Remove(sshKeyFile.Name()) 76 | _, err = sshKeyFile.Write([]byte(sshKey)) 77 | utils.Check(err) 78 | err = sshKeyFile.Close() 79 | utils.Check(err) 80 | 81 | fmt.Printf("Forwarding %s:%s -> %s:%s...\n", ip, remotePort, "0.0.0.0", localPort) 82 | 83 | // #nosec 84 | sshCmd := exec.Command(sshPath, "-L", portForwardRule, portFlag, noStrictRule, identityOnlyrule, userAndIP, "-i", sshKeyFile.Name(), "sleep infinity") 85 | 86 | sshCmd.Stdin = os.Stdin 87 | sshCmd.Stdout = os.Stdout 88 | err = sshCmd.Start() 89 | 90 | utils.Check(err) 91 | 92 | err = sshCmd.Wait() 93 | 94 | utils.Check(err) 95 | } 96 | 97 | func init() { 98 | RootCmd.AddCommand(portForwardCmd) 99 | } 100 | -------------------------------------------------------------------------------- /cmd/rebuild.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/semaphoreci/cli/cmd/pipelines" 5 | "github.com/semaphoreci/cli/cmd/workflows" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var rebuildCmd = &cobra.Command{ 10 | Use: "rebuild [KIND]", 11 | Short: "Rebuild workflow or pipeline.", 12 | Long: ``, 13 | Args: cobra.ExactArgs(1), 14 | } 15 | 16 | var rebuildPplCmd = &cobra.Command{ 17 | Use: "pipeline [id]", 18 | Short: "Partially rebuild failed pipeline.", 19 | Long: ``, 20 | Aliases: []string{"pipelines", "ppl"}, 21 | Args: cobra.ExactArgs(1), 22 | 23 | Run: func(cmd *cobra.Command, args []string) { 24 | id := args[0] 25 | pipelines.Rebuild(id) 26 | }, 27 | } 28 | 29 | var rebuildWfCmd = &cobra.Command{ 30 | Use: "workflow [id]", 31 | Short: "Rebuild workflow.", 32 | Long: `Create new workflow, as if new new github push arrived.`, 33 | Aliases: []string{"workflows", "wf"}, 34 | Args: cobra.ExactArgs(1), 35 | 36 | Run: func(cmd *cobra.Command, args []string) { 37 | id := args[0] 38 | workflows.Rebuild(id) 39 | }, 40 | } 41 | 42 | func init() { 43 | RootCmd.AddCommand(rebuildCmd) 44 | 45 | rebuildCmd.AddCommand(rebuildPplCmd) 46 | rebuildCmd.AddCommand(rebuildWfCmd) 47 | } 48 | -------------------------------------------------------------------------------- /cmd/rebuild_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | httpmock "github.com/jarcoal/httpmock" 8 | ) 9 | 10 | func Test__RebuildPipeline__Response200(t *testing.T) { 11 | httpmock.Activate() 12 | defer httpmock.DeactivateAndReset() 13 | 14 | received := "" 15 | 16 | httpmock.RegisterResponder("POST", "https://org.semaphoretext.xyz/api/v1alpha/pipelines/494b76aa-f3f0-4ecf-b5ef-c389591a01be/partial_rebuild", 17 | func(req *http.Request) (*http.Response, error) { 18 | 19 | received = "true" 20 | 21 | return httpmock.NewStringResponse(200, "message"), nil 22 | }, 23 | ) 24 | 25 | RootCmd.SetArgs([]string{"rebuild", "pipeline", "494b76aa-f3f0-4ecf-b5ef-c389591a01be"}) 26 | RootCmd.Execute() 27 | 28 | expected := "true" 29 | 30 | if received != expected { 31 | t.Errorf("Expected the API to receive POST pipelines with: %s, got: %s", expected, received) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/semaphoreci/cli/cmd/utils" 10 | 11 | homedir "github.com/mitchellh/go-homedir" 12 | "github.com/spf13/cobra" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | var cfgFile string 17 | var Verbose bool 18 | 19 | // RootCmd represents the base command when called without any subcommands 20 | var RootCmd = &cobra.Command{ 21 | Use: "sem", 22 | Short: "Semaphore 2.0 command line interface", 23 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 24 | if !Verbose { 25 | log.SetOutput(ioutil.Discard) 26 | } 27 | }, 28 | 29 | // Uncomment the following line if your bare application 30 | // has an action associated with it: 31 | // Run: func(cmd *cobra.Command, args []string) { }, 32 | } 33 | 34 | // Execute adds all child commands to the root command and sets flags appropriately. 35 | // This is called by main.main(). It only needs to happen once to the RootCmd. 36 | func Execute() { 37 | if err := RootCmd.Execute(); err != nil { 38 | fmt.Println(err) 39 | os.Exit(1) 40 | } 41 | } 42 | 43 | func init() { 44 | cobra.OnInitialize(initConfig) 45 | 46 | RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") 47 | } 48 | 49 | // initConfig reads in config file and ENV variables if set. 50 | func initConfig() { 51 | home, err := homedir.Dir() 52 | 53 | utils.CheckWithMessage(err, "failed to find home directory") 54 | 55 | // Search config in home directory with name ".sem" (without extension). 56 | viper.AddConfigPath(home) 57 | viper.SetConfigName(".sem") 58 | 59 | // Touch config file and make sure that it exists 60 | path := fmt.Sprintf("%s/.sem.yaml", home) 61 | 62 | // #nosec 63 | _, _ = os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0644) 64 | 65 | err = viper.ReadInConfig() 66 | 67 | utils.CheckWithMessage(err, "failed to load config file") 68 | } 69 | -------------------------------------------------------------------------------- /cmd/ssh/connection.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "time" 11 | 12 | models "github.com/semaphoreci/cli/api/models" 13 | ) 14 | 15 | type Connection struct { 16 | IP string 17 | Port int32 18 | Username string 19 | SSHKeyFile *os.File 20 | } 21 | 22 | func NewConnection(ip string, port int32, username string, sshKey string) (*Connection, error) { 23 | sshKeyFile, err := ioutil.TempFile("", "sem-cli-debug-private-key") 24 | if err != nil { 25 | return nil, err 26 | } 27 | if _, err := sshKeyFile.Write([]byte(sshKey)); err != nil { 28 | return nil, err 29 | } 30 | if err := sshKeyFile.Close(); err != nil { 31 | return nil, err 32 | } 33 | 34 | return &Connection{ 35 | IP: ip, 36 | Port: port, 37 | Username: username, 38 | SSHKeyFile: sshKeyFile, 39 | }, nil 40 | } 41 | 42 | func NewConnectionForJob(job *models.JobV1Alpha, sshKeyPath string) (*Connection, error) { 43 | ip := job.Status.Agent.Ip 44 | 45 | var port int32 46 | port = 0 47 | 48 | for _, p := range job.Status.Agent.Ports { 49 | if p.Name == "ssh" { 50 | port = p.Number 51 | } 52 | } 53 | 54 | if ip == "" || port == 0 { 55 | err := fmt.Errorf("Job %s has no exposed SSH ports.\n", job.Metadata.Id) 56 | 57 | return nil, err 58 | } 59 | 60 | return NewConnection(ip, port, "semaphore", sshKeyPath) 61 | } 62 | 63 | func (c *Connection) Close() { 64 | if err := os.Remove(c.SSHKeyFile.Name()); err != nil { 65 | fmt.Printf("Error removing %s: %v\n", c.SSHKeyFile.Name(), err) 66 | } 67 | } 68 | 69 | func (c *Connection) WaitUntilReady(attempts int, callback func()) error { 70 | var err error 71 | var ok bool 72 | 73 | for i := 0; i < attempts-1; i++ { 74 | time.Sleep(1 * time.Second) 75 | 76 | ok, err = c.IsReady() 77 | 78 | if ok && err == nil { 79 | return nil 80 | } else { 81 | callback() 82 | } 83 | } 84 | 85 | return fmt.Errorf("SSH connection can't be established; %s", err) 86 | } 87 | 88 | func (c *Connection) IsReady() (bool, error) { 89 | cmd, err := c.sshCommand("bash /tmp/ssh_jump_point cat /tmp/sempahore-user-commands-have-started", false) 90 | log.Printf("SSH connection: Running %+v", cmd) 91 | 92 | if err != nil { 93 | return false, err 94 | } 95 | 96 | output, err := cmd.CombinedOutput() 97 | log.Printf("SSH connection: Output %s", output) 98 | 99 | if err == nil && strings.Contains(string(output), "yes") { 100 | return true, nil 101 | } else { 102 | outputOneLine := string(output) 103 | // remove empty spaces from ends 104 | outputOneLine = strings.TrimSpace(outputOneLine) 105 | // remove \r 106 | outputOneLine = strings.Replace(outputOneLine, "\r", "", -1) 107 | // join lines 108 | outputOneLine = strings.Replace(outputOneLine, "\n", ", ", -1) 109 | // remove trailing '.' 110 | outputOneLine = strings.Trim(outputOneLine, ".") 111 | 112 | return false, fmt.Errorf("%s; %s", outputOneLine, err) 113 | } 114 | } 115 | 116 | func (c *Connection) Session() error { 117 | cmd, err := c.sshCommand("bash /tmp/ssh_jump_point", true) 118 | 119 | if err != nil { 120 | return err 121 | } 122 | 123 | cmd.Stdin = os.Stdin 124 | cmd.Stdout = os.Stdout 125 | cmd.Stderr = os.Stderr 126 | 127 | err = cmd.Start() 128 | 129 | if err != nil { 130 | return err 131 | } 132 | 133 | return cmd.Wait() 134 | } 135 | 136 | func (c *Connection) sshCommand(directive string, interactive bool) (*exec.Cmd, error) { 137 | path, err := exec.LookPath("ssh") 138 | 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | portFlag := fmt.Sprintf("-p%d", c.Port) 144 | 145 | interactiveFlag := "" 146 | if interactive { 147 | interactiveFlag = "-t" 148 | } else { 149 | interactiveFlag = "-T" 150 | } 151 | 152 | sshKeyFlag := fmt.Sprintf("-i%s", c.SSHKeyFile.Name()) 153 | 154 | noStrictFlag := "-oStrictHostKeyChecking=no" 155 | timeoutFlag := "-oConnectTimeout=5" 156 | identitiesOnlyFlag := "-oIdentitiesOnly=yes" 157 | userAndIp := fmt.Sprintf("%s@%s", c.Username, c.IP) 158 | 159 | // #nosec 160 | cmd := exec.Command( 161 | path, 162 | identitiesOnlyFlag, 163 | interactiveFlag, 164 | sshKeyFlag, 165 | portFlag, 166 | timeoutFlag, 167 | noStrictFlag, 168 | userAndIp, 169 | directive) 170 | 171 | return cmd, nil 172 | } 173 | -------------------------------------------------------------------------------- /cmd/ssh/session.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "time" 8 | 9 | client "github.com/semaphoreci/cli/api/client" 10 | models "github.com/semaphoreci/cli/api/models" 11 | 12 | "github.com/semaphoreci/cli/cmd/utils" 13 | ) 14 | 15 | func StartDebugJobSession(debug *models.DebugJobV1Alpha, message string) error { 16 | c := client.NewJobsV1AlphaApi() 17 | job, err := c.CreateDebugJob(debug) 18 | utils.Check(err) 19 | 20 | return StartDebugSession(job, message) 21 | } 22 | 23 | func StartDebugProjectSession(debug_project *models.DebugProjectV1Alpha, message string) error { 24 | c := client.NewJobsV1AlphaApi() 25 | job, err := c.CreateDebugProject(debug_project) 26 | utils.Check(err) 27 | 28 | return StartDebugSession(job, message) 29 | } 30 | 31 | func StartDebugSession(job *models.JobV1Alpha, message string) error { 32 | c := client.NewJobsV1AlphaApi() 33 | 34 | defer func() { 35 | fmt.Printf("\n") 36 | fmt.Printf("* Stopping debug session ..\n") 37 | 38 | err := c.StopJob(job.Metadata.Id) 39 | 40 | if err != nil { 41 | utils.Check(err) 42 | } else { 43 | fmt.Printf("* Session stopped\n") 44 | } 45 | }() 46 | 47 | fmt.Printf("* Waiting for debug session to boot up .") 48 | err := waitUntilJobIsRunning(job, func() { fmt.Printf(".") }) 49 | fmt.Println() 50 | 51 | // Reload Job 52 | job, err = c.GetJob(job.Metadata.Id) 53 | if err != nil { 54 | fmt.Printf("\n[ERROR] %s\n", err) 55 | return err 56 | } 57 | 58 | /* 59 | * If this is a debug session for a self-hosted job, an SSH key will not be available. 60 | * The only thing we need to do here is return the self-hosted agent name to the user and hang until the users stops it. 61 | */ 62 | if job.IsSelfHosted() { 63 | fmt.Println(selfHostedSessionMessage(job.AgentName())) 64 | fmt.Println("Once you are done with the debug session, stop this command and the debug session will be stopped.") 65 | c := make(chan os.Signal, 1) 66 | signal.Notify(c, os.Interrupt) 67 | <-c 68 | return nil 69 | } 70 | 71 | /* 72 | * If this is for a cloud debug session, we grab the SSH key and SSH into the proper machine. 73 | */ 74 | sshKey, err := c.GetJobDebugSSHKey(job.Metadata.Id) 75 | if err != nil { 76 | fmt.Printf("\n[ERROR] %s\n", err) 77 | return err 78 | } 79 | 80 | conn, err := NewConnectionForJob(job, sshKey.Key) 81 | if err != nil { 82 | fmt.Printf("\n[ERROR] %s\n", err) 83 | return err 84 | } 85 | defer conn.Close() 86 | 87 | fmt.Printf("* Waiting for ssh daemon to become ready .") 88 | err = conn.WaitUntilReady(20, func() { fmt.Printf(".") }) 89 | fmt.Println() 90 | 91 | if err != nil { 92 | fmt.Printf("\n[ERROR] %s\n", err) 93 | return err 94 | } 95 | 96 | fmt.Println(message) 97 | 98 | err = conn.Session() 99 | 100 | if err != nil { 101 | fmt.Printf("\n[ERROR] %s\n", err) 102 | return err 103 | } 104 | 105 | return nil 106 | } 107 | 108 | func waitUntilJobIsRunning(job *models.JobV1Alpha, callback func()) error { 109 | var err error 110 | 111 | c := client.NewJobsV1AlphaApi() 112 | 113 | for { 114 | time.Sleep(1000 * time.Millisecond) 115 | 116 | job, err = c.GetJob(job.Metadata.Id) 117 | 118 | if err != nil { 119 | continue 120 | } 121 | 122 | if job.Status.State == "FINISHED" { 123 | return fmt.Errorf("Job '%s' has already finished.\n", job.Metadata.Id) 124 | } 125 | 126 | if job.Status.State == "RUNNING" { 127 | return nil 128 | } 129 | 130 | // do some processing between ticks 131 | callback() 132 | } 133 | } 134 | 135 | func selfHostedSessionMessage(agentName string) string { 136 | return fmt.Sprintf(` 137 | Semaphore CI Self-Hosted Debug Session. 138 | 139 | - The debug session you created is running in the self-hosted agent named '%s'. 140 | - Once you access the machine where that agent is running, make sure you are logged in as the same user the Semaphore agent is using. 141 | - Source the '/tmp/.env-*' file where the agent keeps all the environment variables exposed to the job. 142 | - Checkout your code with `+"`checkout`"+`. 143 | 144 | Documentation: https://docs.semaphoreci.com/essentials/debugging-with-ssh-access/. 145 | `, agentName) 146 | } 147 | -------------------------------------------------------------------------------- /cmd/stop.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/pipelines" 8 | "github.com/semaphoreci/cli/cmd/utils" 9 | "github.com/semaphoreci/cli/cmd/workflows" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var stopCmd = &cobra.Command{ 14 | Use: "stop [KIND]", 15 | Short: "Stop resource execution.", 16 | Long: ``, 17 | Args: cobra.RangeArgs(1, 2), 18 | } 19 | 20 | var StopPplCmd = &cobra.Command{ 21 | Use: "pipeline [id]", 22 | Short: "Stop running pipeline.", 23 | Long: ``, 24 | Aliases: []string{"pipelines", "ppl"}, 25 | Args: cobra.RangeArgs(1, 1), 26 | 27 | Run: func(cmd *cobra.Command, args []string) { 28 | id := args[0] 29 | pipelines.Stop(id) 30 | }, 31 | } 32 | 33 | var StopJobCmd = &cobra.Command{ 34 | Use: "job [id]", 35 | Short: "Stop running job.", 36 | Long: ``, 37 | Aliases: []string{"jobs"}, 38 | Args: cobra.RangeArgs(1, 1), 39 | 40 | Run: func(cmd *cobra.Command, args []string) { 41 | id := args[0] 42 | 43 | jobClient := client.NewJobsV1AlphaApi() 44 | err := jobClient.StopJob(id) 45 | 46 | utils.Check(err) 47 | 48 | fmt.Printf("Job '%s' stopped.\n", id) 49 | }, 50 | } 51 | 52 | var StopWfCmd = &cobra.Command{ 53 | Use: "workflow [id]", 54 | Short: "Stop all running pipelines in the workflow.", 55 | Long: ``, 56 | Aliases: []string{"workflows", "wf"}, 57 | Args: cobra.RangeArgs(1, 1), 58 | 59 | Run: func(cmd *cobra.Command, args []string) { 60 | id := args[0] 61 | workflows.Stop(id) 62 | }, 63 | } 64 | 65 | func init() { 66 | RootCmd.AddCommand(stopCmd) 67 | 68 | stopCmd.AddCommand(StopPplCmd) 69 | stopCmd.AddCommand(StopJobCmd) 70 | stopCmd.AddCommand(StopWfCmd) 71 | } 72 | -------------------------------------------------------------------------------- /cmd/stop_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "testing" 7 | 8 | httpmock "github.com/jarcoal/httpmock" 9 | assert "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test__StopPipeline__Response200(t *testing.T) { 13 | httpmock.Activate() 14 | defer httpmock.DeactivateAndReset() 15 | 16 | received := "" 17 | 18 | httpmock.RegisterResponder("PATCH", "https://org.semaphoretext.xyz/api/v1alpha/pipelines/494b76aa-f3f0-4ecf-b5ef-c389591a01be", 19 | func(req *http.Request) (*http.Response, error) { 20 | body, _ := ioutil.ReadAll(req.Body) 21 | 22 | received = string(body) 23 | 24 | return httpmock.NewStringResponse(200, "message"), nil 25 | }, 26 | ) 27 | 28 | RootCmd.SetArgs([]string{"stop", "pipeline", "494b76aa-f3f0-4ecf-b5ef-c389591a01be"}) 29 | RootCmd.Execute() 30 | 31 | expected := "{\"terminate_request\": true}" 32 | 33 | if received != expected { 34 | t.Errorf("Expected the API to receive PATCH pipelines with: %s, got: %s", expected, received) 35 | } 36 | } 37 | 38 | func Test__StopJob__Response200(t *testing.T) { 39 | httpmock.Activate() 40 | defer httpmock.DeactivateAndReset() 41 | 42 | received := false 43 | 44 | httpmock.RegisterResponder("POST", "https://org.semaphoretext.xyz/api/v1alpha/jobs/494b76aa-f3f0-4ecf-b5ef-c389591a01be/stop", 45 | func(req *http.Request) (*http.Response, error) { 46 | received = true 47 | 48 | return httpmock.NewStringResponse(200, "message"), nil 49 | }, 50 | ) 51 | 52 | RootCmd.SetArgs([]string{"stop", "job", "494b76aa-f3f0-4ecf-b5ef-c389591a01be"}) 53 | RootCmd.Execute() 54 | 55 | assert.Equal(t, received, true) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/troubleshoot.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | client "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var troubleshootCmd = &cobra.Command{ 12 | Use: "troubleshoot [KIND]", 13 | Short: "Troubleshoot resource.", 14 | Long: ``, 15 | Args: cobra.RangeArgs(1, 2), 16 | } 17 | 18 | var troubleshootPipelineCmd = &cobra.Command{ 19 | Use: "pipeline [id]", 20 | Short: "Troubleshoot pipeline.", 21 | Long: ``, 22 | Aliases: []string{"pipelines", "ppl"}, 23 | Args: cobra.ExactArgs(1), 24 | 25 | Run: func(cmd *cobra.Command, args []string) { 26 | id := args[0] 27 | troubleshootClient := client.NewTroubleshootV1AlphaApi() 28 | t, err := troubleshootClient.TroubleshootPipeline(id) 29 | utils.Check(err) 30 | 31 | v, err := t.ToYaml() 32 | utils.Check(err) 33 | fmt.Printf("%s", v) 34 | }, 35 | } 36 | 37 | var troubleshootJobCmd = &cobra.Command{ 38 | Use: "job [id]", 39 | Short: "Troubleshoot job.", 40 | Long: ``, 41 | Aliases: []string{"jobs"}, 42 | Args: cobra.ExactArgs(1), 43 | 44 | Run: func(cmd *cobra.Command, args []string) { 45 | id := args[0] 46 | troubleshootClient := client.NewTroubleshootV1AlphaApi() 47 | t, err := troubleshootClient.TroubleshootJob(id) 48 | utils.Check(err) 49 | 50 | v, err := t.ToYaml() 51 | utils.Check(err) 52 | fmt.Printf("%s", v) 53 | }, 54 | } 55 | 56 | var troubleshootWorkflowCmd = &cobra.Command{ 57 | Use: "workflow [id]", 58 | Short: "Troubleshoot workflow.", 59 | Long: ``, 60 | Aliases: []string{"workflows", "wf"}, 61 | Args: cobra.ExactArgs(1), 62 | 63 | Run: func(cmd *cobra.Command, args []string) { 64 | id := args[0] 65 | troubleshootClient := client.NewTroubleshootV1AlphaApi() 66 | t, err := troubleshootClient.TroubleshootWorkflow(id) 67 | utils.Check(err) 68 | 69 | v, err := t.ToYaml() 70 | utils.Check(err) 71 | fmt.Printf("%s", v) 72 | }, 73 | } 74 | 75 | func init() { 76 | RootCmd.AddCommand(troubleshootCmd) 77 | 78 | troubleshootCmd.AddCommand(troubleshootPipelineCmd) 79 | troubleshootCmd.AddCommand(troubleshootJobCmd) 80 | troubleshootCmd.AddCommand(troubleshootWorkflowCmd) 81 | } 82 | -------------------------------------------------------------------------------- /cmd/utils/ask_confirm.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | func Ask(in io.Reader, name string) error { 11 | reader := bufio.NewReader(in) 12 | for { 13 | s, _ := reader.ReadString('\n') 14 | s = strings.TrimSuffix(s, "\n") 15 | s = strings.ToLower(s) 16 | if strings.Compare(s, name) == 0 { 17 | break 18 | } else { 19 | return errors.New("user confirmation failed") 20 | } 21 | } 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /cmd/utils/check.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // Checks if an error is present. 10 | // 11 | // If it is present, it displays the error and exits with status 1. 12 | // 13 | // If you want to display a custom message use CheckWithMessage. 14 | func Check(err error) { 15 | if err != nil { 16 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 17 | 18 | Exit(1) 19 | } 20 | } 21 | 22 | // Checks if an error is present. 23 | // 24 | // If it is present, it displays the provided message and exits with status 1. 25 | func CheckWithMessage(err error, message string) { 26 | if err != nil { 27 | fmt.Fprintf(os.Stderr, "error: %+v\n", message) 28 | 29 | Exit(1) 30 | } 31 | } 32 | 33 | func Fail(message string) { 34 | fmt.Fprintf(os.Stderr, "error: %s\n", message) 35 | 36 | Exit(1) 37 | } 38 | 39 | func Exit(code int) { 40 | if flag.Lookup("test.v") == nil { 41 | os.Exit(1) 42 | } else { 43 | panic(fmt.Sprintf("exit %d", code)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cmd/utils/csv_flag.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func CSVFlag(cmd *cobra.Command, flag string) ([]string, error) { 10 | f, err := cmd.Flags().GetString(flag) 11 | 12 | if err != nil { 13 | return []string{}, err 14 | } 15 | 16 | if f == "" { 17 | return []string{}, nil 18 | } 19 | 20 | resultsWithWhiteSpace := strings.Split(f, ",") 21 | results := []string{} 22 | 23 | for _, r := range resultsWithWhiteSpace { 24 | results = append(results, strings.TrimSpace(r)) 25 | } 26 | 27 | return results, err 28 | } 29 | 30 | func CSVArrayFlag(cmd *cobra.Command, flag string, trimSpace bool) (results [][]string, err error) { 31 | vals, err := cmd.Flags().GetStringArray(flag) 32 | if err != nil { 33 | return 34 | } 35 | for _, val := range vals { 36 | results = append(results, processCSVValue(val, trimSpace)) 37 | } 38 | return 39 | } 40 | 41 | func processCSVValue(val string, trimSpace bool) (result []string) { 42 | parts := strings.Split(val, ",") 43 | for _, part := range parts { 44 | if trimSpace { 45 | part = strings.TrimSpace(part) 46 | } 47 | result = append(result, part) 48 | } 49 | 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /cmd/utils/csv_flag_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/spf13/cobra" 7 | assert "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test__CSVFlag__SplitsString(t *testing.T) { 11 | cmd := &cobra.Command{} 12 | 13 | cmd.Flags().String("test", "", "") 14 | cmd.SetArgs([]string{"--test", "a,b,c,d"}) 15 | cmd.Execute() 16 | 17 | values, err := CSVFlag(cmd, "test") 18 | 19 | assert.Nil(t, err) 20 | assert.Equal(t, values, []string{"a", "b", "c", "d"}) 21 | } 22 | 23 | func Test__CSVFlag__RemovesWhitespace(t *testing.T) { 24 | cmd := &cobra.Command{} 25 | 26 | cmd.Flags().String("test", "", "") 27 | cmd.SetArgs([]string{"--test", "a, b, c, d"}) 28 | cmd.Execute() 29 | 30 | values, err := CSVFlag(cmd, "test") 31 | 32 | assert.Nil(t, err) 33 | assert.Equal(t, values, []string{"a", "b", "c", "d"}) 34 | } 35 | 36 | func Test__CSVFlag__EmptyStringHasNoElements(t *testing.T) { 37 | cmd := &cobra.Command{} 38 | 39 | cmd.Flags().String("test", "", "") 40 | cmd.Execute() 41 | 42 | values, err := CSVFlag(cmd, "test") 43 | 44 | assert.Nil(t, err) 45 | assert.Equal(t, values, []string{}) 46 | } 47 | -------------------------------------------------------------------------------- /cmd/utils/editor.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/semaphoreci/cli/config" 12 | ) 13 | 14 | const editedContentTemplate = `# Editing %s. 15 | # When you close the editor, the content will be updated on %s. 16 | 17 | %s 18 | ` 19 | 20 | func EditYamlInEditor(objectName string, content string) (string, error) { 21 | content_with_comment := fmt.Sprintf( 22 | editedContentTemplate, 23 | objectName, 24 | config.GetHost(), 25 | content) 26 | 27 | dir, err := ioutil.TempDir("", "sem-cli-session") 28 | 29 | if err != nil { 30 | return "", fmt.Errorf("Failed to open local temp file for editing '%s'", err) 31 | } 32 | 33 | defer os.RemoveAll(dir) // clean up 34 | 35 | // remove '/' from filename 36 | filename := strings.Replace(fmt.Sprintf("%s.yml", objectName), "/", "-", -1) 37 | // #nosec 38 | tmpfile, err := os.Create(filepath.Join(dir, filename)) 39 | 40 | if err != nil { 41 | return "", fmt.Errorf("Failed to open local temp file for editing '%s'", err) 42 | } 43 | 44 | if _, err := tmpfile.Write([]byte(content_with_comment)); err != nil { 45 | return "", fmt.Errorf("Failed to open local temp file for editing '%s'", err) 46 | } 47 | 48 | if err := tmpfile.Close(); err != nil { 49 | return "", fmt.Errorf("Failed to open local temp file for editing '%s'", err) 50 | } 51 | 52 | editor := config.GetEditor() 53 | 54 | // #nosec 55 | cmd := exec.Command("sh", "-c", fmt.Sprintf("%s %s", editor, tmpfile.Name())) 56 | cmd.Stdin = os.Stdin 57 | cmd.Stdout = os.Stdout 58 | err = cmd.Start() 59 | 60 | if err != nil { 61 | return "", fmt.Errorf("Failed to start editor '%s'", err) 62 | } 63 | 64 | err = cmd.Wait() 65 | 66 | if err != nil { 67 | return "", fmt.Errorf("Editor closed with with '%s'", err) 68 | } 69 | 70 | editedContent, err := ioutil.ReadFile(tmpfile.Name()) 71 | 72 | if err != nil { 73 | return "", fmt.Errorf("Failed to read the content of the edited object '%s'", err) 74 | } 75 | 76 | return string(editedContent), nil 77 | } 78 | -------------------------------------------------------------------------------- /cmd/utils/file_flag.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io/ioutil" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | func ParseFileFlag(raw string) (string, string, error) { 12 | // expected format : 13 | matchFormat, err := regexp.MatchString(`^[^: ]+:[^: ]+$`, raw) 14 | 15 | if err != nil { 16 | return "", "", err 17 | } 18 | 19 | if matchFormat == false { 20 | msg := "The format of --file flag must be: :" 21 | return "", "", fmt.Errorf(msg) 22 | } 23 | 24 | flagPaths := strings.Split(raw, ":") 25 | localPath := flagPaths[0] 26 | remotePath := flagPaths[1] 27 | 28 | // #nosec 29 | content, err := ioutil.ReadFile(localPath) 30 | 31 | if err != nil { 32 | return "", "", err 33 | } 34 | 35 | base64Content := base64.StdEncoding.EncodeToString(content) 36 | 37 | return remotePath, base64Content, nil 38 | } 39 | -------------------------------------------------------------------------------- /cmd/utils/humanized_time.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func RelativeAgeForHumans(timestamp int64) string { 9 | seconds := currentTimestamp() - timestamp 10 | 11 | if seconds < 60 { 12 | return fmt.Sprintf("%ds", seconds) 13 | } 14 | 15 | minutes := seconds / 60 16 | 17 | if minutes < 60 { 18 | return fmt.Sprintf("%dm", minutes) 19 | } 20 | 21 | hours := minutes / 60 22 | 23 | if hours < 24 { 24 | return fmt.Sprintf("%dh", hours) 25 | } 26 | 27 | days := hours / 24 28 | 29 | return fmt.Sprintf("%dd", days) 30 | } 31 | 32 | func currentTimestamp() int64 { 33 | return time.Now().UnixNano() / 1e9 34 | } 35 | -------------------------------------------------------------------------------- /cmd/utils/project.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "log" 7 | "os/exec" 8 | "strings" 9 | 10 | "github.com/semaphoreci/cli/api/client" 11 | ) 12 | 13 | func GetProjectId(name string) string { 14 | projectClient := client.NewProjectV1AlphaApi() 15 | project, err := projectClient.GetProject(name) 16 | 17 | CheckWithMessage(err, fmt.Sprintf("project_id for project '%s' not found; '%s'", name, err)) 18 | 19 | return project.Metadata.Id 20 | } 21 | 22 | func InferProjectName() (string, error) { 23 | originUrl, err := getGitOriginUrl() 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | log.Printf("Origin url: '%s'\n", originUrl) 29 | 30 | projectName, err := getProjectIdFromUrl(originUrl) 31 | if err != nil { 32 | return "", err 33 | } 34 | 35 | return projectName, nil 36 | } 37 | 38 | func getProjectIdFromUrl(url string) (string, error) { 39 | projectClient := client.NewProjectV1AlphaApi() 40 | projects, err := projectClient.ListProjects() 41 | 42 | if err != nil { 43 | return "", fmt.Errorf("getting project list failed '%s'", err) 44 | } 45 | 46 | projectName := "" 47 | for _, p := range projects.Projects { 48 | if p.Spec.Repository.Url == url { 49 | projectName = p.Metadata.Name 50 | break 51 | } 52 | } 53 | 54 | if projectName == "" { 55 | return "", fmt.Errorf("project with url '%s' not found in this org", url) 56 | } 57 | 58 | return projectName, nil 59 | } 60 | 61 | func getGitOriginUrl() (string, error) { 62 | args := []string{"config", "remote.origin.url"} 63 | 64 | cmd := exec.Command("git", args...) 65 | out, err := cmd.CombinedOutput() 66 | if err != nil { 67 | cmd_string := fmt.Sprintf("'%s %s'", "git", strings.Join(args, " ")) 68 | user_msg := "You are probably not in a git directory?" 69 | return "", fmt.Errorf("%s failed with message: '%s'\n%s", cmd_string, err, user_msg) 70 | } 71 | 72 | return strings.TrimSpace(string(out)), nil 73 | } 74 | -------------------------------------------------------------------------------- /cmd/utils/slices.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func Contains(slice []string, item string) bool { 4 | for _, i := range slice { 5 | if i == item { 6 | return true 7 | } 8 | } 9 | 10 | return false 11 | } 12 | -------------------------------------------------------------------------------- /cmd/utils/slices_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | require "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test__Contains(t *testing.T) { 10 | require.False(t, Contains([]string{}, "a")) 11 | require.False(t, Contains([]string{"a", "b", "c"}, "d")) 12 | require.True(t, Contains([]string{"a", "b", "c"}, "a")) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/utils/yaml_resource.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ghodss/yaml" 7 | ) 8 | 9 | // returns tuple (apiVersion, kind, error) 10 | func ParseYamlResourceHeaders(raw []byte) (string, string, error) { 11 | m := make(map[string]interface{}) 12 | 13 | err := yaml.Unmarshal(raw, &m) 14 | if err != nil { 15 | return "", "", fmt.Errorf("Failed to parse resource; %s", err) 16 | } 17 | 18 | apiVersion, ok := m["apiVersion"].(string) 19 | 20 | if !ok { 21 | return "", "", fmt.Errorf("Failed to parse resource's api version") 22 | } 23 | 24 | kind, ok := m["kind"].(string) 25 | 26 | if !ok { 27 | return "", "", fmt.Errorf("Failed to parse resource's kind") 28 | } 29 | 30 | return apiVersion, kind, nil 31 | } 32 | -------------------------------------------------------------------------------- /cmd/utils/yaml_resource_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | assert "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test__ParseYamlResourceHeaders__InvalidValidResource(t *testing.T) { 10 | resource := []byte(` 11 | kind: Projec t 12 | apiVersio n: v1alpha`) 13 | 14 | _, _, err := ParseYamlResourceHeaders(resource) 15 | 16 | assert.Equal(t, err.Error(), "Failed to parse resource; error converting YAML to JSON: yaml: line 2: found character that cannot start any token") 17 | } 18 | 19 | func Test__ParseYamlResourceHeaders__ValidResource(t *testing.T) { 20 | resource := []byte(` 21 | kind: Project 22 | apiVersion: v1alpha`) 23 | 24 | apiVersion, kind, err := ParseYamlResourceHeaders(resource) 25 | 26 | assert.Nil(t, err) 27 | assert.Equal(t, kind, "Project") 28 | assert.Equal(t, apiVersion, "v1alpha") 29 | } 30 | 31 | func Test__ParseYamlResourceHeaders__KindMissing(t *testing.T) { 32 | resource := []byte(`apiVersion: v1alpha`) 33 | 34 | _, _, err := ParseYamlResourceHeaders(resource) 35 | 36 | assert.Equal(t, err.Error(), "Failed to parse resource's kind") 37 | } 38 | 39 | func Test__ParseYamlResourceHeaders__ApiVersionMissing(t *testing.T) { 40 | resource := []byte(`kind: Project`) 41 | 42 | _, _, err := ParseYamlResourceHeaders(resource) 43 | 44 | assert.Equal(t, err.Error(), "Failed to parse resource's api version") 45 | } 46 | 47 | func Test__ParseYamlResourceHeaders__KindIsWrongType(t *testing.T) { 48 | resource := []byte(` 49 | kind: 50 | test: Project 51 | apiVersion: v1alpha`) 52 | 53 | _, _, err := ParseYamlResourceHeaders(resource) 54 | 55 | assert.Equal(t, err.Error(), "Failed to parse resource's kind") 56 | } 57 | 58 | func Test__ParseYamlResourceHeaders__ApiVersionWrongType(t *testing.T) { 59 | resource := []byte(` 60 | kind: Project 61 | apiVersion: 62 | test: v1alpha`) 63 | 64 | _, _, err := ParseYamlResourceHeaders(resource) 65 | 66 | assert.Equal(t, err.Error(), "Failed to parse resource's api version") 67 | } 68 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | ReleaseVersion = "dev" 11 | ReleaseCommit = "none" 12 | ReleaseDate = "unknown" 13 | ) 14 | 15 | var versionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "Display the version", 18 | Long: "", 19 | Run: func(cmd *cobra.Command, args []string) { 20 | fmt.Printf("%v, commit %v, built at %v\n", 21 | ReleaseVersion, 22 | ReleaseCommit, 23 | ReleaseDate) 24 | }, 25 | } 26 | 27 | func init() { 28 | RootCmd.AddCommand(versionCmd) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/workflows/describe.go: -------------------------------------------------------------------------------- 1 | package workflows 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "text/tabwriter" 8 | "time" 9 | 10 | client "github.com/semaphoreci/cli/api/client" 11 | "github.com/semaphoreci/cli/api/models" 12 | "github.com/semaphoreci/cli/cmd/utils" 13 | ) 14 | 15 | func Describe(projectID, wfID string) { 16 | c := client.NewPipelinesV1AlphaApi() 17 | body, err := c.ListPplByWfID(projectID, wfID) 18 | utils.Check(err) 19 | 20 | prettyPrintPipelineList(body) 21 | } 22 | 23 | func prettyPrintPipelineList(jsonList []byte) { 24 | j := models.PipelinesListV1Alpha{} 25 | err := json.Unmarshal(jsonList, &j) 26 | utils.Check(err) 27 | 28 | const padding = 3 29 | w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0) 30 | 31 | fmt.Fprintf(w, "Label: %s\n\n", j[0].Label) 32 | fmt.Fprintln(w, "PIPELINE ID\tPIPELINE NAME\tCREATION TIME\tSTATE") 33 | 34 | for _, p := range j { 35 | createdAt := time.Unix(p.CreatedAt.Seconds, 0).Format("2006-01-02 15:04:05") 36 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", p.Id, p.Name, createdAt, p.State) 37 | } 38 | 39 | if err := w.Flush(); err != nil { 40 | fmt.Printf("Error flushing when pretty printing workflow: %v\n", err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cmd/workflows/get.go: -------------------------------------------------------------------------------- 1 | package workflows 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "text/tabwriter" 7 | "time" 8 | 9 | client "github.com/semaphoreci/cli/api/client" 10 | "github.com/semaphoreci/cli/api/models" 11 | "github.com/semaphoreci/cli/cmd/utils" 12 | ) 13 | 14 | func List(projectID string, options client.ListOptions) { 15 | wfClient := client.NewWorkflowV1AlphaApi() 16 | workflows, err := wfClient.ListWorkflowsWithOptions(projectID, options) 17 | utils.Check(err) 18 | 19 | prettyPrint(workflows) 20 | } 21 | 22 | func prettyPrint(workflows *models.WorkflowListV1Alpha) { 23 | const padding = 3 24 | w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0) 25 | 26 | fmt.Fprintln(w, "WORKFLOW ID\tINITIAL PIPELINE ID\tCREATION TIME\tLABEL") 27 | 28 | for _, p := range workflows.Workflow { 29 | createdAt := time.Unix(p.CreatedAt.Seconds, 0).Format("2006-01-02 15:04:05") 30 | 31 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", p.Id, p.InitialPplId, createdAt, p.BranchName) 32 | } 33 | 34 | if err := w.Flush(); err != nil { 35 | fmt.Printf("Error flushing when pretty printing workflows: %v\n", err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/workflows/rebuild.go: -------------------------------------------------------------------------------- 1 | package workflows 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | ) 9 | 10 | func Rebuild(id string) { 11 | wfClient := client.NewWorkflowV1AlphaApi() 12 | body, err := wfClient.Rebuild(id) 13 | utils.Check(err) 14 | fmt.Printf("%s\n", string(body)) 15 | } 16 | -------------------------------------------------------------------------------- /cmd/workflows/snapshot.go: -------------------------------------------------------------------------------- 1 | package workflows 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/exec" 7 | "path/filepath" 8 | 9 | client "github.com/semaphoreci/cli/api/client" 10 | "github.com/semaphoreci/cli/cmd/utils" 11 | ) 12 | 13 | func CreateSnapshot(projectName, label, archiveName string) ([]byte, error) { 14 | if archiveName == "" { 15 | var err error 16 | archiveName, err = createArchive() 17 | utils.Check(err) 18 | } 19 | 20 | projectID := utils.GetProjectId(projectName) 21 | log.Printf("Project ID: %s\n", projectID) 22 | 23 | if label == "" { 24 | label = "snapshot" 25 | } 26 | log.Printf("Label: %s\n", label) 27 | 28 | c := client.NewWorkflowV1AlphaApi() 29 | return c.CreateSnapshotWf(projectID, label, archiveName) 30 | } 31 | 32 | // FIXME Respect .gitignore file 33 | func createArchive() (string, error) { 34 | archiveFileName := "/tmp/snapshot.tgz" 35 | cmd := exec.Command("rm", "-f", archiveFileName) 36 | err := cmd.Run() 37 | if err != nil { 38 | return "", fmt.Errorf("removing old archive file failed '%s'", err) 39 | } 40 | 41 | files, err := filepath.Glob("*") 42 | if err != nil { 43 | return "", fmt.Errorf("finding files to archive failed '%s'", err) 44 | } 45 | 46 | args := append([]string{"czf", archiveFileName}, files...) 47 | 48 | // #nosec 49 | cmd = exec.Command("/bin/tar", args...) 50 | out, err := cmd.CombinedOutput() 51 | if err != nil { 52 | return "", fmt.Errorf("creating archive file failed '%s'\n%s", out, err) 53 | } 54 | 55 | return archiveFileName, nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/workflows/stop.go: -------------------------------------------------------------------------------- 1 | package workflows 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/semaphoreci/cli/api/client" 7 | "github.com/semaphoreci/cli/cmd/utils" 8 | ) 9 | 10 | func Stop(id string) { 11 | c := client.NewWorkflowV1AlphaApi() 12 | body, err := c.StopWf(id) 13 | utils.Check(err) 14 | fmt.Printf("%s\n", string(body)) 15 | } 16 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | func ContextList() ([]string, error) { 12 | res := []string{} 13 | 14 | m := make(map[string]interface{}) 15 | 16 | err := viper.UnmarshalKey("contexts", &m) 17 | 18 | if err != nil { 19 | return []string{}, err 20 | } 21 | 22 | for k := range m { 23 | res = append(res, k) 24 | } 25 | 26 | return res, nil 27 | } 28 | 29 | func SetActiveContext(name string) { 30 | Set("active-context", name) 31 | } 32 | 33 | func GetActiveContext() string { 34 | if flag.Lookup("test.v") == nil { 35 | return viper.GetString("active-context") 36 | } else { 37 | return "org-semaphoretext-xyz" 38 | } 39 | } 40 | 41 | func GetAuth() string { 42 | if flag.Lookup("test.v") == nil { 43 | context := GetActiveContext() 44 | key_path := fmt.Sprintf("contexts.%s.auth.token", context) 45 | 46 | return Get(key_path) 47 | } else { 48 | return "123456789" 49 | } 50 | } 51 | 52 | func GetEditor() string { 53 | if flag.Lookup("test.v") == nil { 54 | editor := viper.GetString("editor") 55 | 56 | if editor != "" { 57 | return editor 58 | } 59 | 60 | editor = os.Getenv("EDITOR") 61 | 62 | if editor != "" { 63 | return editor 64 | } 65 | 66 | return "vim" 67 | } else { 68 | return "true" // Bash 'true' command, do nothing in tests 69 | } 70 | } 71 | 72 | func SetAuth(token string) { 73 | context := GetActiveContext() 74 | key_path := fmt.Sprintf("contexts.%s.auth.token", context) 75 | 76 | Set(key_path, token) 77 | } 78 | 79 | func GetHost() string { 80 | if flag.Lookup("test.v") == nil { 81 | context := GetActiveContext() 82 | key_path := fmt.Sprintf("contexts.%s.host", context) 83 | 84 | return Get(key_path) 85 | } else { 86 | return "org.semaphoretext.xyz" 87 | } 88 | } 89 | 90 | func SetHost(token string) { 91 | context := GetActiveContext() 92 | key_path := fmt.Sprintf("contexts.%s.host", context) 93 | 94 | Set(key_path, token) 95 | } 96 | 97 | func Set(key string, value string) { 98 | viper.Set(key, value) 99 | if err := viper.WriteConfig(); err != nil { 100 | fmt.Printf("Error writing config: %v.\n", err) 101 | } 102 | } 103 | 104 | func Get(key string) string { 105 | return viper.GetString(key) 106 | } 107 | 108 | func IsSet(key string) bool { 109 | return viper.IsSet(key) 110 | } 111 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | services: 3 | cli: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile.dev 7 | tty: true 8 | command: "sleep 0" 9 | container_name: 'sem-cli' 10 | volumes: 11 | - go-pkg-cache:/go 12 | - .:/app 13 | volumes: 14 | go-pkg-cache: 15 | driver: local 16 | -------------------------------------------------------------------------------- /examples/a.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha 2 | kind: Secret 3 | metadata: 4 | name: aws-secrets 5 | data: 6 | env_vars: 7 | - name: AWS_ID 8 | value: "2" 9 | - name: AWS_SECRET 10 | value: "42 > 23" 11 | -------------------------------------------------------------------------------- /examples/b.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha 2 | kind: Secret 3 | metadata: 4 | name: gcloud-secrets 5 | data: 6 | - name: aws-id 7 | value: "2" 8 | - name: aws-secret 9 | value: "42 > 23" 10 | -------------------------------------------------------------------------------- /examples/p.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha 2 | kind: Project 3 | metadata: 4 | name: test 5 | spec: 6 | repository: 7 | url: "git@github.com:shiroyasha/test.git" 8 | -------------------------------------------------------------------------------- /fixtures/notification.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha 2 | kind: Notification 3 | metadata: 4 | name: test 5 | spec: 6 | rules: 7 | - name: "Rule #1" 8 | filter: 9 | projects: 10 | - cli 11 | branches: 12 | - master 13 | pipelines: 14 | - semaphore.yml 15 | notify: 16 | slack: 17 | endpoint: "https://hooks.slack.com/asdasdasd/sada/sdas/da" 18 | 19 | -------------------------------------------------------------------------------- /generators/yaml.go: -------------------------------------------------------------------------------- 1 | package generators 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | ) 10 | 11 | const semaphore_yaml_template = `version: v1.0 12 | name: First pipeline example 13 | agent: 14 | machine: 15 | type: e1-standard-2 16 | os_image: ubuntu2004 17 | 18 | blocks: 19 | - name: "Build" 20 | task: 21 | env_vars: 22 | - name: APP_ENV 23 | value: prod 24 | jobs: 25 | - name: Docker build 26 | commands: 27 | - checkout 28 | - ls -1 29 | - echo $APP_ENV 30 | - echo "Docker build..." 31 | - echo "done" 32 | 33 | - name: "Smoke tests" 34 | task: 35 | jobs: 36 | - name: Smoke 37 | commands: 38 | - checkout 39 | - echo "make smoke" 40 | 41 | - name: "Unit tests" 42 | task: 43 | jobs: 44 | - name: RSpec 45 | commands: 46 | - checkout 47 | - echo "make rspec" 48 | 49 | - name: Lint code 50 | commands: 51 | - checkout 52 | - echo "make lint" 53 | 54 | - name: Check security 55 | commands: 56 | - checkout 57 | - echo "make security" 58 | 59 | - name: "Integration tests" 60 | task: 61 | jobs: 62 | - name: Cucumber 63 | commands: 64 | - checkout 65 | - echo "make cucumber" 66 | 67 | - name: "Push Image" 68 | task: 69 | jobs: 70 | - name: Push 71 | commands: 72 | - checkout 73 | - echo "make docker.push" 74 | ` 75 | 76 | func GeneratePipelineYaml() error { 77 | if flag.Lookup("test.v") != nil { 78 | return nil // skip generation in tests 79 | } 80 | 81 | path := ".semaphore/semaphore.yml" 82 | 83 | if _, err := os.Stat(".semaphore"); err != nil { 84 | err := os.Mkdir(".semaphore", 0750) 85 | 86 | if err != nil { 87 | return errors.New(fmt.Sprintf("failed to create .semaphore directory '%s'", err)) 88 | } 89 | } 90 | 91 | // #nosec 92 | err := ioutil.WriteFile(".semaphore/semaphore.yml", []byte(semaphore_yaml_template), 0644) 93 | 94 | if err == nil { 95 | return nil 96 | } else { 97 | return errors.New(fmt.Sprintf("failed to create %s file '%s'", path, err)) 98 | } 99 | } 100 | 101 | func PipelineFileExists() bool { 102 | _, err := os.Stat(".semaphore/semaphore.yml") 103 | 104 | return err == nil 105 | } 106 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/semaphoreci/cli 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/ghodss/yaml v1.0.0 7 | github.com/google/uuid v1.3.0 8 | github.com/jarcoal/httpmock v1.3.0 9 | github.com/mitchellh/go-homedir v1.1.0 10 | github.com/spf13/cobra v1.7.0 11 | github.com/spf13/viper v1.16.0 12 | github.com/stretchr/testify v1.8.4 13 | github.com/tcnksm/go-gitconfig v0.1.2 14 | gopkg.in/yaml.v2 v2.4.0 15 | ) 16 | 17 | require ( 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/fsnotify/fsnotify v1.6.0 // indirect 20 | github.com/hashicorp/hcl v1.0.0 // indirect 21 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 22 | github.com/magiconair/properties v1.8.7 // indirect 23 | github.com/mitchellh/mapstructure v1.5.0 // indirect 24 | github.com/onsi/gomega v1.27.10 // indirect 25 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/spf13/afero v1.9.5 // indirect 28 | github.com/spf13/cast v1.5.1 // indirect 29 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 30 | github.com/spf13/pflag v1.0.5 // indirect 31 | github.com/subosito/gotenv v1.4.2 // indirect 32 | golang.org/x/sys v0.10.0 // indirect 33 | golang.org/x/text v0.11.0 // indirect 34 | gopkg.in/ini.v1 v1.67.0 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | client "github.com/semaphoreci/cli/api/client" 8 | "github.com/semaphoreci/cli/cmd" 9 | ) 10 | 11 | // injected as ldflags during building 12 | var ( 13 | version = "dev" 14 | commit = "none" 15 | date = "unknown" 16 | ) 17 | 18 | func main() { 19 | // inject version information 20 | cmd.ReleaseVersion = version 21 | cmd.ReleaseCommit = commit 22 | cmd.ReleaseDate = date 23 | 24 | // Inject Semaphore User-Agent to identify the CLI in HTTP calls 25 | client.UserAgent = fmt.Sprintf("SemaphoreCLI/%s (%s; %s; %s; %s; %s)", version, version, commit, date, runtime.GOOS, runtime.GOARCH) 26 | 27 | cmd.Execute() 28 | } 29 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | go run main.go $@ 4 | -------------------------------------------------------------------------------- /scripts/get.template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | OS=$(uname -s) 4 | ARCH=$(uname -m) 5 | VERSION="VERSION_PLACEHOLDER" 6 | 7 | echo "Downloading Semaphore CLI release ${VERSION} for ${OS}_${ARCH} ..." 8 | echo "" 9 | 10 | readonly TMP_DIR="$(mktemp -d -t sem-XXXX)" 11 | 12 | trap cleanup EXIT 13 | 14 | cleanup() { 15 | rm -rf "${TMP_DIR}" 16 | } 17 | 18 | curl --fail -L "https://github.com/semaphoreci/cli/releases/download/${VERSION}/sem_${OS}_${ARCH}.tar.gz" -o ${TMP_DIR}/sem.tar.gz 19 | 20 | 21 | if ! [ $? -eq 0 ]; then 22 | echo "" 23 | echo "[error] Failed to download Sem CLI release for $OS $ARCH." 24 | echo "" 25 | echo "Supported versions of the Semaphore CLI are:" 26 | echo " - Linux_x86_64" 27 | echo " - Linux_i386" 28 | echo " - Linux_arm64" 29 | echo " - Darwin_i386" 30 | echo " - Darwin_x86_64" 31 | echo " - Darwin_arm64" 32 | echo "" 33 | exit 1 34 | fi 35 | 36 | 37 | tar -xzf ${TMP_DIR}/sem.tar.gz -C ${TMP_DIR} 38 | sudo chmod +x ${TMP_DIR}/sem 39 | sudo mv ${TMP_DIR}/sem /usr/local/bin/ 40 | 41 | 42 | echo "" 43 | echo "Semaphore CLI ${VERSION} for ${OS}_${ARCH} installed." 44 | --------------------------------------------------------------------------------