├── .github └── CODEOWNERS ├── pkg ├── model │ ├── README.md │ ├── StringMap.go │ ├── platform │ │ └── unmarshal.go │ └── abac │ │ └── models_gen.go ├── utils │ ├── test-utils.go │ └── context_reader.go ├── graphql │ ├── cli-release.go │ ├── payments.go │ ├── cli-release_test.go │ ├── user.go │ ├── user_test.go │ ├── cluster_test.go │ ├── component_test.go │ ├── git-source_test.go │ ├── promotion-template.go │ ├── account.go │ ├── git-source.go │ ├── component.go │ ├── graphql.go │ ├── account_test.go │ ├── cluster.go │ ├── pipeline.go │ ├── workflow.go │ └── runtime.go ├── appproxy │ ├── isc.go │ ├── version-info.go │ ├── appproxy.go │ ├── cluster.go │ ├── git-source.go │ └── git_integration.go ├── rest │ ├── progress.go │ ├── user.go │ ├── rest.go │ ├── cluster.go │ ├── cluster_test.go │ ├── token.go │ ├── workflow.go │ ├── argo_test.go │ ├── context_test.go │ ├── context.go │ ├── pipeline.go │ ├── argo.go │ ├── gitops.go │ └── runtime-enrionment.go ├── codefresh │ └── codefresh.go ├── mocks │ └── http_mock.go └── client │ ├── client.go │ └── client_test.go ├── .mockery.yaml ├── scripts ├── test-fmt.sh ├── test.sh └── codecov.sh ├── go.mod ├── .gitignore ├── Makefile ├── go.sum ├── README.md ├── codefresh.yaml └── LICENSE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @codefresh-io/backend 2 | 3 | package/models/*/models_gen.go @codefresh-io/codefresh_developers 4 | -------------------------------------------------------------------------------- /pkg/model/README.md: -------------------------------------------------------------------------------- 1 | # Models Guide 2 | 3 | ## Generate models 4 | 5 | 1) clone `codefresh-io/argo-platform` 6 | 2) open `libs/ql` in terminal 7 | 3) run `yarn gt-go` 8 | 4) copy generated models here 9 | -------------------------------------------------------------------------------- /.mockery.yaml: -------------------------------------------------------------------------------- 1 | dir: pkg/mocks 2 | filename: "{{.PackageName}}_mock.go" 3 | mockname: "Mock{{.InterfaceName}}" 4 | outpkg: "mocks" 5 | with-expecter: true 6 | packages: 7 | net/http: 8 | interfaces: 9 | RoundTripper: {} 10 | -------------------------------------------------------------------------------- /scripts/test-fmt.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | set -e 5 | 6 | files=$(find . -type f -name '*.go') 7 | exitcode=0 8 | for f in $files 9 | do 10 | cmd="gofmt -e -l $f | wc -l" 11 | res=$(eval $cmd) 12 | if [ $res -gt 0 ] 13 | then 14 | echo "cmd: \"$cmd\" failed. cmd result = $res" 15 | exitcode=1 16 | fi 17 | done 18 | 19 | exit $exitcode -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/codefresh-io/go-sdk 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/stretchr/testify v1.9.0 7 | sigs.k8s.io/yaml v1.4.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | github.com/stretchr/objx v0.5.2 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | set -e 5 | 6 | rm -rf cover/ 7 | mkdir cover/ 8 | 9 | echo "running go test" 10 | go test -v -race -coverprofile=cover/cover.out -covermode=atomic ./... 11 | code=$? 12 | echo "go test cmd exited with code $code" 13 | 14 | echo "running go tool cover" 15 | go tool cover -html=cover/cover.out -o=cover/coverage.html 16 | echo "go tool cover exited with code $?" 17 | 18 | exit $code 19 | -------------------------------------------------------------------------------- /pkg/utils/test-utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | "github.com/codefresh-io/go-sdk/pkg/mocks" 9 | ) 10 | 11 | func NewMockClient(t *testing.T) (*client.CfClient, *mocks.MockRoundTripper) { 12 | mockRT := mocks.NewMockRoundTripper(t) 13 | cfClient := client.NewCfClient("https://some.host", "some-token", "grpahql-path", &http.Client{ 14 | Transport: mockRT, 15 | }) 16 | return cfClient, mockRT 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | cover 24 | 25 | # Jetbrains Goland IDE folder 26 | .idea 27 | -------------------------------------------------------------------------------- /pkg/model/StringMap.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | type StringMap map[string]string 10 | 11 | func (m *StringMap) UnmarshalGQL(v interface{}) error { 12 | anyMap, ok := v.(map[string]any) 13 | if !ok { 14 | return fmt.Errorf("StringMap must be a map") 15 | } 16 | 17 | *m = make(map[string]string, len(anyMap)) 18 | for k, v := range anyMap { 19 | (*m)[k], ok = v.(string) 20 | if !ok { 21 | return fmt.Errorf("StringMap value %q must be strings", k) 22 | } 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func (m StringMap) MarshalGQL(w io.Writer) { 29 | _ = json.NewEncoder(w).Encode(m) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/graphql/cli-release.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | ) 9 | 10 | type ( 11 | CliReleaseAPI interface { 12 | GetLatest(ctx context.Context) (string, error) 13 | } 14 | 15 | cliRelease struct { 16 | client *client.CfClient 17 | } 18 | ) 19 | 20 | func (c *cliRelease) GetLatest(ctx context.Context) (string, error) { 21 | query := ` 22 | query LatestCliRelease { 23 | latestCliRelease 24 | }` 25 | variables := map[string]any{} 26 | res, err := client.GraphqlAPI[string](ctx, c.client, query, variables) 27 | if err != nil { 28 | return "", fmt.Errorf("failed getting latest cli release: %w", err) 29 | } 30 | 31 | return res, nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/appproxy/isc.go: -------------------------------------------------------------------------------- 1 | package appproxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | ) 9 | 10 | type ( 11 | IscAPI interface { 12 | RemoveRuntimeFromIscRepo(ctx context.Context) (int, error) 13 | } 14 | 15 | isc struct { 16 | client *client.CfClient 17 | } 18 | ) 19 | 20 | func (c *isc) RemoveRuntimeFromIscRepo(ctx context.Context) (int, error) { 21 | query := ` 22 | mutation RemoveRuntimeFromIscRepo { 23 | removeRuntimeFromIscRepo 24 | }` 25 | variables := map[string]any{} 26 | res, err := client.GraphqlAPI[int](ctx, c.client, query, variables) 27 | if err != nil { 28 | return 0, fmt.Errorf("failed removing runtime from ISC repo: %w", err) 29 | } 30 | 31 | return res, nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/appproxy/version-info.go: -------------------------------------------------------------------------------- 1 | package appproxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | apmodel "github.com/codefresh-io/go-sdk/pkg/model/app-proxy" 9 | ) 10 | 11 | type ( 12 | VersionInfoAPI interface { 13 | VersionInfo(ctx context.Context) (*apmodel.AppProxyVersionInfo, error) 14 | } 15 | 16 | versionInfo struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *versionInfo) VersionInfo(ctx context.Context) (*apmodel.AppProxyVersionInfo, error) { 22 | query := ` 23 | query VersionInfo { 24 | versionInfo { 25 | version 26 | platformHost 27 | platformVersion 28 | } 29 | }` 30 | variables := map[string]any{} 31 | res, err := client.GraphqlAPI[apmodel.AppProxyVersionInfo](ctx, c.client, query, variables) 32 | if err != nil { 33 | return nil, fmt.Errorf("failed getting version info: %w", err) 34 | } 35 | 36 | return &res, nil 37 | } 38 | -------------------------------------------------------------------------------- /pkg/graphql/payments.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/client" 9 | ) 10 | 11 | type ( 12 | PaymentsAPI interface { 13 | GetLimitsStatus(ctx context.Context) (*platmodel.LimitsStatus, error) 14 | } 15 | 16 | payments struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *payments) GetLimitsStatus(ctx context.Context) (*platmodel.LimitsStatus, error) { 22 | query := ` 23 | query LimitsStatus { 24 | limitsStatus { 25 | usage { 26 | clusters 27 | applications 28 | } 29 | limits { 30 | clusters 31 | applications 32 | } 33 | status 34 | } 35 | }` 36 | limitsStatus, err := client.GraphqlAPI[platmodel.LimitsStatus](ctx, c.client, query, nil) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to get limits status: %w", err) 39 | } 40 | 41 | return &limitsStatus, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/rest/progress.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/client" 9 | ) 10 | 11 | type ( 12 | ProgressAPI interface { 13 | Get(string) (*Progress, error) 14 | } 15 | 16 | progress struct { 17 | client *client.CfClient 18 | } 19 | 20 | Progress struct { 21 | ID string `json:"id"` 22 | Status string `json:"status"` 23 | Location Location `json:"location"` 24 | } 25 | 26 | Location struct { 27 | Type string `json:"type"` 28 | URL string `json:"url"` 29 | } 30 | ) 31 | 32 | func (p *progress) Get(id string) (*Progress, error) { 33 | res, err := p.client.RestAPI(context.TODO(), &client.RequestOptions{ 34 | Path: fmt.Sprintf("/api/progress/%s", id), 35 | Method: "GET", 36 | }) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed getting progress: %w", err) 39 | } 40 | 41 | result := &Progress{} 42 | return result, json.Unmarshal(res, result) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/appproxy/appproxy.go: -------------------------------------------------------------------------------- 1 | package appproxy 2 | 3 | import "github.com/codefresh-io/go-sdk/pkg/client" 4 | 5 | type ( 6 | AppProxyAPI interface { 7 | Cluster() ClusterAPI 8 | GitSource() GitSourceAPI 9 | ISC() IscAPI 10 | GitIntegration() GitIntegrationAPI 11 | VersionInfo() VersionInfoAPI 12 | } 13 | 14 | apImpl struct { 15 | client *client.CfClient 16 | } 17 | ) 18 | 19 | func NewAppProxyClient(c *client.CfClient) AppProxyAPI { 20 | return &apImpl{client: c} 21 | } 22 | 23 | func (ap *apImpl) Cluster() ClusterAPI { 24 | return &cluster{client: ap.client} 25 | } 26 | 27 | func (ap *apImpl) GitIntegration() GitIntegrationAPI { 28 | return &gitIntegration{client: ap.client} 29 | } 30 | 31 | func (ap *apImpl) VersionInfo() VersionInfoAPI { 32 | return &versionInfo{client: ap.client} 33 | } 34 | 35 | func (ap *apImpl) GitSource() GitSourceAPI { 36 | return &gitSource{client: ap.client} 37 | } 38 | 39 | func (ap *apImpl) ISC() IscAPI { 40 | return &isc{client: ap.client} 41 | } 42 | -------------------------------------------------------------------------------- /pkg/graphql/cli-release_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/mocks" 8 | "github.com/codefresh-io/go-sdk/pkg/utils" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_cliRelease_GetLatest(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | want string 16 | wantErr string 17 | beforeFn func(rt *mocks.MockRoundTripper) 18 | }{} 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | cfClient, mockRT := utils.NewMockClient(t) 22 | if tt.beforeFn != nil { 23 | tt.beforeFn(mockRT) 24 | } 25 | 26 | c := &cliRelease{ 27 | client: cfClient, 28 | } 29 | got, err := c.GetLatest(context.Background()) 30 | if err != nil || tt.wantErr != "" { 31 | assert.EqualError(t, err, tt.wantErr) 32 | return 33 | } 34 | 35 | if got != tt.want { 36 | t.Errorf("cliRelease.GetLatest() = %v, want %v", got, tt.want) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/graphql/user.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | UserAPI interface { 13 | GetCurrent(ctx context.Context) (*platmodel.User, error) 14 | } 15 | 16 | user struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *user) GetCurrent(ctx context.Context) (*platmodel.User, error) { 22 | query := ` 23 | query Me { 24 | me { 25 | id 26 | name 27 | email 28 | isAdmin 29 | accounts { 30 | id 31 | name 32 | } 33 | activeAccount { 34 | id 35 | name 36 | gitProvider 37 | gitApiUrl 38 | sharedConfigRepo 39 | admins 40 | features { 41 | gitopsPlanEnforcement 42 | } 43 | } 44 | } 45 | }` 46 | variables := map[string]any{} 47 | res, err := client.GraphqlAPI[platmodel.User](ctx, c.client, query, variables) 48 | if err != nil { 49 | return nil, fmt.Errorf("failed getting current user: %w", err) 50 | } 51 | 52 | return &res, nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/graphql/user_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/mocks" 9 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 10 | "github.com/codefresh-io/go-sdk/pkg/utils" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func Test_user_GetCurrent(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | want *platmodel.User 18 | wantErr string 19 | beforeFn func(rt *mocks.MockRoundTripper) 20 | }{} 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | cfClient, mockRT := utils.NewMockClient(t) 24 | if tt.beforeFn != nil { 25 | tt.beforeFn(mockRT) 26 | } 27 | 28 | c := &user{ 29 | client: cfClient, 30 | } 31 | got, err := c.GetCurrent(context.Background()) 32 | if err != nil || tt.wantErr != "" { 33 | assert.EqualError(t, err, tt.wantErr) 34 | return 35 | } 36 | 37 | if !reflect.DeepEqual(got, tt.want) { 38 | t.Errorf("user.GetCurrent() = %v, want %v", got, tt.want) 39 | } 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/graphql/cluster_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/mocks" 9 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 10 | "github.com/codefresh-io/go-sdk/pkg/utils" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func Test_cluster_List(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | runtime string 18 | want []platmodel.Cluster 19 | wantErr string 20 | beforeFn func(rt *mocks.MockRoundTripper) 21 | }{} 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | cfClient, mockRT := utils.NewMockClient(t) 25 | if tt.beforeFn != nil { 26 | tt.beforeFn(mockRT) 27 | } 28 | 29 | c := &cluster{ 30 | client: cfClient, 31 | } 32 | got, err := c.List(context.Background(), tt.runtime) 33 | if err != nil || tt.wantErr != "" { 34 | assert.EqualError(t, err, tt.wantErr) 35 | return 36 | } 37 | 38 | if !reflect.DeepEqual(got, tt.want) { 39 | t.Errorf("cluster.List() = %v, want %v", got, tt.want) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/graphql/component_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/mocks" 9 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 10 | "github.com/codefresh-io/go-sdk/pkg/utils" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func Test_component_List(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | runtimeName string 18 | want []platmodel.Component 19 | wantErr string 20 | beforeFn func(rt *mocks.MockRoundTripper) 21 | }{} 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | cfClient, mockRT := utils.NewMockClient(t) 25 | if tt.beforeFn != nil { 26 | tt.beforeFn(mockRT) 27 | } 28 | 29 | c := &component{ 30 | client: cfClient, 31 | } 32 | got, err := c.List(context.Background(), tt.runtimeName) 33 | if err != nil || tt.wantErr != "" { 34 | assert.EqualError(t, err, tt.wantErr) 35 | return 36 | } 37 | 38 | if !reflect.DeepEqual(got, tt.want) { 39 | t.Errorf("component.List() = %v, want %v", got, tt.want) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/graphql/git-source_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/mocks" 9 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 10 | "github.com/codefresh-io/go-sdk/pkg/utils" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func Test_gitSource_List(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | runtimeName string 18 | want []platmodel.GitSource 19 | wantErr string 20 | beforeFn func(rt *mocks.MockRoundTripper) 21 | }{} 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | cfClient, mockRT := utils.NewMockClient(t) 25 | if tt.beforeFn != nil { 26 | tt.beforeFn(mockRT) 27 | } 28 | 29 | c := &gitSource{ 30 | client: cfClient, 31 | } 32 | got, err := c.List(context.Background(), tt.runtimeName) 33 | if err != nil || tt.wantErr != "" { 34 | assert.EqualError(t, err, tt.wantErr) 35 | return 36 | } 37 | 38 | if !reflect.DeepEqual(got, tt.want) { 39 | t.Errorf("gitSource.List() = %v, want %v", got, tt.want) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/graphql/promotion-template.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/client" 9 | ) 10 | 11 | type ( 12 | PromotionTemplateAPI interface { 13 | GetVersionSourceByRuntime(ctx context.Context, app *platmodel.ObjectMeta) (*platmodel.PromotionTemplateShort, error) 14 | } 15 | 16 | promotionTemplate struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *promotionTemplate) GetVersionSourceByRuntime(ctx context.Context, app *platmodel.ObjectMeta) (*platmodel.PromotionTemplateShort, error) { 22 | query := ` 23 | query ($applicationMetadata: Object!) { 24 | promotionTemplateByRuntime(applicationMetadata: $applicationMetadata) { 25 | versionSource { 26 | file 27 | jsonPath 28 | } 29 | } 30 | }` 31 | variables := map[string]any{ 32 | "applicationMetadata": app, 33 | } 34 | versionSource, err := client.GraphqlAPI[platmodel.PromotionTemplateShort](ctx, c.client, query, variables) 35 | if err != nil { 36 | return nil, fmt.Errorf("failed to get promotion template: %w", err) 37 | } 38 | 39 | return &versionSource, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/graphql/account.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | AccountAPI interface { 13 | UpdateCsdpSettings(ctx context.Context, gitProvider platmodel.GitProviders, gitApiUrl, sharedConfigRepo string) error 14 | } 15 | 16 | account struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *account) UpdateCsdpSettings(ctx context.Context, gitProvider platmodel.GitProviders, gitApiUrl, sharedConfigRepo string) error { 22 | query := ` 23 | mutation updateCsdpSettings($gitProvider: GitProviders!, $gitApiUrl: String!, $sharedConfigRepo: String!) { 24 | updateCsdpSettings(gitProvider: $gitProvider, gitApiUrl: $gitApiUrl, sharedConfigRepo: $sharedConfigRepo) 25 | }` 26 | variables := map[string]any{ 27 | "gitProvider": gitProvider, 28 | "gitApiUrl": gitApiUrl, 29 | "sharedConfigRepo": sharedConfigRepo, 30 | } 31 | _, err := client.GraphqlAPI[client.GraphqlVoidResponse](ctx, c.client, query, variables) 32 | if err != nil { 33 | return fmt.Errorf("failed updating csdp settings: %w", err) 34 | } 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /pkg/graphql/git-source.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | GitSourceAPI interface { 13 | List(ctc context.Context, runtimeName string) ([]platmodel.GitSource, error) 14 | } 15 | 16 | gitSource struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *gitSource) List(ctx context.Context, runtimeName string) ([]platmodel.GitSource, error) { 22 | query := ` 23 | query GitSources($runtime: String) { 24 | gitSources(runtime: $runtime) { 25 | edges { 26 | node { 27 | metadata { 28 | name 29 | } 30 | self { 31 | path 32 | repoURL 33 | status { 34 | syncStatus 35 | healthStatus 36 | } 37 | } 38 | } 39 | } 40 | } 41 | }` 42 | variables := map[string]any{ 43 | "runtime": runtimeName, 44 | } 45 | res, err := client.GraphqlAPI[platmodel.GitSourceSlice](ctx, c.client, query, variables) 46 | if err != nil { 47 | return nil, fmt.Errorf("failed getting git-source list: %w", err) 48 | } 49 | 50 | gitSources := make([]platmodel.GitSource, len(res.Edges)) 51 | for i := range res.Edges { 52 | gitSources[i] = *res.Edges[i].Node 53 | } 54 | 55 | return gitSources, nil 56 | } 57 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=v1.4.15 2 | 3 | ifndef GOBIN 4 | ifndef GOPATH 5 | $(error GOPATH is not set, please make sure you set your GOPATH correctly!) 6 | endif 7 | GOBIN=$(GOPATH)/bin 8 | ifndef GOBIN 9 | $(error GOBIN is not set, please make sure you set your GOBIN correctly!) 10 | endif 11 | endif 12 | 13 | .PHONY: test-all 14 | test-all: test test-fmt 15 | 16 | .PHONY: test 17 | test: 18 | @sh ./scripts/test.sh 19 | 20 | .PHONY: test-fmt 21 | test-fmt: 22 | @sh ./scripts/test-fmt.sh 23 | 24 | .PHONY: lint 25 | lint: $(GOBIN)/golangci-lint 26 | @echo linting go code... 27 | @$(GOBIN)/golangci-lint run --fix --timeout 10m 28 | 29 | 30 | # Fix fmt errors in file 31 | .PHONY: fmt 32 | fmt: 33 | go fmt ./... 34 | 35 | # Generate mock struct from interface 36 | # example: make mock PKG=./pkg/runtime NAME=Runtime 37 | .PHONY: mock 38 | mock: $(GOBIN)/mockery 39 | @mockery 40 | 41 | # Runs cript to upload codecov coverage data 42 | .PHONY: upload-coverage 43 | upload-coverage: 44 | @./scripts/codecov.sh -t $(CODECOV_TOKEN) 45 | 46 | .PHONY: cur-version 47 | cur-version: 48 | @echo -n $(VERSION) 49 | 50 | $(GOBIN)/mockery: 51 | @go install github.com/vektra/mockery/v2@v2.42.0 52 | @mockery --version 53 | 54 | $(GOBIN)/golangci-lint: 55 | @echo installing: golangci-lint 56 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.56.2 57 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 4 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 8 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 9 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 10 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 16 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 17 | -------------------------------------------------------------------------------- /pkg/rest/user.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/client" 9 | ) 10 | 11 | type ( 12 | UserAPI interface { 13 | GetCurrent(ctx context.Context) (*User, error) 14 | } 15 | 16 | user struct { 17 | client *client.CfClient 18 | } 19 | 20 | User struct { 21 | ID string `json:"_id"` 22 | Name string `json:"userName"` 23 | Email string `json:"email"` 24 | Accounts []Account `json:"account"` 25 | ActiveAccountName string `json:"activeAccountName"` 26 | Roles []string `json:"roles"` 27 | UserData struct { 28 | Avatar string `json:"image"` 29 | } `json:"user_data"` 30 | } 31 | 32 | Account struct { 33 | Name string `json:"name"` 34 | ID string `json:"_id"` 35 | } 36 | ) 37 | 38 | func (u *user) GetCurrent(ctx context.Context) (*User, error) { 39 | res, err := u.client.RestAPI(ctx, &client.RequestOptions{ 40 | Method: "GET", 41 | Path: "/api/user", 42 | }) 43 | if err != nil { 44 | return nil, fmt.Errorf("failed getting current user: %w", err) 45 | } 46 | 47 | result := &User{} 48 | return result, json.Unmarshal(res, result) 49 | } 50 | 51 | func (u *User) GetActiveAccount() *Account { 52 | for i := 0; i < len(u.Accounts); i++ { 53 | if u.Accounts[i].Name == u.ActiveAccountName { 54 | return &u.Accounts[i] 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/graphql/component.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | ComponentAPI interface { 13 | List(ctx context.Context, runtimeName string) ([]platmodel.Component, error) 14 | } 15 | 16 | component struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *component) List(ctx context.Context, runtimeName string) ([]platmodel.Component, error) { 22 | query := ` 23 | query Components($runtime: String!) { 24 | components(runtime: $runtime) { 25 | edges { 26 | node { 27 | metadata { 28 | name 29 | runtime 30 | } 31 | version 32 | self { 33 | status { 34 | syncStatus 35 | healthStatus 36 | } 37 | errors { 38 | ...on SyncError{ 39 | title 40 | message 41 | suggestion 42 | level 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | }` 50 | variables := map[string]any{ 51 | "runtime": runtimeName, 52 | } 53 | res, err := client.GraphqlAPI[platmodel.ComponentSlice](ctx, c.client, query, variables) 54 | if err != nil { 55 | return nil, fmt.Errorf("failed getting component list: %w", err) 56 | } 57 | 58 | components := make([]platmodel.Component, len(res.Edges)) 59 | for i := range res.Edges { 60 | components[i] = *res.Edges[i].Node 61 | } 62 | 63 | return components, nil 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codefresh SDK for Golang 2 | 3 | [![Codefresh build status]( https://g.codefresh.io/api/badges/pipeline/codefresh-inc/codefresh-io%2Fgo-sdk%2Fgo-sdk?type=cf-1)]( https://g.codefresh.io/public/accounts/codefresh-inc/pipelines/codefresh-io/go-sdk/go-sdk) 4 | [![GoDoc](https://godoc.org/github.com/codefresh-io/go-sdk?status.svg)](https://godoc.org/github.com/codefresh-io/go-sdk) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/codefresh-io/go-sdk)](https://goreportcard.com/report/github.com/codefresh-io/go-sdk) 6 | 7 | # Start 8 | 9 | `go get -u github.com/codefresh-io/go-sdk` 10 | 11 | ```go 12 | import ( 13 | "fmt" 14 | "os" 15 | 16 | "github.com/codefresh-io/go-sdk/pkg/utils" 17 | "github.com/codefresh-io/go-sdk/pkg" 18 | ) 19 | 20 | func main() { 21 | path := fmt.Sprintf("%s/.cfconfig", os.Getenv("HOME")) 22 | options, err := utils.ReadAuthContext(path, "") 23 | if err != nil { 24 | fmt.Println("Failed to read codefresh config file") 25 | panic(err) 26 | } 27 | clientOptions := codefresh.ClientOptions{Host: options.URL, 28 | Auth: codefresh.AuthOptions{Token: options.Token}} 29 | cf := codefresh.New(&clientOptions) 30 | pipelines, err := cf.Pipelines().List() 31 | if err != nil { 32 | fmt.Println("Failed to get Pipelines from Codefresh API") 33 | panic(err) 34 | } 35 | for _, p := range pipelines { 36 | fmt.Printf("Pipeline: %+v\n\n", p) 37 | } 38 | } 39 | 40 | ``` 41 | 42 | This is not an official Codefresh project. 43 | -------------------------------------------------------------------------------- /pkg/appproxy/cluster.go: -------------------------------------------------------------------------------- 1 | package appproxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | ) 9 | 10 | type ( 11 | ClusterAPI interface { 12 | CreateArgoRollouts(ctx context.Context, server string, namespace string) error 13 | Delete(ctx context.Context, server string, runtime string) error 14 | } 15 | 16 | cluster struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *cluster) CreateArgoRollouts(ctx context.Context, server string, namespace string) error { 22 | query := ` 23 | mutation CreateArgoRollouts($args: CreateArgoRolloutsInput!) { 24 | createArgoRollouts(args: $args) 25 | }` 26 | variables := map[string]any{ 27 | "args": map[string]any{ 28 | "destServer": server, 29 | "destNamespace": namespace, 30 | }, 31 | } 32 | _, err := client.GraphqlAPI[client.GraphqlVoidResponse](ctx, c.client, query, variables) 33 | if err != nil { 34 | return fmt.Errorf("failed creating argo rollouts: %w", err) 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (c *cluster) Delete(ctx context.Context, server string, runtime string) error { 41 | query := ` 42 | mutation RemoveCluster($server: String!, $runtime: String!) { 43 | removeCluster(server: $server, runtime: $runtime) 44 | }` 45 | variables := map[string]any{ 46 | "server": server, 47 | "runtime": runtime, 48 | } 49 | _, err := client.GraphqlAPI[client.GraphqlVoidResponse](ctx, c.client, query, variables) 50 | if err != nil { 51 | return fmt.Errorf("failed deleting cluster: %w", err) 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/rest/rest.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import "github.com/codefresh-io/go-sdk/pkg/client" 4 | 5 | type ( 6 | RestAPI interface { 7 | Argo() ArgoAPI 8 | Cluster() ClusterAPI 9 | Context() ContextAPI 10 | Gitops() GitopsAPI 11 | Pipeline() PipelineAPI 12 | Progress() ProgressAPI 13 | RuntimeEnvironment() RuntimeEnvironmentAPI 14 | Token() TokenAPI 15 | User() UserAPI 16 | Workflow() WorkflowAPI 17 | } 18 | 19 | restImpl struct { 20 | client *client.CfClient 21 | } 22 | ) 23 | 24 | func NewRestClient(c *client.CfClient) RestAPI { 25 | return &restImpl{client: c} 26 | } 27 | 28 | func (v1 *restImpl) Argo() ArgoAPI { 29 | return &argo{client: v1.client} 30 | } 31 | 32 | func (v1 *restImpl) Cluster() ClusterAPI { 33 | return &cluster{client: v1.client} 34 | } 35 | 36 | func (v1 *restImpl) Context() ContextAPI { 37 | return &v1Context{client: v1.client} 38 | } 39 | 40 | func (v1 *restImpl) Gitops() GitopsAPI { 41 | return &gitops{client: v1.client} 42 | } 43 | 44 | func (v1 *restImpl) Pipeline() PipelineAPI { 45 | return &pipeline{client: v1.client} 46 | } 47 | 48 | func (v1 *restImpl) Progress() ProgressAPI { 49 | return &progress{client: v1.client} 50 | } 51 | 52 | func (v1 *restImpl) RuntimeEnvironment() RuntimeEnvironmentAPI { 53 | return &runtimeEnvironment{client: v1.client} 54 | } 55 | 56 | func (v1 *restImpl) Token() TokenAPI { 57 | return &token{client: v1.client} 58 | } 59 | 60 | func (v1 *restImpl) User() UserAPI { 61 | return &user{client: v1.client} 62 | } 63 | 64 | func (v1 *restImpl) Workflow() WorkflowAPI { 65 | return &workflow{codefresh: v1.client} 66 | } 67 | -------------------------------------------------------------------------------- /pkg/utils/context_reader.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "sigs.k8s.io/yaml" 8 | ) 9 | 10 | type ( 11 | CFContext struct { 12 | Type string `json:"type"` 13 | Name string `json:"name"` 14 | URL string `json:"url"` 15 | Token string `json:"token"` 16 | Beta bool `json:"beta"` 17 | OnPrem bool `json:"onPrem"` 18 | ACLType string `json:"acl-type"` 19 | UserID string `json:"user-id"` 20 | AccountID string `json:"account-id"` 21 | Expires int `json:"expires"` 22 | UserName string `json:"user-name"` 23 | AccountName string `json:"account-name"` 24 | } 25 | 26 | CFConfig struct { 27 | Contexts map[string]*CFContext `json:"contexts"` 28 | CurrentContext string `json:"current-context"` 29 | } 30 | ) 31 | 32 | func ReadAuthContext(path string, name string) (*CFContext, error) { 33 | config, err := getCFConfig(path) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | var context *CFContext 39 | if name != "" { 40 | context = config.Contexts[name] 41 | } else { 42 | context = config.Contexts[config.CurrentContext] 43 | } 44 | 45 | return context, nil 46 | } 47 | 48 | func getCFConfig(path string) (*CFConfig, error) { 49 | content, err := os.ReadFile(path) 50 | if err != nil { 51 | fmt.Printf("Error reading file\n") 52 | return nil, err 53 | } 54 | 55 | config := CFConfig{} 56 | err = yaml.Unmarshal(content, &config) 57 | if err != nil { 58 | fmt.Printf("Error unmarshaling content\n") 59 | fmt.Println(err.Error()) 60 | return nil, err 61 | } 62 | 63 | return &config, nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/rest/cluster.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/client" 9 | ) 10 | 11 | type ( 12 | ClusterAPI interface { 13 | GetAccountClusters() ([]ClusterMinified, error) 14 | GetClusterCredentialsByAccountId(selector string) (*Cluster, error) 15 | } 16 | 17 | cluster struct { 18 | client *client.CfClient 19 | } 20 | 21 | Cluster struct { 22 | Auth struct { 23 | Bearer string 24 | } `json:"auth"` 25 | Ca string `json:"ca"` 26 | Url string `json:"url"` 27 | } 28 | 29 | ClusterMinified struct { 30 | Cluster struct { 31 | Name string `json:"name"` 32 | } `json:"cluster"` 33 | BehindFirewall bool `json:"behindFirewall"` 34 | Selector string `json:"selector"` 35 | Provider string `json:"provider"` 36 | } 37 | ) 38 | 39 | func (p *cluster) GetAccountClusters() ([]ClusterMinified, error) { 40 | res, err := p.client.RestAPI(context.TODO(), &client.RequestOptions{ 41 | Method: "GET", 42 | Path: "/api/clusters", 43 | }) 44 | if err != nil { 45 | return nil, fmt.Errorf("failed getting account cluster list: %w", err) 46 | } 47 | 48 | result := make([]ClusterMinified, 0) 49 | return result, json.Unmarshal(res, &result) 50 | } 51 | 52 | func (p *cluster) GetClusterCredentialsByAccountId(selector string) (*Cluster, error) { 53 | res, err := p.client.RestAPI(context.TODO(), &client.RequestOptions{ 54 | Method: "GET", 55 | Path: fmt.Sprintf("/api/clusters/%s/credentials", selector), 56 | }) 57 | if err != nil { 58 | return nil, fmt.Errorf("failed getting account cluster credentials: %w", err) 59 | } 60 | 61 | result := &Cluster{} 62 | return result, json.Unmarshal(res, result) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/graphql/graphql.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import "github.com/codefresh-io/go-sdk/pkg/client" 4 | 5 | type ( 6 | GraphQLAPI interface { 7 | Account() AccountAPI 8 | CliRelease() CliReleaseAPI 9 | Cluster() ClusterAPI 10 | Component() ComponentAPI 11 | GitSource() GitSourceAPI 12 | Pipeline() PipelineAPI 13 | Runtime() RuntimeAPI 14 | User() UserAPI 15 | Workflow() WorkflowAPI 16 | PromotionTemplate() PromotionTemplateAPI 17 | Payments() PaymentsAPI 18 | } 19 | 20 | gqlImpl struct { 21 | client *client.CfClient 22 | } 23 | ) 24 | 25 | func NewGraphQLClient(c *client.CfClient) GraphQLAPI { 26 | return &gqlImpl{client: c} 27 | } 28 | 29 | func (v2 *gqlImpl) Account() AccountAPI { 30 | return &account{client: v2.client} 31 | } 32 | 33 | func (v2 *gqlImpl) CliRelease() CliReleaseAPI { 34 | return &cliRelease{client: v2.client} 35 | } 36 | 37 | func (v2 *gqlImpl) Cluster() ClusterAPI { 38 | return &cluster{client: v2.client} 39 | } 40 | 41 | func (v2 *gqlImpl) Component() ComponentAPI { 42 | return &component{client: v2.client} 43 | } 44 | 45 | func (v2 *gqlImpl) GitSource() GitSourceAPI { 46 | return &gitSource{client: v2.client} 47 | } 48 | 49 | func (v2 *gqlImpl) Pipeline() PipelineAPI { 50 | return &pipeline{client: v2.client} 51 | } 52 | 53 | func (v2 *gqlImpl) Runtime() RuntimeAPI { 54 | return &runtime{client: v2.client} 55 | } 56 | 57 | func (v2 *gqlImpl) User() UserAPI { 58 | return &user{client: v2.client} 59 | } 60 | 61 | func (v2 *gqlImpl) Workflow() WorkflowAPI { 62 | return &workflow{client: v2.client} 63 | } 64 | 65 | func (v2 *gqlImpl) PromotionTemplate() PromotionTemplateAPI { 66 | return &promotionTemplate{client: v2.client} 67 | } 68 | 69 | func (v2 *gqlImpl) Payments() PaymentsAPI { 70 | return &payments{client: v2.client} 71 | } 72 | -------------------------------------------------------------------------------- /pkg/rest/cluster_test.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/mocks" 8 | "github.com/codefresh-io/go-sdk/pkg/utils" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_cluster_GetAccountClusters(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | want []ClusterMinified 16 | wantErr string 17 | beforeFn func(rt *mocks.MockRoundTripper) 18 | }{} 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | cfClient, mockRT := utils.NewMockClient(t) 22 | if tt.beforeFn != nil { 23 | tt.beforeFn(mockRT) 24 | } 25 | 26 | p := &cluster{ 27 | client: cfClient, 28 | } 29 | got, err := p.GetAccountClusters() 30 | if err != nil || tt.wantErr != "" { 31 | assert.EqualError(t, err, tt.wantErr) 32 | return 33 | } 34 | 35 | if !reflect.DeepEqual(got, tt.want) { 36 | t.Errorf("cluster.GetAccountClusters() = %v, want %v", got, tt.want) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | func Test_cluster_GetClusterCredentialsByAccountId(t *testing.T) { 43 | tests := []struct { 44 | name string 45 | selector string 46 | want *Cluster 47 | wantErr string 48 | beforeFn func(rt *mocks.MockRoundTripper) 49 | }{} 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | cfClient, mockRT := utils.NewMockClient(t) 53 | if tt.beforeFn != nil { 54 | tt.beforeFn(mockRT) 55 | } 56 | 57 | p := &cluster{ 58 | client: cfClient, 59 | } 60 | got, err := p.GetClusterCredentialsByAccountId(tt.selector) 61 | if err != nil || tt.wantErr != "" { 62 | assert.EqualError(t, err, tt.wantErr) 63 | return 64 | } 65 | 66 | if !reflect.DeepEqual(got, tt.want) { 67 | t.Errorf("cluster.GetClusterCredentialsByAccountId() = %v, want %v", got, tt.want) 68 | } 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/graphql/account_test.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/codefresh-io/go-sdk/pkg/mocks" 11 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 12 | "github.com/codefresh-io/go-sdk/pkg/utils" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/mock" 16 | ) 17 | 18 | func Test_account_UpdateCsdpSettings(t *testing.T) { 19 | type args struct { 20 | gitProvider platmodel.GitProviders 21 | gitApiUrl string 22 | sharedConfigRepo string 23 | } 24 | tests := []struct { 25 | name string 26 | args args 27 | wantErr string 28 | beforeFn func(rt *mocks.MockRoundTripper) 29 | }{ 30 | { 31 | name: "should return nil when csdp settings updated", 32 | args: args{ 33 | gitProvider: platmodel.GitProvidersBitbucket, 34 | gitApiUrl: "https://bitbucket.org", 35 | sharedConfigRepo: "codefresh/cf-test", 36 | }, 37 | beforeFn: func(rt *mocks.MockRoundTripper) { 38 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(_ *http.Request) (*http.Response, error) { 39 | bodyReader := io.NopCloser(strings.NewReader("null")) 40 | res := &http.Response{ 41 | StatusCode: 200, 42 | Body: bodyReader, 43 | } 44 | return res, nil 45 | }) 46 | }, 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | cfClient, mockRT := utils.NewMockClient(t) 52 | if tt.beforeFn != nil { 53 | tt.beforeFn(mockRT) 54 | } 55 | 56 | c := &account{ 57 | client: cfClient, 58 | } 59 | if err := c.UpdateCsdpSettings(context.Background(), tt.args.gitProvider, tt.args.gitApiUrl, tt.args.sharedConfigRepo); err != nil || tt.wantErr != "" { 60 | assert.EqualError(t, err, tt.wantErr) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/rest/token.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/codefresh-io/go-sdk/pkg/client" 10 | ) 11 | 12 | type ( 13 | TokenAPI interface { 14 | Create(name string, subject string) (*Token, error) 15 | List() ([]Token, error) 16 | } 17 | 18 | token struct { 19 | client *client.CfClient 20 | } 21 | 22 | Token struct { 23 | ID string `json:"_id"` 24 | Name string `json:"name"` 25 | TokenPrefix string `json:"tokenPrefix"` 26 | Created time.Time `json:"created"` 27 | Subject struct { 28 | Type string `json:"type"` 29 | Ref string `json:"ref"` 30 | } `json:"subject"` 31 | Value string 32 | } 33 | 34 | tokenSubjectType int 35 | ) 36 | 37 | const ( 38 | runtimeEnvironmentSubject tokenSubjectType = 0 39 | ) 40 | 41 | func (s tokenSubjectType) String() string { 42 | return [...]string{"runtime-environment"}[s] 43 | } 44 | 45 | func (t *token) Create(name string, subject string) (*Token, error) { 46 | res, err := t.client.RestAPI(context.TODO(), &client.RequestOptions{ 47 | Path: "/api/auth/key", 48 | Method: "POST", 49 | Body: map[string]any{ 50 | "name": name, 51 | }, 52 | Query: map[string]any{ 53 | "subjectReference": subject, 54 | "subjectType": runtimeEnvironmentSubject.String(), 55 | }, 56 | }) 57 | if err != nil { 58 | return nil, fmt.Errorf("failed creating token: %w", err) 59 | } 60 | 61 | return &Token{ 62 | Name: name, 63 | Value: string(res), 64 | }, err 65 | } 66 | 67 | func (t *token) List() ([]Token, error) { 68 | res, err := t.client.RestAPI(context.TODO(), &client.RequestOptions{ 69 | Path: "/api/auth/keys", 70 | Method: "GET", 71 | }) 72 | if err != nil { 73 | return nil, fmt.Errorf("failed listing tokens: %w", err) 74 | } 75 | 76 | result := make([]Token, 0) 77 | return result, json.Unmarshal(res, &result) 78 | } 79 | -------------------------------------------------------------------------------- /codefresh.yaml: -------------------------------------------------------------------------------- 1 | version: '1.0' 2 | 3 | stages: 4 | - Prepare 5 | - Test 6 | - Release 7 | 8 | steps: 9 | main_clone: 10 | stage: Prepare 11 | title: clone repository 12 | type: git-clone 13 | git: cf_github 14 | repo: ${{CF_REPO_OWNER}}/${{CF_REPO_NAME}} 15 | revision: ${{CF_BRANCH}} 16 | 17 | prepare_env_vars: &deps 18 | stage: Prepare 19 | title: prepare-env 20 | image: quay.io/codefresh/golang-ci-helper:1.22 21 | commands: 22 | - cf_export GO111MODULE=on 23 | - cf_export GOCACHE=/codefresh/volume/gocache # change gopath to codefresh shared volume 24 | - cf_export GOPATH=/codefresh/volume/gopath 25 | - cf_export GOSUMDB=off 26 | - cf_export PATH=$PATH:/codefresh/volume/gopath/bin 27 | - cf_export LATEST_VERSION=$(curl --silent -H "Authorization:Bearer ${{GITHUB_TOKEN}}" "https://api.github.com/repos/${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}/releases/latest" | jq -r ".tag_name") 28 | - cf_export VERSION=$(make cur-version) 29 | 30 | compare_versions: 31 | stage: Prepare 32 | title: compare versions 33 | image: alpine/semver 34 | commands: 35 | - semver "${{VERSION}}" -r ">${{LATEST_VERSION}}" || (echo "bump version! current is ${{VERSION}} latest is ${{LATEST_VERSION}}" && exit 1) 36 | when: 37 | branch: 38 | ignore: 39 | - master 40 | 41 | lint: 42 | <<: *deps 43 | stage: Test 44 | title: lint 45 | commands: 46 | - make lint 47 | 48 | test: 49 | <<: *deps 50 | stage: Test 51 | title: unit-tests 52 | commands: 53 | - make test-all 54 | 55 | ReleasingBinaries: 56 | <<: *deps 57 | stage: Release 58 | title: Create release in Github 59 | commands: 60 | - VERSION=$(if [[ ${VERSION:0:1} == "v" ]] ; then echo $VERSION; else echo "v${VERSION}"; fi ) 61 | - gh release create --repo ${{CF_REPO_OWNER}}/${{CF_REPO_NAME}} -t $VERSION -n $VERSION $VERSION 62 | when: 63 | branch: 64 | only: 65 | - master 66 | -------------------------------------------------------------------------------- /pkg/graphql/cluster.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | ClusterAPI interface { 13 | List(ctx context.Context, runtime string) ([]platmodel.Cluster, error) 14 | } 15 | 16 | cluster struct { 17 | client *client.CfClient 18 | } 19 | ) 20 | 21 | func (c *cluster) List(ctx context.Context, runtime string) ([]platmodel.Cluster, error) { 22 | after := "" 23 | clusters := make([]platmodel.Cluster, 0) 24 | for { 25 | clusterSlice, err := c.getClusterSlice(ctx, runtime, after) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | for i := range clusterSlice.Edges { 31 | clusters = append(clusters, *clusterSlice.Edges[i].Node) 32 | } 33 | 34 | if clusterSlice.PageInfo.HasNextPage { 35 | after = *clusterSlice.PageInfo.EndCursor 36 | } else { 37 | break 38 | } 39 | } 40 | 41 | return clusters, nil 42 | } 43 | 44 | func (c *cluster) getClusterSlice(ctx context.Context, runtime string, after string) (*platmodel.ClusterSlice, error) { 45 | query := ` 46 | query clusters($runtime: String, $pagination: SlicePaginationArgs) { 47 | clusters(runtime: $runtime, pagination: $pagination) { 48 | edges { 49 | node { 50 | metadata { 51 | name 52 | runtime 53 | } 54 | server 55 | info { 56 | connectionState { 57 | status 58 | message 59 | } 60 | serverVersion 61 | cacheInfo { 62 | resourcesCount 63 | apisCount 64 | } 65 | } 66 | } 67 | } 68 | pageInfo { 69 | endCursor 70 | hasNextPage 71 | } 72 | } 73 | }` 74 | variables := map[string]any{ 75 | "runtime": runtime, 76 | "pagination": map[string]any{ 77 | "after": after, 78 | }, 79 | } 80 | res, err := client.GraphqlAPI[platmodel.ClusterSlice](ctx, c.client, query, variables) 81 | if err != nil { 82 | return nil, fmt.Errorf("failed getting cluster list: %w", err) 83 | } 84 | 85 | return &res, nil 86 | } 87 | -------------------------------------------------------------------------------- /pkg/appproxy/git-source.go: -------------------------------------------------------------------------------- 1 | package appproxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | apmodel "github.com/codefresh-io/go-sdk/pkg/model/app-proxy" 9 | ) 10 | 11 | type ( 12 | GitSourceAPI interface { 13 | Create(ctx context.Context, opts *apmodel.CreateGitSourceInput) error 14 | Delete(ctx context.Context, appName string) error 15 | Edit(ctx context.Context, opts *apmodel.EditGitSourceInput) error 16 | } 17 | 18 | gitSource struct { 19 | client *client.CfClient 20 | } 21 | ) 22 | 23 | func (c *gitSource) Create(ctx context.Context, args *apmodel.CreateGitSourceInput) error { 24 | query := ` 25 | mutation CreateGitSource($args: CreateGitSourceInput!) { 26 | createGitSource(args: $args) 27 | }` 28 | variables := map[string]any{ 29 | "args": args, 30 | } 31 | _, err := client.GraphqlAPI[client.GraphqlVoidResponse](ctx, c.client, query, variables) 32 | if err != nil { 33 | return fmt.Errorf("failed creating a git-source: %w", err) 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func (c *gitSource) Delete(ctx context.Context, appName string) error { 40 | query := ` 41 | mutation DeleteApplication($args: DeleteApplicationInput!) { 42 | deleteApplication(args: $args) 43 | }` 44 | variables := map[string]any{ 45 | "args": map[string]string{ 46 | "appName": appName, 47 | }, 48 | } 49 | _, err := client.GraphqlAPI[client.GraphqlVoidResponse](ctx, c.client, query, variables) 50 | if err != nil { 51 | return fmt.Errorf("failed deleting a git-source: %w", err) 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func (c *gitSource) Edit(ctx context.Context, args *apmodel.EditGitSourceInput) error { 58 | query := ` 59 | mutation EditGitSource($args: EditGitSourceInput!) { 60 | editGitSource(args: $args) 61 | }` 62 | variables := map[string]any{ 63 | "args": args, 64 | } 65 | _, err := client.GraphqlAPI[client.GraphqlVoidResponse](ctx, c.client, query, variables) 66 | if err != nil { 67 | return fmt.Errorf("failed editing a git-source: %w", err) 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/rest/workflow.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "time" 9 | 10 | "github.com/codefresh-io/go-sdk/pkg/client" 11 | ) 12 | 13 | type ( 14 | WorkflowAPI interface { 15 | Get(string) (*Workflow, error) 16 | WaitForStatus(string, string, time.Duration, time.Duration) error 17 | } 18 | 19 | workflow struct { 20 | codefresh *client.CfClient 21 | } 22 | 23 | Workflow struct { 24 | ID string `json:"id"` 25 | Status string `json:"status"` 26 | UserYamlDescriptor string `json:"userYamlDescriptor"` 27 | Progress string `json:"progress"` 28 | Created time.Time `json:"created"` 29 | Updated time.Time `json:"updated"` 30 | Finished time.Time `json:"finished"` 31 | } 32 | ) 33 | 34 | func (w *workflow) Get(id string) (*Workflow, error) { 35 | res, err := w.codefresh.RestAPI(context.TODO(), &client.RequestOptions{ 36 | Method: "GET", 37 | Path: fmt.Sprintf("/api/builds/%s", id), 38 | }) 39 | if err != nil { 40 | return nil, fmt.Errorf("failed getting a workflow: %w", err) 41 | } 42 | 43 | result := &Workflow{} 44 | return result, json.Unmarshal(res, result) 45 | } 46 | 47 | func (w *workflow) WaitForStatus(id string, status string, interval time.Duration, timeout time.Duration) error { 48 | return waitFor(interval, timeout, func() (bool, error) { 49 | res, err := w.Get(id) 50 | if err != nil { 51 | return false, err 52 | } 53 | 54 | if res.Status == status { 55 | return true, nil 56 | } 57 | 58 | return false, nil 59 | }) 60 | } 61 | 62 | func waitFor(interval time.Duration, timeout time.Duration, execution func() (bool, error)) error { 63 | t := time.After(timeout) 64 | ticker := time.NewTicker(interval) 65 | // Keep trying until we're timed out or got a result or got an error 66 | for { 67 | select { 68 | // Got a timeout! fail with a timeout error 69 | case <-t: 70 | return errors.New("timed out") 71 | case <-ticker.C: 72 | ok, err := execution() 73 | if err != nil { 74 | return err 75 | } 76 | 77 | if ok { 78 | return nil 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pkg/codefresh/codefresh.go: -------------------------------------------------------------------------------- 1 | package codefresh 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | ap "github.com/codefresh-io/go-sdk/pkg/appproxy" 9 | "github.com/codefresh-io/go-sdk/pkg/client" 10 | gql "github.com/codefresh-io/go-sdk/pkg/graphql" 11 | "github.com/codefresh-io/go-sdk/pkg/rest" 12 | ) 13 | 14 | type ( 15 | Codefresh interface { 16 | AppProxy(ctx context.Context, runtime string, insecure bool) (ap.AppProxyAPI, error) 17 | GraphQL() gql.GraphQLAPI 18 | Rest() rest.RestAPI 19 | InternalClient() *client.CfClient 20 | HttpClient() HttpClient 21 | } 22 | 23 | HttpClient interface { 24 | NativeRestAPI(ctx context.Context, opt *client.RequestOptions) (*http.Response, error) 25 | } 26 | 27 | ClientOptions struct { 28 | Token string 29 | Host string 30 | Client *http.Client 31 | GraphqlPath string 32 | } 33 | 34 | codefresh struct { 35 | client *client.CfClient 36 | } 37 | ) 38 | 39 | func New(opt *ClientOptions) Codefresh { 40 | client := client.NewCfClient(opt.Host, opt.Token, opt.GraphqlPath, opt.Client) 41 | return &codefresh{client: client} 42 | } 43 | 44 | func (c *codefresh) AppProxy(ctx context.Context, runtime string, insecure bool) (ap.AppProxyAPI, error) { 45 | rt, err := c.GraphQL().Runtime().Get(ctx, runtime) 46 | if err != nil { 47 | return nil, fmt.Errorf("failed to create app-proxy client for runtime %s: %w", runtime, err) 48 | } 49 | 50 | var host string 51 | if rt.InternalIngressHost != nil && *rt.InternalIngressHost != "" { 52 | host = *rt.InternalIngressHost 53 | } else if rt.IngressHost != nil && *rt.IngressHost != "" { 54 | host = *rt.IngressHost 55 | } else { 56 | return nil, fmt.Errorf("failed to create app-proxy client for runtime %s: runtime does not have ingressHost configured", runtime) 57 | } 58 | 59 | apClient := c.client.AppProxyClient(host, insecure) 60 | return ap.NewAppProxyClient(apClient), nil 61 | } 62 | 63 | func (c *codefresh) GraphQL() gql.GraphQLAPI { 64 | return gql.NewGraphQLClient(c.client) 65 | } 66 | 67 | func (c *codefresh) Rest() rest.RestAPI { 68 | return rest.NewRestClient(c.client) 69 | } 70 | 71 | func (c *codefresh) InternalClient() *client.CfClient { 72 | return c.client 73 | } 74 | 75 | func (c *codefresh) HttpClient() HttpClient { 76 | return c.client 77 | } 78 | -------------------------------------------------------------------------------- /pkg/graphql/pipeline.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | PipelineAPI interface { 13 | Get(ctx context.Context, name, namespace, runtime string) (*platmodel.Pipeline, error) 14 | List(ctx context.Context, filterArgs platmodel.PipelinesFilterArgs) ([]platmodel.Pipeline, error) 15 | } 16 | 17 | pipeline struct { 18 | client *client.CfClient 19 | } 20 | ) 21 | 22 | func (c *pipeline) Get(ctx context.Context, name, namespace, runtime string) (*platmodel.Pipeline, error) { 23 | query := ` 24 | query Pipeline( 25 | $runtime: String! 26 | $name: String! 27 | $namespace: String 28 | ) { 29 | pipeline(name: $name, namespace: $namespace, runtime: $runtime) { 30 | metadata { 31 | name 32 | namespace 33 | runtime 34 | } 35 | self { 36 | healthStatus 37 | syncStatus 38 | version 39 | } 40 | projects 41 | spec { 42 | trigger 43 | } 44 | } 45 | }` 46 | variables := map[string]any{ 47 | "runtime": runtime, 48 | "name": name, 49 | "namespace": namespace, 50 | } 51 | res, err := client.GraphqlAPI[*platmodel.Pipeline](ctx, c.client, query, variables) 52 | if err != nil { 53 | return nil, fmt.Errorf("failed getting a pipeline: %w", err) 54 | } 55 | 56 | return res, nil 57 | } 58 | 59 | func (c *pipeline) List(ctx context.Context, filterArgs platmodel.PipelinesFilterArgs) ([]platmodel.Pipeline, error) { 60 | query := ` 61 | query Pipelines($filters: PipelinesFilterArgs) { 62 | pipelines(filters: $filters) { 63 | edges { 64 | node { 65 | metadata { 66 | name 67 | namespace 68 | runtime 69 | } 70 | self { 71 | healthStatus 72 | syncStatus 73 | version 74 | } 75 | projects 76 | spec { 77 | trigger 78 | } 79 | } 80 | } 81 | } 82 | }` 83 | variables := map[string]any{ 84 | "filters": filterArgs, 85 | } 86 | res, err := client.GraphqlAPI[platmodel.PipelineSlice](ctx, c.client, query, variables) 87 | if err != nil { 88 | return nil, fmt.Errorf("failed getting pipeline list: %w", err) 89 | } 90 | 91 | pipelines := make([]platmodel.Pipeline, len(res.Edges)) 92 | for i := range res.Edges { 93 | pipelines[i] = *res.Edges[i].Node 94 | } 95 | 96 | return pipelines, nil 97 | } 98 | -------------------------------------------------------------------------------- /pkg/rest/argo_test.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/mocks" 8 | "github.com/codefresh-io/go-sdk/pkg/utils" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_argo_GetIntegrations(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | want []IntegrationPayload 16 | wantErr string 17 | beforeFn func(rt *mocks.MockRoundTripper) 18 | }{} 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | cfClient, mockRT := utils.NewMockClient(t) 22 | if tt.beforeFn != nil { 23 | tt.beforeFn(mockRT) 24 | } 25 | 26 | a := &argo{ 27 | client: cfClient, 28 | } 29 | got, err := a.GetIntegrations() 30 | if err != nil || tt.wantErr != "" { 31 | assert.EqualError(t, err, tt.wantErr) 32 | return 33 | } 34 | 35 | if !reflect.DeepEqual(got, tt.want) { 36 | t.Errorf("argo.GetIntegrations() = %v, want %v", got, tt.want) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | func Test_argo_GetIntegrationByName(t *testing.T) { 43 | tests := []struct { 44 | name string 45 | integrationName string 46 | want *IntegrationPayload 47 | wantErr string 48 | beforeFn func(rt *mocks.MockRoundTripper) 49 | }{} 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | cfClient, mockRT := utils.NewMockClient(t) 53 | if tt.beforeFn != nil { 54 | tt.beforeFn(mockRT) 55 | } 56 | 57 | a := &argo{ 58 | client: cfClient, 59 | } 60 | got, err := a.GetIntegrationByName(tt.integrationName) 61 | if err != nil || tt.wantErr != "" { 62 | assert.EqualError(t, err, tt.wantErr) 63 | return 64 | } 65 | 66 | if !reflect.DeepEqual(got, tt.want) { 67 | t.Errorf("argo.GetIntegrationByName() = %v, want %v", got, tt.want) 68 | } 69 | }) 70 | } 71 | } 72 | 73 | func Test_argo_DeleteIntegrationByName(t *testing.T) { 74 | tests := []struct { 75 | name string 76 | integrationName string 77 | wantErr string 78 | beforeFn func(rt *mocks.MockRoundTripper) 79 | }{} 80 | for _, tt := range tests { 81 | t.Run(tt.name, func(t *testing.T) { 82 | cfClient, mockRT := utils.NewMockClient(t) 83 | if tt.beforeFn != nil { 84 | tt.beforeFn(mockRT) 85 | } 86 | 87 | a := &argo{ 88 | client: cfClient, 89 | } 90 | if err := a.DeleteIntegrationByName(tt.integrationName); err != nil || tt.wantErr != "" { 91 | assert.EqualError(t, err, tt.wantErr) 92 | } 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /pkg/rest/context_test.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/mocks" 8 | "github.com/codefresh-io/go-sdk/pkg/utils" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_v1Context_GetDefaultGitContext(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | want *ContextPayload 16 | wantErr string 17 | beforeFn func(rt *mocks.MockRoundTripper) 18 | }{} 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | cfClient, mockRT := utils.NewMockClient(t) 22 | if tt.beforeFn != nil { 23 | tt.beforeFn(mockRT) 24 | } 25 | 26 | c := v1Context{ 27 | client: cfClient, 28 | } 29 | got, err := c.GetDefaultGitContext() 30 | if err != nil || tt.wantErr != "" { 31 | assert.EqualError(t, err, tt.wantErr) 32 | return 33 | } 34 | 35 | if !reflect.DeepEqual(got, tt.want) { 36 | t.Errorf("v1Context.GetDefaultGitContext() = %v, want %v", got, tt.want) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | func Test_v1Context_GetGitContextByName(t *testing.T) { 43 | tests := []struct { 44 | name string 45 | contextName string 46 | want *ContextPayload 47 | wantErr string 48 | beforeFn func(rt *mocks.MockRoundTripper) 49 | }{} 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | cfClient, mockRT := utils.NewMockClient(t) 53 | if tt.beforeFn != nil { 54 | tt.beforeFn(mockRT) 55 | } 56 | 57 | c := v1Context{ 58 | client: cfClient, 59 | } 60 | got, err := c.GetGitContextByName(tt.contextName) 61 | if err != nil || tt.wantErr != "" { 62 | assert.EqualError(t, err, tt.wantErr) 63 | return 64 | } 65 | 66 | if !reflect.DeepEqual(got, tt.want) { 67 | t.Errorf("v1Context.GetGitContextByName() = %v, want %v", got, tt.want) 68 | } 69 | }) 70 | } 71 | } 72 | 73 | func Test_v1Context_GetGitContexts(t *testing.T) { 74 | tests := []struct { 75 | name string 76 | want []ContextPayload 77 | wantErr string 78 | beforeFn func(rt *mocks.MockRoundTripper) 79 | }{} 80 | for _, tt := range tests { 81 | t.Run(tt.name, func(t *testing.T) { 82 | cfClient, mockRT := utils.NewMockClient(t) 83 | if tt.beforeFn != nil { 84 | tt.beforeFn(mockRT) 85 | } 86 | 87 | c := v1Context{ 88 | client: cfClient, 89 | } 90 | got, err := c.GetGitContexts() 91 | if err != nil || tt.wantErr != "" { 92 | assert.EqualError(t, err, tt.wantErr) 93 | return 94 | } 95 | 96 | if !reflect.DeepEqual(got, tt.want) { 97 | t.Errorf("v1Context.GetGitContexts() = %v, want %v", got, tt.want) 98 | } 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /pkg/rest/context.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/client" 9 | ) 10 | 11 | type ( 12 | ContextAPI interface { 13 | GetDefaultGitContext() (*ContextPayload, error) 14 | GetGitContextByName(name string) (*ContextPayload, error) 15 | GetGitContexts() ([]ContextPayload, error) 16 | } 17 | 18 | v1Context struct { 19 | client *client.CfClient 20 | } 21 | 22 | ContextPayload struct { 23 | Metadata struct { 24 | Name string `json:"name"` 25 | } 26 | Spec struct { 27 | Type string `json:"type"` 28 | Data struct { 29 | Auth struct { 30 | Type string `json:"type"` 31 | Username string `json:"username"` 32 | Password string `json:"password"` 33 | ApiHost string `json:"apiHost"` 34 | // for gitlab 35 | ApiURL string `json:"apiURL"` 36 | ApiPathPrefix string `json:"apiPathPrefix"` 37 | SshPrivateKey string `json:"sshPrivateKey"` 38 | AppId string `json:"appId"` 39 | InstallationId string `json:"installationId"` 40 | PrivateKey string `json:"privateKey"` 41 | } `json:"auth"` 42 | } `json:"data"` 43 | } `json:"spec"` 44 | } 45 | 46 | GitContextsQs struct { 47 | Type []string `url:"type"` 48 | Decrypt string `url:"decrypt"` 49 | } 50 | ) 51 | 52 | func (c v1Context) GetDefaultGitContext() (*ContextPayload, error) { 53 | res, err := c.client.RestAPI(context.TODO(), &client.RequestOptions{ 54 | Method: "GET", 55 | Path: "/api/contexts/git/default", 56 | }) 57 | if err != nil { 58 | return nil, fmt.Errorf("failed getting default git context: %w", err) 59 | } 60 | 61 | result := &ContextPayload{} 62 | return result, json.Unmarshal(res, result) 63 | } 64 | 65 | func (c v1Context) GetGitContextByName(name string) (*ContextPayload, error) { 66 | res, err := c.client.RestAPI(context.TODO(), &client.RequestOptions{ 67 | Method: "GET", 68 | Path: "/api/contexts/" + name, 69 | Query: map[string]any{ 70 | "decrypt": "true", 71 | }, 72 | }) 73 | if err != nil { 74 | return nil, fmt.Errorf("failed getting git context by name: %w", err) 75 | } 76 | 77 | result := &ContextPayload{} 78 | return result, json.Unmarshal(res, result) 79 | } 80 | 81 | func (c v1Context) GetGitContexts() ([]ContextPayload, error) { 82 | res, err := c.client.RestAPI(context.TODO(), &client.RequestOptions{ 83 | Method: "GET", 84 | Path: "/api/contexts", 85 | Query: map[string]any{ 86 | "type": []string{"git.github", "git.gitlab", "git.github-app"}, 87 | "decrypt": "true", 88 | }, 89 | }) 90 | if err != nil { 91 | return nil, fmt.Errorf("failed getting git context list: %w", err) 92 | } 93 | 94 | result := make([]ContextPayload, 0) 95 | return result, json.Unmarshal(res, &result) 96 | } 97 | -------------------------------------------------------------------------------- /pkg/graphql/workflow.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | WorkflowAPI interface { 13 | Get(ctx context.Context, uid string) (*platmodel.Workflow, error) 14 | List(ctx context.Context, filterArgs platmodel.WorkflowsFilterArgs) ([]platmodel.Workflow, error) 15 | } 16 | 17 | workflow struct { 18 | client *client.CfClient 19 | } 20 | ) 21 | 22 | func (c *workflow) Get(ctx context.Context, uid string) (*platmodel.Workflow, error) { 23 | query := ` 24 | query Workflow($uid: String!) { 25 | workflow(uid: $uid) { 26 | metadata { 27 | uid 28 | name 29 | namespace 30 | runtime 31 | } 32 | projects 33 | spec { 34 | entrypoint 35 | templates { 36 | name 37 | } 38 | workflowTemplateRef { 39 | name 40 | namespace 41 | } 42 | } 43 | status { 44 | phase 45 | progress 46 | nodes { 47 | type 48 | name 49 | } 50 | } 51 | pipeline { 52 | metadata { 53 | name 54 | namespace 55 | } 56 | } 57 | } 58 | }` 59 | variables := map[string]any{ 60 | "uid": uid, 61 | } 62 | res, err := client.GraphqlAPI[*platmodel.Workflow](ctx, c.client, query, variables) 63 | if err != nil { 64 | return nil, fmt.Errorf("failed getting a workflow: %w", err) 65 | } 66 | 67 | return res, nil 68 | } 69 | 70 | func (c *workflow) List(ctx context.Context, filterArgs platmodel.WorkflowsFilterArgs) ([]platmodel.Workflow, error) { 71 | query := ` 72 | query Workflows($filters: WorkflowsFilterArgs) { 73 | workflows(filters: $filters) { 74 | edges { 75 | node { 76 | metadata { 77 | uid 78 | name 79 | namespace 80 | runtime 81 | } 82 | projects 83 | spec { 84 | entrypoint 85 | templates { 86 | name 87 | } 88 | workflowTemplateRef { 89 | name 90 | namespace 91 | } 92 | } 93 | status { 94 | phase 95 | progress 96 | nodes { 97 | type 98 | name 99 | } 100 | } 101 | pipeline { 102 | metadata { 103 | name 104 | namespace 105 | } 106 | } 107 | } 108 | } 109 | } 110 | }` 111 | variables := map[string]any{ 112 | "filters": filterArgs, 113 | } 114 | res, err := client.GraphqlAPI[platmodel.WorkflowSlice](ctx, c.client, query, variables) 115 | if err != nil { 116 | return nil, fmt.Errorf("failed getting workflow list: %w", err) 117 | } 118 | 119 | workflows := make([]platmodel.Workflow, len(res.Edges)) 120 | for i := range res.Edges { 121 | workflows[i] = *res.Edges[i].Node 122 | } 123 | 124 | return workflows, nil 125 | } 126 | -------------------------------------------------------------------------------- /pkg/mocks/http_mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.42.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | http "net/http" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | ) 10 | 11 | // MockRoundTripper is an autogenerated mock type for the RoundTripper type 12 | type MockRoundTripper struct { 13 | mock.Mock 14 | } 15 | 16 | type MockRoundTripper_Expecter struct { 17 | mock *mock.Mock 18 | } 19 | 20 | func (_m *MockRoundTripper) EXPECT() *MockRoundTripper_Expecter { 21 | return &MockRoundTripper_Expecter{mock: &_m.Mock} 22 | } 23 | 24 | // RoundTrip provides a mock function with given fields: _a0 25 | func (_m *MockRoundTripper) RoundTrip(_a0 *http.Request) (*http.Response, error) { 26 | ret := _m.Called(_a0) 27 | 28 | if len(ret) == 0 { 29 | panic("no return value specified for RoundTrip") 30 | } 31 | 32 | var r0 *http.Response 33 | var r1 error 34 | if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok { 35 | return rf(_a0) 36 | } 37 | if rf, ok := ret.Get(0).(func(*http.Request) *http.Response); ok { 38 | r0 = rf(_a0) 39 | } else { 40 | if ret.Get(0) != nil { 41 | r0 = ret.Get(0).(*http.Response) 42 | } 43 | } 44 | 45 | if rf, ok := ret.Get(1).(func(*http.Request) error); ok { 46 | r1 = rf(_a0) 47 | } else { 48 | r1 = ret.Error(1) 49 | } 50 | 51 | return r0, r1 52 | } 53 | 54 | // MockRoundTripper_RoundTrip_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RoundTrip' 55 | type MockRoundTripper_RoundTrip_Call struct { 56 | *mock.Call 57 | } 58 | 59 | // RoundTrip is a helper method to define mock.On call 60 | // - _a0 *http.Request 61 | func (_e *MockRoundTripper_Expecter) RoundTrip(_a0 interface{}) *MockRoundTripper_RoundTrip_Call { 62 | return &MockRoundTripper_RoundTrip_Call{Call: _e.mock.On("RoundTrip", _a0)} 63 | } 64 | 65 | func (_c *MockRoundTripper_RoundTrip_Call) Run(run func(_a0 *http.Request)) *MockRoundTripper_RoundTrip_Call { 66 | _c.Call.Run(func(args mock.Arguments) { 67 | run(args[0].(*http.Request)) 68 | }) 69 | return _c 70 | } 71 | 72 | func (_c *MockRoundTripper_RoundTrip_Call) Return(_a0 *http.Response, _a1 error) *MockRoundTripper_RoundTrip_Call { 73 | _c.Call.Return(_a0, _a1) 74 | return _c 75 | } 76 | 77 | func (_c *MockRoundTripper_RoundTrip_Call) RunAndReturn(run func(*http.Request) (*http.Response, error)) *MockRoundTripper_RoundTrip_Call { 78 | _c.Call.Return(run) 79 | return _c 80 | } 81 | 82 | // NewMockRoundTripper creates a new instance of MockRoundTripper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 83 | // The first argument is typically a *testing.T value. 84 | func NewMockRoundTripper(t interface { 85 | mock.TestingT 86 | Cleanup(func()) 87 | }) *MockRoundTripper { 88 | mock := &MockRoundTripper{} 89 | mock.Mock.Test(t) 90 | 91 | t.Cleanup(func() { mock.AssertExpectations(t) }) 92 | 93 | return mock 94 | } 95 | -------------------------------------------------------------------------------- /pkg/rest/pipeline.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | "time" 10 | 11 | "github.com/codefresh-io/go-sdk/pkg/client" 12 | ) 13 | 14 | type ( 15 | PipelineAPI interface { 16 | List(query map[string]string) ([]Pipeline, error) 17 | Run(string, *RunOptions) (string, error) 18 | } 19 | 20 | pipeline struct { 21 | client *client.CfClient 22 | } 23 | 24 | PipelineMetadata struct { 25 | Name string `json:"name"` 26 | IsPublic bool `json:"isPublic"` 27 | Labels struct { 28 | Tags []string `json:"tags"` 29 | } `json:"labels"` 30 | Deprecate struct { 31 | ApplicationPort string `json:"applicationPort"` 32 | RepoPipeline bool `json:"repoPipeline"` 33 | } `json:"deprecate"` 34 | OriginalYamlString string `json:"originalYamlString"` 35 | AccountID string `json:"accountId"` 36 | CreatedAt time.Time `json:"created_at"` 37 | UpdatedAt time.Time `json:"updated_at"` 38 | Project string `json:"project"` 39 | ID string `json:"id"` 40 | } 41 | 42 | PipelineSpec struct { 43 | Triggers []struct { 44 | Type string `json:"type"` 45 | Repo string `json:"repo"` 46 | Events []string `json:"events"` 47 | Provider string `json:"provider"` 48 | Context string `json:"context"` 49 | } `json:"triggers"` 50 | Contexts []any `json:"contexts"` 51 | Variables []struct { 52 | Key string `json:"key"` 53 | Value string `json:"value"` 54 | } `json:"variables"` 55 | Steps map[string]any `json:"steps"` 56 | Stages []any `json:"stages"` 57 | Mode string `json:"mode"` 58 | } 59 | 60 | Pipeline struct { 61 | Metadata PipelineMetadata `json:"metadata"` 62 | Spec PipelineSpec `json:"spec"` 63 | } 64 | 65 | getPipelineResponse struct { 66 | Docs []Pipeline `json:"docs"` 67 | Count int `json:"count"` 68 | } 69 | 70 | RunOptions struct { 71 | Branch string 72 | Variables map[string]string 73 | } 74 | ) 75 | 76 | // Get - returns pipelines from API 77 | func (p *pipeline) List(query map[string]string) ([]Pipeline, error) { 78 | anyQuery := map[string]any{} 79 | for k, v := range query { 80 | anyQuery[k] = v 81 | } 82 | 83 | res, err := p.client.RestAPI(context.TODO(), &client.RequestOptions{ 84 | Method: "GET", 85 | Path: "/api/pipelines", 86 | Query: anyQuery, 87 | }) 88 | if err != nil { 89 | return nil, fmt.Errorf("failed getting pipeline list: %w", err) 90 | } 91 | 92 | result := &getPipelineResponse{} 93 | return result.Docs, json.Unmarshal(res, result) 94 | } 95 | 96 | func (p *pipeline) Run(name string, options *RunOptions) (string, error) { 97 | if options == nil { 98 | options = &RunOptions{} 99 | } 100 | 101 | res, err := p.client.RestAPI(context.TODO(), &client.RequestOptions{ 102 | Method: "POST", 103 | Path: fmt.Sprintf("/api/pipelines/run/%s", url.PathEscape(name)), 104 | Body: map[string]any{ 105 | "branch": options.Branch, 106 | "variables": options.Variables, 107 | }, 108 | }) 109 | if err != nil { 110 | return "", fmt.Errorf("failed running pipeline: %w", err) 111 | } 112 | 113 | return strings.Replace(string(res), "\"", "", -1), nil 114 | } 115 | -------------------------------------------------------------------------------- /pkg/model/platform/unmarshal.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "encoding/json" 4 | 5 | // This file includes unmarshal overrides for types that can 6 | // not be simply unmarshal otherwise. 7 | // 8 | // For example, if one of the fields on the type you are trying 9 | // to unmarshal to is an interface, you need to provide custom 10 | // behavior that decides to which concrete type it should be 11 | // umarshaled into. 12 | 13 | // Application entity 14 | type ApplicationJSON struct { 15 | // Object metadata 16 | Metadata *ObjectMeta `json:"metadata"` 17 | // Errors 18 | Errors []SyncError `json:"errors"` 19 | // Entities referencing this entity 20 | ReferencedBy []BaseEntity `json:"referencedBy"` 21 | // Entities referenced by this enitity 22 | References []BaseEntity `json:"references"` 23 | // Relations between parents and child applications in tree 24 | AppsRelations *AppsRelations `json:"appsRelations"` 25 | // Version of the entity (generation) 26 | Version *int `json:"version"` 27 | // Is this the latest version of this entity 28 | Latest *bool `json:"latest"` 29 | // Entity source 30 | Source *GitopsEntitySource `json:"source"` 31 | // Sync status 32 | SyncStatus SyncStatus `json:"syncStatus"` 33 | // Health status 34 | HealthStatus *HealthStatus `json:"healthStatus"` 35 | // Health message 36 | HealthMessage *string `json:"healthMessage"` 37 | // Desired manifest 38 | DesiredManifest *string `json:"desiredManifest"` 39 | // Actual manifest 40 | ActualManifest *string `json:"actualManifest"` 41 | // Projects 42 | Projects []string `json:"projects"` 43 | // Updated At 44 | UpdatedAt *string `json:"updatedAt"` 45 | // Path 46 | Path *string `json:"path"` 47 | // RepoURL 48 | RepoURL *string `json:"repoURL"` 49 | // Number of resources 50 | Size *int `json:"size"` 51 | // Revision 52 | Revision *string `json:"revision"` 53 | // Status 54 | Status *ArgoCDApplicationStatus `json:"status"` 55 | // Favorites 56 | Favorites []string `json:"favorites"` 57 | // Argo CD application destination config 58 | Destination *ArgoCDApplicationDestination `json:"destination"` 59 | } 60 | 61 | func (a *Application) UnmarshalJSON(data []byte) error { 62 | aj := ApplicationJSON{} 63 | if err := json.Unmarshal(data, &aj); err != nil { 64 | return err 65 | } 66 | 67 | errs := make([]Error, len(aj.Errors)) 68 | for i := range aj.Errors { 69 | errs[i] = aj.Errors[i] 70 | } 71 | 72 | a.Metadata = aj.Metadata 73 | a.Errors = errs 74 | a.ReferencedBy = aj.ReferencedBy 75 | a.References = aj.References 76 | a.AppsRelations = aj.AppsRelations 77 | a.Version = aj.Version 78 | a.Latest = aj.Latest 79 | a.Source = aj.Source 80 | a.SyncStatus = aj.SyncStatus 81 | a.HealthStatus = aj.HealthStatus 82 | a.HealthMessage = aj.HealthMessage 83 | a.DesiredManifest = aj.DesiredManifest 84 | a.ActualManifest = aj.ActualManifest 85 | a.Projects = aj.Projects 86 | a.UpdatedAt = aj.UpdatedAt 87 | a.Path = aj.Path 88 | a.RepoURL = aj.RepoURL 89 | a.Size = aj.Size 90 | a.Revision = aj.Revision 91 | a.Status = aj.Status 92 | a.Favorites = aj.Favorites 93 | a.Destination = aj.Destination 94 | 95 | return nil 96 | } 97 | 98 | func (u *User) IsActiveAccountAdmin() bool { 99 | for _, id := range u.ActiveAccount.Admins { 100 | if id == u.ID { 101 | return true 102 | } 103 | } 104 | 105 | return false 106 | } 107 | -------------------------------------------------------------------------------- /pkg/rest/argo.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/codefresh-io/go-sdk/pkg/client" 9 | ) 10 | 11 | type ( 12 | ArgoAPI interface { 13 | CreateIntegration(integration IntegrationPayloadData) error 14 | DeleteIntegrationByName(name string) error 15 | GetIntegrationByName(name string) (*IntegrationPayload, error) 16 | GetIntegrations() ([]IntegrationPayload, error) 17 | HeartBeat(error string, version string, integration string) error 18 | SendResources(kind string, items any, amount int, integration string) error 19 | UpdateIntegration(name string, integration IntegrationPayloadData) error 20 | } 21 | 22 | argo struct { 23 | client *client.CfClient 24 | } 25 | 26 | IntegrationItem struct { 27 | Amount int `json:"amount"` 28 | } 29 | 30 | IntegrationPayloadData struct { 31 | Name string `json:"name"` 32 | Url string `json:"url"` 33 | Clusters IntegrationItem `json:"clusters"` 34 | Applications IntegrationItem `json:"applications"` 35 | Repositories IntegrationItem `json:"repositories"` 36 | Username *string `json:"username"` 37 | Password *string `json:"password"` 38 | Token *string `json:"token"` 39 | ClusterName *string `json:"clusterName"` 40 | ServerVersion *string `json:"serverVersion"` 41 | Provider *string `json:"provider"` 42 | } 43 | 44 | IntegrationPayload struct { 45 | Type string `json:"type"` 46 | Data IntegrationPayloadData `json:"data"` 47 | } 48 | 49 | Heartbeat struct { 50 | Error string `json:"error"` 51 | AgentVersion string `json:"agentVersion"` 52 | } 53 | AgentState struct { 54 | Kind string `json:"type"` 55 | Items any `json:"items"` 56 | } 57 | ) 58 | 59 | func (a *argo) CreateIntegration(integration IntegrationPayloadData) error { 60 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 61 | Path: "/api/argo", 62 | Method: "POST", 63 | Body: &IntegrationPayload{ 64 | Type: "argo-cd", 65 | Data: integration, 66 | }, 67 | }) 68 | if err != nil { 69 | return fmt.Errorf("failed creating an argo integration: %w", err) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func (a *argo) DeleteIntegrationByName(name string) error { 76 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 77 | Method: "DELETE", 78 | Path: fmt.Sprintf("/api/argo/%s", name), 79 | }) 80 | if err != nil { 81 | return fmt.Errorf("failed deleting an argo integration: %w", err) 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func (a *argo) GetIntegrationByName(name string) (*IntegrationPayload, error) { 88 | res, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 89 | Method: "GET", 90 | Path: fmt.Sprintf("/api/argo/%s", name), 91 | }) 92 | if err != nil { 93 | return nil, fmt.Errorf("failed getting an argo integration: %w", err) 94 | } 95 | 96 | result := &IntegrationPayload{} 97 | return result, json.Unmarshal(res, result) 98 | } 99 | 100 | func (a *argo) GetIntegrations() ([]IntegrationPayload, error) { 101 | res, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 102 | Method: "GET", 103 | Path: "/api/argo", 104 | }) 105 | if err != nil { 106 | return nil, fmt.Errorf("failed getting argo integration list: %w", err) 107 | } 108 | 109 | result := make([]IntegrationPayload, 0) 110 | return result, json.Unmarshal(res, &result) 111 | } 112 | 113 | func (a *argo) HeartBeat(error string, version string, integration string) error { 114 | var body = Heartbeat{} 115 | if error != "" { 116 | body.Error = error 117 | } 118 | 119 | if version != "" { 120 | body.AgentVersion = version 121 | } 122 | 123 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 124 | Method: "POST", 125 | Path: fmt.Sprintf("/api/argo-agent/%s/heartbeat", integration), 126 | Body: body, 127 | }) 128 | if err != nil { 129 | return fmt.Errorf("failed sending argo heartbeat: %w", err) 130 | } 131 | 132 | return nil 133 | } 134 | 135 | func (a *argo) SendResources(kind string, items any, amount int, integration string) error { 136 | if items == nil { 137 | return nil 138 | } 139 | 140 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 141 | Method: "POST", 142 | Path: fmt.Sprintf("/api/argo-agent/%s", integration), 143 | Body: &AgentState{Kind: kind, Items: items}, 144 | }) 145 | if err != nil { 146 | return fmt.Errorf("failed sending argo resources: %w", err) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func (a *argo) UpdateIntegration(name string, integration IntegrationPayloadData) error { 153 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 154 | Method: "PUT", 155 | Path: fmt.Sprintf("/api/argo/%s", name), 156 | Body: &IntegrationPayload{ 157 | Type: "argo-cd", 158 | Data: integration, 159 | }, 160 | }) 161 | if err != nil { 162 | return fmt.Errorf("failed updating an argo integration: %w", err) 163 | } 164 | 165 | return nil 166 | } 167 | -------------------------------------------------------------------------------- /pkg/appproxy/git_integration.go: -------------------------------------------------------------------------------- 1 | package appproxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | apmodel "github.com/codefresh-io/go-sdk/pkg/model/app-proxy" 9 | ) 10 | 11 | type ( 12 | GitIntegrationAPI interface { 13 | Add(ctx context.Context, args *apmodel.AddGitIntegrationArgs) (*apmodel.GitIntegration, error) 14 | Deregister(ctx context.Context, name *string) (*apmodel.GitIntegration, error) 15 | Edit(ctx context.Context, args *apmodel.EditGitIntegrationArgs) (*apmodel.GitIntegration, error) 16 | Get(ctx context.Context, name *string) (*apmodel.GitIntegration, error) 17 | List(ctx context.Context) ([]apmodel.GitIntegration, error) 18 | Register(ctx context.Context, args *apmodel.RegisterToGitIntegrationArgs) (*apmodel.GitIntegration, error) 19 | Remove(ctx context.Context, name string) error 20 | } 21 | 22 | gitIntegration struct { 23 | client *client.CfClient 24 | } 25 | ) 26 | 27 | func (c *gitIntegration) Add(ctx context.Context, args *apmodel.AddGitIntegrationArgs) (*apmodel.GitIntegration, error) { 28 | query := ` 29 | mutation AddGitIntegration($args: AddGitIntegrationArgs!) { 30 | addGitIntegration(args: $args) { 31 | name 32 | sharingPolicy 33 | provider 34 | apiUrl 35 | registeredUsers 36 | } 37 | }` 38 | variables := map[string]any{ 39 | "args": args, 40 | } 41 | res, err := client.GraphqlAPI[apmodel.GitIntegration](ctx, c.client, query, variables) 42 | if err != nil { 43 | return nil, fmt.Errorf("failed adding a git integration: %w", err) 44 | } 45 | 46 | return &res, nil 47 | } 48 | 49 | func (c *gitIntegration) Deregister(ctx context.Context, name *string) (*apmodel.GitIntegration, error) { 50 | query := ` 51 | mutation DeregisterFromGitIntegration($name: String) { 52 | deregisterFromGitIntegration(name: $name) { 53 | name 54 | sharingPolicy 55 | provider 56 | apiUrl 57 | registeredUsers 58 | } 59 | }` 60 | variables := map[string]any{ 61 | "name": name, 62 | } 63 | res, err := client.GraphqlAPI[apmodel.GitIntegration](ctx, c.client, query, variables) 64 | if err != nil { 65 | return nil, fmt.Errorf("failed deregistering a git integration: %w", err) 66 | } 67 | 68 | return &res, nil 69 | } 70 | 71 | func (c *gitIntegration) Edit(ctx context.Context, args *apmodel.EditGitIntegrationArgs) (*apmodel.GitIntegration, error) { 72 | query := ` 73 | mutation EditGitIntegration($args: EditGitIntegrationArgs!) { 74 | editGitIntegration(args: $args) { 75 | name 76 | sharingPolicy 77 | provider 78 | apiUrl 79 | registeredUsers 80 | } 81 | }` 82 | variables := map[string]any{ 83 | "args": args, 84 | } 85 | res, err := client.GraphqlAPI[apmodel.GitIntegration](ctx, c.client, query, variables) 86 | if err != nil { 87 | return nil, fmt.Errorf("failed editing a git integration: %w", err) 88 | } 89 | 90 | return &res, nil 91 | } 92 | 93 | func (c *gitIntegration) Get(ctx context.Context, name *string) (*apmodel.GitIntegration, error) { 94 | query := ` 95 | query GitIntegration($name: String) { 96 | gitIntegration(name: $name) { 97 | name 98 | sharingPolicy 99 | provider 100 | apiUrl 101 | registeredUsers 102 | } 103 | }` 104 | variables := map[string]any{ 105 | "name": name, 106 | } 107 | res, err := client.GraphqlAPI[*apmodel.GitIntegration](ctx, c.client, query, variables) 108 | if err != nil { 109 | return nil, fmt.Errorf("failed getting a git integration: %w", err) 110 | } 111 | 112 | return res, nil 113 | } 114 | 115 | func (c *gitIntegration) List(ctx context.Context) ([]apmodel.GitIntegration, error) { 116 | query := ` 117 | query GitIntegrations { 118 | gitIntegrations { 119 | name 120 | sharingPolicy 121 | provider 122 | apiUrl 123 | users { 124 | userId 125 | } 126 | } 127 | }` 128 | variables := map[string]any{} 129 | res, err := client.GraphqlAPI[[]apmodel.GitIntegration](ctx, c.client, query, variables) 130 | if err != nil { 131 | return nil, fmt.Errorf("failed getting git integration list: %w", err) 132 | } 133 | 134 | return res, nil 135 | } 136 | 137 | func (c *gitIntegration) Register(ctx context.Context, args *apmodel.RegisterToGitIntegrationArgs) (*apmodel.GitIntegration, error) { 138 | query := ` 139 | mutation RegisterToGitIntegration($args: RegisterToGitIntegrationArgs!) { 140 | registerToGitIntegration(args: $args) { 141 | name 142 | sharingPolicy 143 | provider 144 | apiUrl 145 | registeredUsers 146 | } 147 | }` 148 | variables := map[string]any{ 149 | "args": args, 150 | } 151 | res, err := client.GraphqlAPI[apmodel.GitIntegration](ctx, c.client, query, variables) 152 | if err != nil { 153 | return nil, fmt.Errorf("failed registering a git integration: %w", err) 154 | } 155 | 156 | return &res, nil 157 | } 158 | 159 | func (c *gitIntegration) Remove(ctx context.Context, name string) error { 160 | query := ` 161 | mutation RemoveGitIntegration($name: String!) { 162 | removeGitIntegration(name: $name) 163 | }` 164 | variables := map[string]any{ 165 | "name": name, 166 | } 167 | _, err := client.GraphqlAPI[client.GraphqlVoidResponse](ctx, c.client, query, variables) 168 | if err != nil { 169 | return fmt.Errorf("failed removing a git integration: %w", err) 170 | } 171 | 172 | return nil 173 | } 174 | -------------------------------------------------------------------------------- /pkg/graphql/runtime.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/codefresh-io/go-sdk/pkg/client" 8 | platmodel "github.com/codefresh-io/go-sdk/pkg/model/platform" 9 | ) 10 | 11 | type ( 12 | RuntimeAPI interface { 13 | Create(ctx context.Context, opts *platmodel.RuntimeInstallationArgs) (*platmodel.RuntimeCreationResponse, error) 14 | Delete(ctx context.Context, runtimeName string) (int, error) 15 | DeleteManaged(ctx context.Context, runtimeName string) (int, error) 16 | Get(ctx context.Context, name string) (*platmodel.Runtime, error) 17 | List(ctx context.Context) ([]platmodel.Runtime, error) 18 | MigrateRuntime(ctx context.Context, runtimeName string) error 19 | ReportErrors(ctx context.Context, opts *platmodel.ReportRuntimeErrorsArgs) (int, error) 20 | SetSharedConfigRepo(ctx context.Context, suggestedSharedConfigRepo string) (string, error) 21 | } 22 | 23 | runtime struct { 24 | client *client.CfClient 25 | } 26 | ) 27 | 28 | func (c *runtime) Create(ctx context.Context, opts *platmodel.RuntimeInstallationArgs) (*platmodel.RuntimeCreationResponse, error) { 29 | query := ` 30 | mutation CreateRuntime($installationArgs: RuntimeInstallationArgs!) { 31 | createRuntime(installationArgs: $installationArgs) { 32 | name 33 | newAccessToken 34 | } 35 | }` 36 | variables := map[string]any{ 37 | "installationArgs": opts, 38 | } 39 | res, err := client.GraphqlAPI[platmodel.RuntimeCreationResponse](ctx, c.client, query, variables) 40 | if err != nil { 41 | return nil, fmt.Errorf("failed creating a runtime: %w", err) 42 | } 43 | 44 | return &res, nil 45 | } 46 | 47 | func (c *runtime) Delete(ctx context.Context, runtimeName string) (int, error) { 48 | query := ` 49 | mutation DeleteRuntime($name: String!) { 50 | deleteRuntime(name: $name) 51 | }` 52 | variables := map[string]any{ 53 | "name": runtimeName, 54 | } 55 | res, err := client.GraphqlAPI[int](ctx, c.client, query, variables) 56 | if err != nil { 57 | return 0, fmt.Errorf("failed deleting a runtime: %w", err) 58 | } 59 | 60 | return res, nil 61 | } 62 | 63 | func (c *runtime) DeleteManaged(ctx context.Context, runtimeName string) (int, error) { 64 | query := ` 65 | mutation DeleteManagedRuntime( 66 | $name: String! 67 | ) { 68 | deleteManagedRuntime(name: $name) 69 | }` 70 | variables := map[string]any{ 71 | "name": runtimeName, 72 | } 73 | res, err := client.GraphqlAPI[int](ctx, c.client, query, variables) 74 | if err != nil { 75 | return 0, fmt.Errorf("failed deleting a hosted runtime: %w", err) 76 | } 77 | 78 | return res, nil 79 | } 80 | 81 | func (c *runtime) Get(ctx context.Context, name string) (*platmodel.Runtime, error) { 82 | query := ` 83 | query GetRuntime($name: String!) { 84 | runtime(name: $name) { 85 | metadata { 86 | name 87 | namespace 88 | } 89 | self { 90 | syncStatus 91 | healthMessage 92 | healthStatus 93 | } 94 | syncStatus 95 | healthStatus 96 | healthMessage 97 | cluster 98 | managed 99 | isRemoteClusterConnected 100 | ingressHost 101 | internalIngressHost 102 | ingressClass 103 | ingressController 104 | runtimeVersion 105 | installationStatus 106 | installationType 107 | repo 108 | managedClustersNum 109 | gitProvider 110 | accessMode 111 | } 112 | }` 113 | variables := map[string]any{ 114 | "name": name, 115 | } 116 | res, err := client.GraphqlAPI[*platmodel.Runtime](ctx, c.client, query, variables) 117 | if err != nil { 118 | return nil, fmt.Errorf("failed getting a runtime: %w", err) 119 | } 120 | 121 | if res == nil { 122 | return nil, fmt.Errorf("runtime '%s' does not exist", name) 123 | } 124 | 125 | return res, nil 126 | } 127 | 128 | func (c *runtime) List(ctx context.Context) ([]platmodel.Runtime, error) { 129 | query := ` 130 | query Runtimes { 131 | runtimes { 132 | edges { 133 | node { 134 | metadata { 135 | name 136 | namespace 137 | } 138 | self { 139 | syncStatus 140 | healthMessage 141 | healthStatus 142 | } 143 | syncStatus 144 | healthMessage 145 | healthStatus 146 | managed 147 | cluster 148 | ingressHost 149 | runtimeVersion 150 | installationStatus 151 | installationType 152 | } 153 | } 154 | } 155 | }` 156 | variables := map[string]any{} 157 | res, err := client.GraphqlAPI[platmodel.RuntimeSlice](ctx, c.client, query, variables) 158 | if err != nil { 159 | return nil, fmt.Errorf("failed getting runtime list: %w", err) 160 | } 161 | 162 | runtimes := make([]platmodel.Runtime, len(res.Edges)) 163 | for i := range res.Edges { 164 | runtimes[i] = *res.Edges[i].Node 165 | } 166 | 167 | return runtimes, nil 168 | } 169 | 170 | func (c *runtime) MigrateRuntime(ctx context.Context, runtimeName string) error { 171 | query := ` 172 | mutation migrateRuntime($runtimeName: String!) { 173 | migrateRuntime(runtimeName: $runtimeName) 174 | }` 175 | variables := map[string]any{ 176 | "runtimeName": runtimeName, 177 | } 178 | _, err := client.GraphqlAPI[client.GraphqlBaseResponse](ctx, c.client, query, variables) 179 | if err != nil { 180 | return fmt.Errorf("failed migrating a runtime: %w", err) 181 | } 182 | 183 | return nil 184 | } 185 | 186 | func (c *runtime) ReportErrors(ctx context.Context, opts *platmodel.ReportRuntimeErrorsArgs) (int, error) { 187 | query := ` 188 | mutation ReportRuntimeErrors($reportErrorsArgs: ReportRuntimeErrorsArgs!) { 189 | reportRuntimeErrors(reportErrorsArgs: $reportErrorsArgs) 190 | }` 191 | variables := map[string]any{ 192 | "reportErrorsArgs": opts, 193 | } 194 | res, err := client.GraphqlAPI[int](ctx, c.client, query, variables) 195 | if err != nil { 196 | return 0, fmt.Errorf("failed reporting errors: %w", err) 197 | } 198 | 199 | return res, nil 200 | } 201 | 202 | func (c *runtime) SetSharedConfigRepo(ctx context.Context, suggestedSharedConfigRepo string) (string, error) { 203 | query := ` 204 | mutation SuggestIscRepo($suggestedSharedConfigRepo: String!) { 205 | suggestIscRepo(suggestedSharedConfigRepo: $suggestedSharedConfigRepo) 206 | }` 207 | variables := map[string]any{ 208 | "suggestedSharedConfigRepo": suggestedSharedConfigRepo, 209 | } 210 | res, err := client.GraphqlAPI[string](ctx, c.client, query, variables) 211 | if err != nil { 212 | return "", fmt.Errorf("failed suggesting ISC repo: %w", err) 213 | } 214 | 215 | return res, nil 216 | } 217 | -------------------------------------------------------------------------------- /pkg/rest/gitops.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/codefresh-io/go-sdk/pkg/client" 10 | ) 11 | 12 | type ( 13 | GitopsAPI interface { 14 | CreateEnvironment(name string, project string, application string, integration string) error 15 | DeleteEnvironment(name string) error 16 | GetEnvironments() ([]CFEnvironment, error) 17 | SendApplicationResources(resources *ApplicationResources) error 18 | SendEnvironment(environment Environment) (map[string]any, error) 19 | SendEvent(name string, props map[string]string) error 20 | } 21 | 22 | gitops struct { 23 | client *client.CfClient 24 | } 25 | CodefreshEvent struct { 26 | Event string `json:"event"` 27 | Props map[string]string `json:"props"` 28 | } 29 | 30 | MongoCFEnvWrapper struct { 31 | Docs []CFEnvironment `json:"docs"` 32 | } 33 | 34 | CFEnvironment struct { 35 | Metadata struct { 36 | Name string `json:"name"` 37 | } `json:"metadata"` 38 | Spec struct { 39 | Type string `json:"type"` 40 | Application string `json:"application"` 41 | } `json:"spec"` 42 | } 43 | 44 | EnvironmentMetadata struct { 45 | Name string `json:"name"` 46 | } 47 | 48 | EnvironmentSpec struct { 49 | Type string `json:"type"` 50 | Context string `json:"context"` 51 | Project string `json:"project"` 52 | Application string `json:"application"` 53 | } 54 | 55 | EnvironmentPayload struct { 56 | Version string `json:"version"` 57 | Metadata EnvironmentMetadata `json:"metadata"` 58 | Spec EnvironmentSpec `json:"spec"` 59 | } 60 | 61 | SyncPolicy struct { 62 | AutoSync bool `json:"autoSync"` 63 | } 64 | 65 | Commit struct { 66 | Time *time.Time `json:"time,omitempty"` 67 | Message *string `json:"message"` 68 | Avatar *string `json:"avatar"` 69 | } 70 | 71 | EnvironmentActivityRS struct { 72 | From ReplicaState `json:"from"` 73 | To ReplicaState `json:"to"` 74 | } 75 | 76 | ReplicaState struct { 77 | Current int64 `json:"current"` 78 | Desired int64 `json:"desired"` 79 | } 80 | 81 | Annotation struct { 82 | Key string `json:"key"` 83 | Value string `json:"value"` 84 | } 85 | 86 | GitopsUser struct { 87 | Name string `json:"name"` 88 | Avatar string `json:"avatar"` 89 | } 90 | 91 | Gitops struct { 92 | Comitters []GitopsUser `json:"comitters"` 93 | Prs []Annotation `json:"prs"` 94 | Issues []Annotation `json:"issues"` 95 | } 96 | 97 | Environment struct { 98 | Gitops Gitops `json:"gitops"` 99 | FinishedAt string `json:"finishedAt"` 100 | HealthStatus string `json:"healthStatus"` 101 | SyncStatus string `json:"status"` 102 | HistoryId int64 `json:"historyId"` 103 | SyncRevision string `json:"revision"` 104 | Name string `json:"name"` 105 | Activities []EnvironmentActivity `json:"activities"` 106 | Resources any `json:"resources"` 107 | RepoUrl string `json:"repoUrl"` 108 | Commit Commit `json:"commit"` 109 | SyncPolicy SyncPolicy `json:"syncPolicy"` 110 | Date string `json:"date"` 111 | ParentApp string `json:"parentApp"` 112 | Namespace string `json:"namespace"` 113 | Server string `json:"server"` 114 | Context *string `json:"context"` 115 | } 116 | 117 | EnvironmentActivity struct { 118 | Name string `json:"name"` 119 | TargetImages []string `json:"targetImages"` 120 | Status string `json:"status"` 121 | LiveImages []string `json:"liveImages"` 122 | ReplicaSet EnvironmentActivityRS `json:"replicaSet"` 123 | } 124 | 125 | ApplicationResources struct { 126 | Name string `json:"name,omitempty"` 127 | HistoryId int64 `json:"historyId"` 128 | Revision string `json:"revision,omitempty"` 129 | Resources any `json:"resources"` 130 | Context *string `json:"context"` 131 | } 132 | ) 133 | 134 | func (a *gitops) CreateEnvironment(name string, project string, application string, integration string) error { 135 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 136 | Method: "POST", 137 | Path: "/api/environments-v2", 138 | Body: &EnvironmentPayload{ 139 | Version: "1.0", 140 | Metadata: EnvironmentMetadata{ 141 | Name: name, 142 | }, 143 | Spec: EnvironmentSpec{ 144 | Type: "argo", 145 | Context: integration, 146 | Project: project, 147 | Application: application, 148 | }, 149 | }, 150 | }) 151 | return err 152 | } 153 | 154 | func (a *gitops) DeleteEnvironment(name string) error { 155 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 156 | Method: "DELETE", 157 | Path: fmt.Sprintf("/api/environments-v2/%s", name), 158 | }) 159 | if err != nil { 160 | return fmt.Errorf("failed deleting an environment: %w", err) 161 | } 162 | 163 | return nil 164 | } 165 | 166 | func (a *gitops) GetEnvironments() ([]CFEnvironment, error) { 167 | res, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 168 | Method: "GET", 169 | Path: "/api/environments-v2?plain=true&isEnvironment=false", 170 | }) 171 | if err != nil { 172 | return nil, fmt.Errorf("failed getting environment list: %w", err) 173 | } 174 | 175 | result := &MongoCFEnvWrapper{} 176 | return result.Docs, json.Unmarshal(res, result) 177 | } 178 | 179 | func (a *gitops) SendApplicationResources(resources *ApplicationResources) error { 180 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 181 | Method: "POST", 182 | Path: "/api/gitops/resources", 183 | Body: &resources, 184 | }) 185 | if err != nil { 186 | return fmt.Errorf("failed sending application resources: %w", err) 187 | } 188 | 189 | return nil 190 | } 191 | 192 | func (a *gitops) SendEnvironment(environment Environment) (map[string]any, error) { 193 | res, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{Method: "POST", Path: "/api/environments-v2/argo/events", Body: environment}) 194 | if err != nil { 195 | return nil, fmt.Errorf("failed sending an environment: %w", err) 196 | } 197 | 198 | result := make(map[string]any) 199 | return result, json.Unmarshal(res, &result) 200 | } 201 | 202 | func (a *gitops) SendEvent(name string, props map[string]string) error { 203 | _, err := a.client.RestAPI(context.TODO(), &client.RequestOptions{ 204 | Method: "POST", 205 | Path: "/api/gitops/system/events", 206 | Body: CodefreshEvent{Event: name, Props: props}, 207 | }) 208 | if err != nil { 209 | return fmt.Errorf("failed sending event: %w", err) 210 | } 211 | 212 | return nil 213 | } 214 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/url" 12 | "strings" 13 | ) 14 | 15 | type ( 16 | GraphqlResponse interface { 17 | HasErrors() bool 18 | } 19 | 20 | ClientOptions struct { 21 | Token string 22 | Host string 23 | Client *http.Client 24 | GraphqlPath string 25 | } 26 | 27 | CfClient struct { 28 | token string 29 | baseUrl *url.URL 30 | gqlUrl *url.URL 31 | client *http.Client 32 | } 33 | 34 | RequestOptions struct { 35 | Path string 36 | Method string 37 | Query map[string]any 38 | Body any 39 | } 40 | 41 | ApiError struct { 42 | status string 43 | statusCode int 44 | body string 45 | } 46 | 47 | GraphqlError struct { 48 | Message string 49 | Extensions any 50 | } 51 | 52 | GraphqlErrorResponse struct { 53 | Errors []GraphqlError 54 | concatenatedErrors string 55 | } 56 | 57 | GraphqlBaseResponse struct { 58 | Errors []GraphqlError 59 | } 60 | 61 | GraphqlVoidResponse struct{} 62 | ) 63 | 64 | func (e *ApiError) Error() string { 65 | return fmt.Sprintf("API error: %s: %s", e.status, e.body) 66 | } 67 | 68 | func NewCfClient(host, token, graphqlPath string, httpClient *http.Client) *CfClient { 69 | baseUrl, err := url.Parse(host) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | if graphqlPath == "" { 75 | graphqlPath = "/2.0/api/graphql" 76 | } 77 | 78 | gqlUrl := baseUrl.JoinPath(graphqlPath) 79 | if httpClient == nil { 80 | httpClient = &http.Client{} 81 | } 82 | 83 | return &CfClient{ 84 | baseUrl: baseUrl, 85 | token: token, 86 | gqlUrl: gqlUrl, 87 | client: httpClient, 88 | } 89 | } 90 | 91 | func (c *CfClient) AppProxyClient(host string, insecure bool) *CfClient { 92 | httpClient := &http.Client{} 93 | httpClient.Timeout = c.client.Timeout 94 | if insecure { 95 | customTransport := http.DefaultTransport.(*http.Transport).Clone() 96 | customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 97 | httpClient.Transport = customTransport 98 | } 99 | 100 | return NewCfClient(host, c.token, "/app-proxy/api/graphql", httpClient) 101 | } 102 | 103 | func (c *CfClient) RestAPI(ctx context.Context, opt *RequestOptions) ([]byte, error) { 104 | res, err := c.apiCall(ctx, c.baseUrl, opt) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | res, err = c.wrapResponse(res) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | defer res.Body.Close() 115 | bytes, err := io.ReadAll(res.Body) 116 | if err != nil { 117 | return nil, fmt.Errorf("failed to read response Body: %w", err) 118 | } 119 | 120 | return bytes, nil 121 | } 122 | func (c *CfClient) NativeRestAPI(ctx context.Context, opt *RequestOptions) (*http.Response, error) { 123 | return c.apiCall(ctx, c.baseUrl, opt) 124 | } 125 | 126 | func (c *CfClient) GraphqlAPI(ctx context.Context, query string, variables any, result any) error { 127 | body := map[string]any{ 128 | "query": query, 129 | "variables": variables, 130 | } 131 | res, err := c.apiCall(ctx, c.gqlUrl, &RequestOptions{ 132 | Method: "POST", 133 | Body: body, 134 | }) 135 | if err != nil { 136 | return err 137 | } 138 | 139 | res, err = c.wrapResponse(res) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | defer res.Body.Close() 145 | bytes, err := io.ReadAll(res.Body) 146 | if err != nil { 147 | return fmt.Errorf("failed to read response Body: %w", err) 148 | } 149 | 150 | err = json.Unmarshal(bytes, result) 151 | if err != nil { 152 | return fmt.Errorf("failed to unmarshal response Body: %w", err) 153 | } 154 | 155 | return nil 156 | } 157 | 158 | func (c *CfClient) apiCall(ctx context.Context, baseUrl *url.URL, opt *RequestOptions) (*http.Response, error) { 159 | var body []byte 160 | finalUrl := baseUrl.JoinPath(opt.Path) 161 | q := finalUrl.Query() 162 | err := setQueryParams(q, opt.Query) 163 | if err != nil { 164 | return nil, err 165 | } 166 | 167 | finalUrl.RawQuery = q.Encode() 168 | if opt.Body != nil { 169 | body, _ = json.Marshal(opt.Body) 170 | } 171 | 172 | method := http.MethodGet 173 | if opt.Method != "" { 174 | method = opt.Method 175 | } 176 | 177 | request, err := http.NewRequestWithContext(ctx, method, finalUrl.String(), bytes.NewBuffer(body)) 178 | if err != nil { 179 | return nil, fmt.Errorf("failed to create request: %w", err) 180 | } 181 | 182 | request.Header.Set("Authorization", c.token) 183 | request.Header.Set("Content-Type", "application/json") 184 | request.Header.Set("origin", c.baseUrl.Host) 185 | 186 | res, err := c.client.Do(request) 187 | if err != nil { 188 | return nil, fmt.Errorf("failed to send request: %w", err) 189 | } 190 | 191 | return res, nil 192 | } 193 | 194 | func (c *CfClient) wrapResponse(res *http.Response) (*http.Response, error) { 195 | if res.StatusCode >= http.StatusBadRequest { 196 | defer res.Body.Close() 197 | bytes, err := io.ReadAll(res.Body) 198 | body := string(bytes) 199 | if err != nil { 200 | body = fmt.Sprintf("failed to read response Body: %s", err.Error()) 201 | } 202 | 203 | return nil, &ApiError{ 204 | status: res.Status, 205 | statusCode: res.StatusCode, 206 | body: body, 207 | } 208 | } 209 | return res, nil 210 | } 211 | 212 | func (e GraphqlErrorResponse) Error() string { 213 | if e.concatenatedErrors != "" { 214 | return e.concatenatedErrors 215 | } 216 | 217 | var sb strings.Builder 218 | for _, err := range e.Errors { 219 | sb.WriteString(fmt.Sprintln(err.Message)) 220 | } 221 | 222 | e.concatenatedErrors = sb.String() 223 | return e.concatenatedErrors 224 | } 225 | 226 | func (e GraphqlBaseResponse) HasErrors() bool { 227 | return len(e.Errors) > 0 228 | } 229 | 230 | func GraphqlAPI[T any](ctx context.Context, client *CfClient, query string, variables any) (T, error) { 231 | var ( 232 | wrapper struct { 233 | Data map[string]T `json:"data,omitempty"` 234 | Errors []GraphqlError `json:"errors,omitempty"` 235 | } 236 | result T 237 | ) 238 | 239 | err := client.GraphqlAPI(ctx, query, variables, &wrapper) 240 | if err != nil { 241 | return result, err 242 | } 243 | 244 | // we assume there is only a single data key in the result (= a single query in the request) 245 | for k := range wrapper.Data { 246 | result = wrapper.Data[k] 247 | break 248 | } 249 | 250 | if wrapper.Errors != nil { 251 | err = &GraphqlErrorResponse{Errors: wrapper.Errors} 252 | } 253 | 254 | return result, err 255 | } 256 | 257 | func setQueryParams(q url.Values, query map[string]any) error { 258 | for k, v := range query { 259 | if str, ok := v.(string); ok { 260 | q.Set(k, str) 261 | } else if arr, ok := v.([]string); ok { 262 | for _, item := range arr { 263 | q.Add(k, item) 264 | } 265 | } else { 266 | return fmt.Errorf("invalid query param type: %T", v) 267 | } 268 | } 269 | 270 | return nil 271 | } 272 | -------------------------------------------------------------------------------- /pkg/rest/runtime-enrionment.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/codefresh-io/go-sdk/pkg/client" 11 | ) 12 | 13 | const ( 14 | KubernetesRunnerType = "kubernetes" 15 | ) 16 | 17 | type ( 18 | // RuntimeEnvironmentAPI declers Codefresh runtime environment API 19 | RuntimeEnvironmentAPI interface { 20 | Create(*CreateRuntimeOptions) (*RuntimeEnvironment, error) 21 | Default(string) (bool, error) 22 | Delete(string) (bool, error) 23 | Get(string) (*RuntimeEnvironment, error) 24 | List() ([]RuntimeEnvironment, error) 25 | SignCertificate(*SignCertificatesOptions) ([]byte, error) 26 | Validate(*ValidateRuntimeOptions) error 27 | } 28 | 29 | runtimeEnvironment struct { 30 | client *client.CfClient 31 | } 32 | 33 | RuntimeEnvironment struct { 34 | Version int `json:"version"` 35 | Metadata RuntimeMetadata `json:"metadata"` 36 | Extends []string `json:"extends"` 37 | Description string `json:"description"` 38 | AccountID string `json:"accountId"` 39 | RuntimeScheduler RuntimeScheduler `json:"runtimeScheduler"` 40 | DockerDaemonScheduler DockerDaemonScheduler `json:"dockerDaemonScheduler"` 41 | Status struct { 42 | Message string `json:"message"` 43 | UpdatedAt time.Time `json:"updated_at"` 44 | } `json:"status"` 45 | } 46 | 47 | RuntimeScheduler struct { 48 | Cluster struct { 49 | ClusterProvider struct { 50 | AccountID string `json:"accountId"` 51 | Selector string `json:"selector"` 52 | } `json:"clusterProvider"` 53 | Namespace string `json:"namespace"` 54 | } `json:"cluster"` 55 | UserAccess bool `json:"userAccess"` 56 | Pvcs struct { 57 | Dind struct { 58 | StorageClassName string `yaml:"storageClassName"` 59 | } `yaml:"dind"` 60 | } `yaml:"pvcs"` 61 | } 62 | 63 | DockerDaemonScheduler struct { 64 | Cluster struct { 65 | ClusterProvider struct { 66 | AccountID string `json:"accountId"` 67 | Selector string `json:"selector"` 68 | } `json:"clusterProvider"` 69 | Namespace string `json:"namespace"` 70 | } `json:"cluster"` 71 | UserAccess bool `json:"userAccess"` 72 | } 73 | 74 | RuntimeMetadata struct { 75 | Agent bool `json:"agent"` 76 | Name string `json:"name"` 77 | ChangedBy string `json:"changedBy"` 78 | CreationTime string `json:"creationTime"` 79 | } 80 | 81 | CreateRuntimeOptions struct { 82 | Cluster string 83 | Namespace string 84 | HasAgent bool 85 | StorageClass string 86 | RunnerType string 87 | DockerDaemonParams string 88 | NodeSelector map[string]string 89 | Annotations map[string]string 90 | } 91 | 92 | ValidateRuntimeOptions struct { 93 | Cluster string 94 | Namespace string 95 | } 96 | 97 | SignCertificatesOptions struct { 98 | AltName string 99 | CSR string 100 | } 101 | 102 | CreateResponse struct { 103 | Name string 104 | } 105 | ) 106 | 107 | // Create - create Runtime-Environment 108 | func (r *runtimeEnvironment) Create(opt *CreateRuntimeOptions) (*RuntimeEnvironment, error) { 109 | body := map[string]any{ 110 | "clusterName": opt.Cluster, 111 | "namespace": opt.Namespace, 112 | "storageClassName": opt.StorageClass, 113 | "runnerType": opt.RunnerType, 114 | "dockerDaemonParams": opt.DockerDaemonParams, 115 | "nodeSelector": opt.NodeSelector, 116 | "annotations": opt.Annotations, 117 | } 118 | if opt.HasAgent { 119 | body["agent"] = true 120 | } 121 | 122 | _, err := r.client.RestAPI(context.TODO(), &client.RequestOptions{ 123 | Method: "POST", 124 | Path: "/api/custom_clusters/register", 125 | Body: body, 126 | }) 127 | if err != nil { 128 | return nil, fmt.Errorf("failed creating runtime environment: %w", err) 129 | } 130 | 131 | re := &RuntimeEnvironment{ 132 | Metadata: RuntimeMetadata{ 133 | Name: fmt.Sprintf("%s/%s", opt.Cluster, opt.Namespace), 134 | }, 135 | } 136 | 137 | return re, nil 138 | } 139 | 140 | func (r *runtimeEnvironment) Default(name string) (bool, error) { 141 | _, err := r.client.RestAPI(context.TODO(), &client.RequestOptions{ 142 | Method: "PUT", 143 | Path: fmt.Sprintf("/api/runtime-environments/default/%s", url.PathEscape(name)), 144 | }) 145 | if err != nil { 146 | return false, fmt.Errorf("failed setting default runtime environment: %w", err) 147 | } 148 | 149 | return true, nil 150 | } 151 | 152 | func (r *runtimeEnvironment) Delete(name string) (bool, error) { 153 | _, err := r.client.RestAPI(context.TODO(), &client.RequestOptions{ 154 | Method: "DELETE", 155 | Path: fmt.Sprintf("/api/runtime-environments/%s", url.PathEscape(name)), 156 | }) 157 | if err != nil { 158 | return false, fmt.Errorf("failed deleting runtime environment: %w", err) 159 | } 160 | 161 | return true, nil 162 | } 163 | 164 | func (r *runtimeEnvironment) Get(name string) (*RuntimeEnvironment, error) { 165 | res, err := r.client.RestAPI(context.TODO(), &client.RequestOptions{ 166 | Method: "GET", 167 | Path: fmt.Sprintf("/api/runtime-environments/%s", url.PathEscape(name)), 168 | Query: map[string]any{ 169 | "extend": "false", 170 | }, 171 | }) 172 | if err != nil { 173 | return nil, fmt.Errorf("failed getting runtime environment: %w", err) 174 | } 175 | 176 | result := &RuntimeEnvironment{} 177 | return result, json.Unmarshal(res, result) 178 | } 179 | 180 | func (r *runtimeEnvironment) List() ([]RuntimeEnvironment, error) { 181 | res, err := r.client.RestAPI(context.TODO(), &client.RequestOptions{ 182 | Path: "/api/runtime-environments", 183 | Method: "GET", 184 | }) 185 | if err != nil { 186 | return nil, fmt.Errorf("failed getting runtime environment list: %w", err) 187 | } 188 | 189 | result := make([]RuntimeEnvironment, 0) 190 | return result, json.Unmarshal(res, &result) 191 | } 192 | 193 | func (r *runtimeEnvironment) SignCertificate(opt *SignCertificatesOptions) ([]byte, error) { 194 | res, err := r.client.RestAPI(context.TODO(), &client.RequestOptions{ 195 | Path: "/api/custom_clusters/signServerCerts", 196 | Method: "POST", 197 | Body: map[string]any{ 198 | "reqSubjectAltName": opt.AltName, 199 | "csr": opt.CSR, 200 | }, 201 | }) 202 | if err != nil { 203 | return nil, fmt.Errorf("failed signing certificate: %w", err) 204 | } 205 | 206 | return res, err 207 | } 208 | 209 | func (r *runtimeEnvironment) Validate(opt *ValidateRuntimeOptions) error { 210 | _, err := r.client.RestAPI(context.TODO(), &client.RequestOptions{ 211 | Path: "/api/custom_clusters/validate", 212 | Method: "POST", 213 | Body: map[string]any{ 214 | "clusterName": opt.Cluster, 215 | "namespace": opt.Namespace, 216 | }, 217 | }) 218 | if err != nil { 219 | return fmt.Errorf("failed validating runtime environment: %w", err) 220 | } 221 | 222 | return nil 223 | } 224 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /pkg/model/abac/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "strconv" 9 | ) 10 | 11 | // AbacAllActionsValidatedEntity 12 | type AbacAllActionsValidatedEntityAction struct { 13 | Action AbacActionNames `json:"action"` 14 | Enabled bool `json:"enabled"` 15 | } 16 | 17 | // AbacAllActionsValidationResult 18 | type AbacAllActionsValidationResult struct { 19 | Entity string `json:"entity"` 20 | ValidationResult []*AbacAllActionsValidatedEntityAction `json:"validationResult"` 21 | } 22 | 23 | // AbacAttribute 24 | type AbacAttribute struct { 25 | // Name 26 | Name AbacAttributeNames `json:"name"` 27 | // Key 28 | Key *string `json:"key,omitempty"` 29 | // Value 30 | Value string `json:"value"` 31 | } 32 | 33 | // AbacAttributeInput 34 | type AbacAttributeInput struct { 35 | // Name 36 | Name string `json:"name"` 37 | // Key 38 | Key *string `json:"key,omitempty"` 39 | // Value 40 | Value string `json:"value"` 41 | } 42 | 43 | // AbacRulesFilterArgs 44 | type AbacRulesFilterArgs struct { 45 | // Filter by entity types 46 | Type *AbacEntityValues `json:"type,omitempty"` 47 | } 48 | 49 | // AbacValidateAllActionsInput 50 | type AbacValidateAllActionsInput struct { 51 | // AccountId 52 | AccountID string `json:"accountId"` 53 | // Teams 54 | Teams []*string `json:"teams"` 55 | // EntityType 56 | EntityType AbacEntityValues `json:"entityType"` 57 | // Entity 58 | Entities []*string `json:"entities"` 59 | } 60 | 61 | // AbacValidateInput 62 | type AbacValidateInput struct { 63 | // AccountId 64 | AccountID string `json:"accountId"` 65 | // Teams 66 | Teams []*string `json:"teams"` 67 | // Action 68 | Action string `json:"action"` 69 | // EntityType 70 | EntityType AbacEntityValues `json:"entityType"` 71 | // Entity 72 | Entity *string `json:"entity,omitempty"` 73 | } 74 | 75 | // AbacValidationResult 76 | type AbacValidationResult struct { 77 | IsValid bool `json:"isValid"` 78 | Message *string `json:"message,omitempty"` 79 | } 80 | 81 | // CreateRuleInput 82 | type CreateAbacRuleInput struct { 83 | // EntityType 84 | EntityType AbacEntityValues `json:"entityType"` 85 | // Teams 86 | Teams []string `json:"teams"` 87 | // Actions 88 | Actions []string `json:"actions"` 89 | // Tags 90 | Tags []*string `json:"tags,omitempty"` 91 | // Attributes 92 | Attributes []*AbacAttributeInput `json:"attributes,omitempty"` 93 | } 94 | 95 | // EntityAbacRules 96 | type EntityAbacRules struct { 97 | // Id 98 | ID *string `json:"id,omitempty"` 99 | // AccountId 100 | AccountID string `json:"accountId"` 101 | // EntityType 102 | EntityType AbacEntityValues `json:"entityType"` 103 | // Teams 104 | Teams []string `json:"teams"` 105 | // Tags 106 | Tags []*string `json:"tags,omitempty"` 107 | // Actions 108 | Actions []AbacActionNames `json:"actions"` 109 | // Attributes 110 | Attributes []*AbacAttribute `json:"attributes"` 111 | } 112 | 113 | // Mutation root 114 | type Mutation struct { 115 | } 116 | 117 | // Query root 118 | type Query struct { 119 | } 120 | 121 | // UpdateRuleInput 122 | type UpdateAbacRuleInput struct { 123 | // _id 124 | ID string `json:"id"` 125 | // AccountId 126 | AccountID string `json:"accountId"` 127 | // EntityType 128 | EntityType AbacEntityValues `json:"entityType"` 129 | // Teams 130 | Teams []string `json:"teams"` 131 | // Actions 132 | Actions []string `json:"actions"` 133 | // Tags 134 | Tags []*string `json:"tags,omitempty"` 135 | // Attributes 136 | Attributes []*AbacAttributeInput `json:"attributes,omitempty"` 137 | } 138 | 139 | // AbacActionNames 140 | type AbacActionNames string 141 | 142 | const ( 143 | AbacActionNamesDeleteResource AbacActionNames = "DELETE_RESOURCE" 144 | AbacActionNamesExecToPod AbacActionNames = "EXEC_TO_POD" 145 | AbacActionNamesRefresh AbacActionNames = "REFRESH" 146 | AbacActionNamesSync AbacActionNames = "SYNC" 147 | AbacActionNamesTerminateSync AbacActionNames = "TERMINATE_SYNC" 148 | AbacActionNamesView AbacActionNames = "VIEW" 149 | AbacActionNamesViewPodLogs AbacActionNames = "VIEW_POD_LOGS" 150 | AbacActionNamesAppRollback AbacActionNames = "APP_ROLLBACK" 151 | AbacActionNamesRolloutPause AbacActionNames = "ROLLOUT_PAUSE" 152 | AbacActionNamesRolloutResume AbacActionNames = "ROLLOUT_RESUME" 153 | AbacActionNamesRolloutPromoteFull AbacActionNames = "ROLLOUT_PROMOTE_FULL" 154 | AbacActionNamesRolloutSkipCurrentStep AbacActionNames = "ROLLOUT_SKIP_CURRENT_STEP" 155 | AbacActionNamesRolloutAbort AbacActionNames = "ROLLOUT_ABORT" 156 | AbacActionNamesRolloutRetry AbacActionNames = "ROLLOUT_RETRY" 157 | AbacActionNamesAccessArtifacts AbacActionNames = "ACCESS_ARTIFACTS" 158 | AbacActionNamesAccessLogs AbacActionNames = "ACCESS_LOGS" 159 | AbacActionNamesCreate AbacActionNames = "CREATE" 160 | AbacActionNamesRestart AbacActionNames = "RESTART" 161 | AbacActionNamesResubmit AbacActionNames = "RESUBMIT" 162 | AbacActionNamesStop AbacActionNames = "STOP" 163 | AbacActionNamesTerminate AbacActionNames = "TERMINATE" 164 | AbacActionNamesTriggerPromotion AbacActionNames = "TRIGGER_PROMOTION" 165 | AbacActionNamesRetryRelease AbacActionNames = "RETRY_RELEASE" 166 | AbacActionNamesPromoteTo AbacActionNames = "PROMOTE_TO" 167 | ) 168 | 169 | var AllAbacActionNames = []AbacActionNames{ 170 | AbacActionNamesDeleteResource, 171 | AbacActionNamesExecToPod, 172 | AbacActionNamesRefresh, 173 | AbacActionNamesSync, 174 | AbacActionNamesTerminateSync, 175 | AbacActionNamesView, 176 | AbacActionNamesViewPodLogs, 177 | AbacActionNamesAppRollback, 178 | AbacActionNamesRolloutPause, 179 | AbacActionNamesRolloutResume, 180 | AbacActionNamesRolloutPromoteFull, 181 | AbacActionNamesRolloutSkipCurrentStep, 182 | AbacActionNamesRolloutAbort, 183 | AbacActionNamesRolloutRetry, 184 | AbacActionNamesAccessArtifacts, 185 | AbacActionNamesAccessLogs, 186 | AbacActionNamesCreate, 187 | AbacActionNamesRestart, 188 | AbacActionNamesResubmit, 189 | AbacActionNamesStop, 190 | AbacActionNamesTerminate, 191 | AbacActionNamesTriggerPromotion, 192 | AbacActionNamesRetryRelease, 193 | AbacActionNamesPromoteTo, 194 | } 195 | 196 | func (e AbacActionNames) IsValid() bool { 197 | switch e { 198 | case AbacActionNamesDeleteResource, AbacActionNamesExecToPod, AbacActionNamesRefresh, AbacActionNamesSync, AbacActionNamesTerminateSync, AbacActionNamesView, AbacActionNamesViewPodLogs, AbacActionNamesAppRollback, AbacActionNamesRolloutPause, AbacActionNamesRolloutResume, AbacActionNamesRolloutPromoteFull, AbacActionNamesRolloutSkipCurrentStep, AbacActionNamesRolloutAbort, AbacActionNamesRolloutRetry, AbacActionNamesAccessArtifacts, AbacActionNamesAccessLogs, AbacActionNamesCreate, AbacActionNamesRestart, AbacActionNamesResubmit, AbacActionNamesStop, AbacActionNamesTerminate, AbacActionNamesTriggerPromotion, AbacActionNamesRetryRelease, AbacActionNamesPromoteTo: 199 | return true 200 | } 201 | return false 202 | } 203 | 204 | func (e AbacActionNames) String() string { 205 | return string(e) 206 | } 207 | 208 | func (e *AbacActionNames) UnmarshalGQL(v interface{}) error { 209 | str, ok := v.(string) 210 | if !ok { 211 | return fmt.Errorf("enums must be strings") 212 | } 213 | 214 | *e = AbacActionNames(str) 215 | if !e.IsValid() { 216 | return fmt.Errorf("%s is not a valid AbacActionNames", str) 217 | } 218 | return nil 219 | } 220 | 221 | func (e AbacActionNames) MarshalGQL(w io.Writer) { 222 | fmt.Fprint(w, strconv.Quote(e.String())) 223 | } 224 | 225 | // AbacAttributeNames 226 | type AbacAttributeNames string 227 | 228 | const ( 229 | AbacAttributeNamesCluster AbacAttributeNames = "CLUSTER" 230 | AbacAttributeNamesGitSource AbacAttributeNames = "GIT_SOURCE" 231 | AbacAttributeNamesLabel AbacAttributeNames = "LABEL" 232 | AbacAttributeNamesNamespace AbacAttributeNames = "NAMESPACE" 233 | AbacAttributeNamesRuntime AbacAttributeNames = "RUNTIME" 234 | AbacAttributeNamesProductName AbacAttributeNames = "PRODUCT_NAME" 235 | AbacAttributeNamesEnvironmentName AbacAttributeNames = "ENVIRONMENT_NAME" 236 | AbacAttributeNamesEnvironmentKind AbacAttributeNames = "ENVIRONMENT_KIND" 237 | ) 238 | 239 | var AllAbacAttributeNames = []AbacAttributeNames{ 240 | AbacAttributeNamesCluster, 241 | AbacAttributeNamesGitSource, 242 | AbacAttributeNamesLabel, 243 | AbacAttributeNamesNamespace, 244 | AbacAttributeNamesRuntime, 245 | AbacAttributeNamesProductName, 246 | AbacAttributeNamesEnvironmentName, 247 | AbacAttributeNamesEnvironmentKind, 248 | } 249 | 250 | func (e AbacAttributeNames) IsValid() bool { 251 | switch e { 252 | case AbacAttributeNamesCluster, AbacAttributeNamesGitSource, AbacAttributeNamesLabel, AbacAttributeNamesNamespace, AbacAttributeNamesRuntime, AbacAttributeNamesProductName, AbacAttributeNamesEnvironmentName, AbacAttributeNamesEnvironmentKind: 253 | return true 254 | } 255 | return false 256 | } 257 | 258 | func (e AbacAttributeNames) String() string { 259 | return string(e) 260 | } 261 | 262 | func (e *AbacAttributeNames) UnmarshalGQL(v interface{}) error { 263 | str, ok := v.(string) 264 | if !ok { 265 | return fmt.Errorf("enums must be strings") 266 | } 267 | 268 | *e = AbacAttributeNames(str) 269 | if !e.IsValid() { 270 | return fmt.Errorf("%s is not a valid AbacAttributeNames", str) 271 | } 272 | return nil 273 | } 274 | 275 | func (e AbacAttributeNames) MarshalGQL(w io.Writer) { 276 | fmt.Fprint(w, strconv.Quote(e.String())) 277 | } 278 | 279 | // Values from AbacEntityValues enum 280 | type AbacEntityValues string 281 | 282 | const ( 283 | AbacEntityValuesClusters AbacEntityValues = "clusters" 284 | AbacEntityValuesExecutionContext AbacEntityValues = "executionContext" 285 | AbacEntityValuesGitContexts AbacEntityValues = "gitContexts" 286 | AbacEntityValuesGitopsApplications AbacEntityValues = "gitopsApplications" 287 | AbacEntityValuesPromotionFlows AbacEntityValues = "promotionFlows" 288 | AbacEntityValuesProducts AbacEntityValues = "products" 289 | AbacEntityValuesEnvironments AbacEntityValues = "environments" 290 | AbacEntityValuesHelmCharts AbacEntityValues = "helmCharts" 291 | AbacEntityValuesPipelines AbacEntityValues = "pipelines" 292 | AbacEntityValuesProjects AbacEntityValues = "projects" 293 | AbacEntityValuesSharedConfiguration AbacEntityValues = "sharedConfiguration" 294 | AbacEntityValuesWorkflows AbacEntityValues = "workflows" 295 | AbacEntityValuesWorkflowTemplates AbacEntityValues = "workflowTemplates" 296 | ) 297 | 298 | var AllAbacEntityValues = []AbacEntityValues{ 299 | AbacEntityValuesClusters, 300 | AbacEntityValuesExecutionContext, 301 | AbacEntityValuesGitContexts, 302 | AbacEntityValuesGitopsApplications, 303 | AbacEntityValuesPromotionFlows, 304 | AbacEntityValuesProducts, 305 | AbacEntityValuesEnvironments, 306 | AbacEntityValuesHelmCharts, 307 | AbacEntityValuesPipelines, 308 | AbacEntityValuesProjects, 309 | AbacEntityValuesSharedConfiguration, 310 | AbacEntityValuesWorkflows, 311 | AbacEntityValuesWorkflowTemplates, 312 | } 313 | 314 | func (e AbacEntityValues) IsValid() bool { 315 | switch e { 316 | case AbacEntityValuesClusters, AbacEntityValuesExecutionContext, AbacEntityValuesGitContexts, AbacEntityValuesGitopsApplications, AbacEntityValuesPromotionFlows, AbacEntityValuesProducts, AbacEntityValuesEnvironments, AbacEntityValuesHelmCharts, AbacEntityValuesPipelines, AbacEntityValuesProjects, AbacEntityValuesSharedConfiguration, AbacEntityValuesWorkflows, AbacEntityValuesWorkflowTemplates: 317 | return true 318 | } 319 | return false 320 | } 321 | 322 | func (e AbacEntityValues) String() string { 323 | return string(e) 324 | } 325 | 326 | func (e *AbacEntityValues) UnmarshalGQL(v interface{}) error { 327 | str, ok := v.(string) 328 | if !ok { 329 | return fmt.Errorf("enums must be strings") 330 | } 331 | 332 | *e = AbacEntityValues(str) 333 | if !e.IsValid() { 334 | return fmt.Errorf("%s is not a valid AbacEntityValues", str) 335 | } 336 | return nil 337 | } 338 | 339 | func (e AbacEntityValues) MarshalGQL(w io.Writer) { 340 | fmt.Fprint(w, strconv.Quote(e.String())) 341 | } 342 | -------------------------------------------------------------------------------- /pkg/client/client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | "testing" 11 | "time" 12 | 13 | "github.com/codefresh-io/go-sdk/pkg/mocks" 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/mock" 16 | ) 17 | 18 | // newMockClient creates a mock client for testing 19 | func newMockClient(t *testing.T) (*CfClient, *mocks.MockRoundTripper) { 20 | mockRT := mocks.NewMockRoundTripper(t) 21 | cfClient := NewCfClient("https://some.host", "some-token", "grpahql-path", &http.Client{ 22 | Transport: mockRT, 23 | }) 24 | return cfClient, mockRT 25 | } 26 | 27 | func TestNewCfClient(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | host string 31 | token string 32 | graphqlPath string 33 | httpClient *http.Client 34 | wantPanic bool 35 | }{ 36 | { 37 | name: "should create client with default graphql path", 38 | host: "https://api.codefresh.io", 39 | token: "test-token", 40 | graphqlPath: "", 41 | httpClient: nil, 42 | wantPanic: false, 43 | }, 44 | { 45 | name: "should create client with custom graphql path", 46 | host: "https://api.codefresh.io", 47 | token: "test-token", 48 | graphqlPath: "/custom/graphql", 49 | httpClient: &http.Client{Timeout: 30 * time.Second}, 50 | wantPanic: false, 51 | }, 52 | { 53 | name: "should panic with invalid host", 54 | host: "://invalid-url", 55 | token: "test-token", 56 | graphqlPath: "", 57 | httpClient: nil, 58 | wantPanic: true, 59 | }, 60 | } 61 | 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | if tt.wantPanic { 65 | assert.Panics(t, func() { 66 | NewCfClient(tt.host, tt.token, tt.graphqlPath, tt.httpClient) 67 | }) 68 | return 69 | } 70 | 71 | client := NewCfClient(tt.host, tt.token, tt.graphqlPath, tt.httpClient) 72 | assert.NotNil(t, client) 73 | assert.Equal(t, tt.token, client.token) 74 | assert.Equal(t, tt.host, client.baseUrl.String()) 75 | 76 | expectedPath := tt.graphqlPath 77 | if expectedPath == "" { 78 | expectedPath = "/2.0/api/graphql" 79 | } 80 | assert.Contains(t, client.gqlUrl.String(), expectedPath) 81 | 82 | if tt.httpClient == nil { 83 | assert.NotNil(t, client.client) 84 | } else { 85 | assert.Equal(t, tt.httpClient, client.client) 86 | } 87 | }) 88 | } 89 | } 90 | 91 | func TestCfClient_AppProxyClient(t *testing.T) { 92 | originalClient := NewCfClient("https://api.codefresh.io", "test-token", "", &http.Client{ 93 | Timeout: 30 * time.Second, 94 | }) 95 | 96 | tests := []struct { 97 | name string 98 | host string 99 | insecure bool 100 | }{ 101 | { 102 | name: "should create secure app proxy client", 103 | host: "https://app-proxy.codefresh.io", 104 | insecure: false, 105 | }, 106 | { 107 | name: "should create insecure app proxy client", 108 | host: "https://app-proxy.codefresh.io", 109 | insecure: true, 110 | }, 111 | } 112 | 113 | for _, tt := range tests { 114 | t.Run(tt.name, func(t *testing.T) { 115 | proxyClient := originalClient.AppProxyClient(tt.host, tt.insecure) 116 | 117 | assert.NotNil(t, proxyClient) 118 | assert.Equal(t, originalClient.token, proxyClient.token) 119 | assert.Equal(t, tt.host, proxyClient.baseUrl.String()) 120 | assert.Contains(t, proxyClient.gqlUrl.String(), "/app-proxy/api/graphql") 121 | assert.Equal(t, originalClient.client.Timeout, proxyClient.client.Timeout) 122 | 123 | if tt.insecure { 124 | transport := proxyClient.client.Transport.(*http.Transport) 125 | assert.NotNil(t, transport.TLSClientConfig) 126 | assert.True(t, transport.TLSClientConfig.InsecureSkipVerify) 127 | } 128 | }) 129 | } 130 | } 131 | 132 | func TestCfClient_RestAPI(t *testing.T) { 133 | tests := []struct { 134 | name string 135 | opt *RequestOptions 136 | wantErr string 137 | beforeFn func(rt *mocks.MockRoundTripper) 138 | wantData string 139 | }{ 140 | { 141 | name: "should make successful GET request", 142 | opt: &RequestOptions{ 143 | Path: "/api/accounts", 144 | Method: "GET", 145 | }, 146 | beforeFn: func(rt *mocks.MockRoundTripper) { 147 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 148 | assert.Equal(t, "GET", req.Method) 149 | assert.Contains(t, req.URL.Path, "/api/accounts") 150 | assert.Equal(t, "some-token", req.Header.Get("Authorization")) 151 | 152 | bodyReader := io.NopCloser(strings.NewReader(`{"success": true}`)) 153 | return &http.Response{ 154 | StatusCode: 200, 155 | Body: bodyReader, 156 | }, nil 157 | }) 158 | }, 159 | wantData: `{"success": true}`, 160 | }, 161 | { 162 | name: "should make successful POST request with body", 163 | opt: &RequestOptions{ 164 | Path: "/api/accounts", 165 | Method: "POST", 166 | Body: map[string]string{"name": "test"}, 167 | }, 168 | beforeFn: func(rt *mocks.MockRoundTripper) { 169 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 170 | assert.Equal(t, "POST", req.Method) 171 | assert.Equal(t, "application/json", req.Header.Get("Content-Type")) 172 | 173 | bodyReader := io.NopCloser(strings.NewReader(`{"id": "123"}`)) 174 | return &http.Response{ 175 | StatusCode: 201, 176 | Body: bodyReader, 177 | }, nil 178 | }) 179 | }, 180 | wantData: `{"id": "123"}`, 181 | }, 182 | { 183 | name: "should handle query parameters", 184 | opt: &RequestOptions{ 185 | Path: "/api/accounts", 186 | Query: map[string]any{"limit": "10", "tags": []string{"tag1", "tag2"}}, 187 | }, 188 | beforeFn: func(rt *mocks.MockRoundTripper) { 189 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 190 | assert.Equal(t, "10", req.URL.Query().Get("limit")) 191 | assert.ElementsMatch(t, []string{"tag1", "tag2"}, req.URL.Query()["tags"]) 192 | 193 | bodyReader := io.NopCloser(strings.NewReader(`[]`)) 194 | return &http.Response{ 195 | StatusCode: 200, 196 | Body: bodyReader, 197 | }, nil 198 | }) 199 | }, 200 | wantData: `[]`, 201 | }, 202 | { 203 | name: "should handle API error", 204 | opt: &RequestOptions{ 205 | Path: "/api/accounts", 206 | }, 207 | beforeFn: func(rt *mocks.MockRoundTripper) { 208 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 209 | bodyReader := io.NopCloser(strings.NewReader(`{"error": "not found"}`)) 210 | return &http.Response{ 211 | StatusCode: 404, 212 | Status: "404 Not Found", 213 | Body: bodyReader, 214 | }, nil 215 | }) 216 | }, 217 | wantErr: "API error: 404 Not Found: {\"error\": \"not found\"}", 218 | }, 219 | { 220 | name: "should handle network error", 221 | opt: &RequestOptions{ 222 | Path: "/api/accounts", 223 | }, 224 | beforeFn: func(rt *mocks.MockRoundTripper) { 225 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).Return(nil, fmt.Errorf("network error")) 226 | }, 227 | wantErr: "network error", 228 | }, 229 | } 230 | 231 | for _, tt := range tests { 232 | t.Run(tt.name, func(t *testing.T) { 233 | cfClient, mockRT := newMockClient(t) 234 | if tt.beforeFn != nil { 235 | tt.beforeFn(mockRT) 236 | } 237 | 238 | data, err := cfClient.RestAPI(context.Background(), tt.opt) 239 | if tt.wantErr != "" { 240 | assert.Error(t, err) 241 | assert.Contains(t, err.Error(), tt.wantErr) 242 | return 243 | } 244 | 245 | assert.NoError(t, err) 246 | assert.Equal(t, tt.wantData, string(data)) 247 | }) 248 | } 249 | } 250 | 251 | func TestCfClient_NativeRestAPI(t *testing.T) { 252 | tests := []struct { 253 | name string 254 | opt *RequestOptions 255 | wantErr string 256 | beforeFn func(rt *mocks.MockRoundTripper) 257 | }{ 258 | { 259 | name: "should return raw response", 260 | opt: &RequestOptions{ 261 | Path: "/api/accounts", 262 | }, 263 | beforeFn: func(rt *mocks.MockRoundTripper) { 264 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 265 | bodyReader := io.NopCloser(strings.NewReader(`{"data": "test"}`)) 266 | return &http.Response{ 267 | StatusCode: 200, 268 | Body: bodyReader, 269 | }, nil 270 | }) 271 | }, 272 | }, 273 | { 274 | name: "should return error response without wrapping", 275 | opt: &RequestOptions{ 276 | Path: "/api/accounts", 277 | }, 278 | beforeFn: func(rt *mocks.MockRoundTripper) { 279 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 280 | bodyReader := io.NopCloser(strings.NewReader(`{"error": "bad request"}`)) 281 | return &http.Response{ 282 | StatusCode: 400, 283 | Body: bodyReader, 284 | }, nil 285 | }) 286 | }, 287 | }, 288 | } 289 | 290 | for _, tt := range tests { 291 | t.Run(tt.name, func(t *testing.T) { 292 | cfClient, mockRT := newMockClient(t) 293 | if tt.beforeFn != nil { 294 | tt.beforeFn(mockRT) 295 | } 296 | 297 | resp, err := cfClient.NativeRestAPI(context.Background(), tt.opt) 298 | if tt.wantErr != "" { 299 | assert.Error(t, err) 300 | assert.Contains(t, err.Error(), tt.wantErr) 301 | return 302 | } 303 | 304 | assert.NoError(t, err) 305 | assert.NotNil(t, resp) 306 | defer resp.Body.Close() 307 | }) 308 | } 309 | } 310 | 311 | func TestCfClient_GraphqlAPI(t *testing.T) { 312 | tests := []struct { 313 | name string 314 | query string 315 | variables any 316 | wantErr string 317 | beforeFn func(rt *mocks.MockRoundTripper) 318 | }{ 319 | { 320 | name: "should make successful GraphQL request", 321 | query: "query { accounts { id name } }", 322 | variables: map[string]string{ 323 | "limit": "10", 324 | }, 325 | beforeFn: func(rt *mocks.MockRoundTripper) { 326 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 327 | assert.Equal(t, "POST", req.Method) 328 | assert.Contains(t, req.URL.Path, "grpahql-path") // Note: this matches the typo in test-utils.go 329 | assert.Equal(t, "some-token", req.Header.Get("Authorization")) 330 | assert.Equal(t, "application/json", req.Header.Get("Content-Type")) 331 | 332 | bodyReader := io.NopCloser(strings.NewReader(`{"data": {"accounts": []}}`)) 333 | return &http.Response{ 334 | StatusCode: 200, 335 | Body: bodyReader, 336 | }, nil 337 | }) 338 | }, 339 | }, 340 | { 341 | name: "should handle GraphQL error", 342 | query: "invalid query", 343 | beforeFn: func(rt *mocks.MockRoundTripper) { 344 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 345 | bodyReader := io.NopCloser(strings.NewReader(`{"error": "GraphQL error"}`)) 346 | return &http.Response{ 347 | StatusCode: 400, 348 | Status: "400 Bad Request", 349 | Body: bodyReader, 350 | }, nil 351 | }) 352 | }, 353 | wantErr: "API error: 400 Bad Request: {\"error\": \"GraphQL error\"}", 354 | }, 355 | { 356 | name: "should handle malformed JSON response", 357 | query: "query { accounts { id } }", 358 | beforeFn: func(rt *mocks.MockRoundTripper) { 359 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 360 | bodyReader := io.NopCloser(strings.NewReader(`invalid json`)) 361 | return &http.Response{ 362 | StatusCode: 200, 363 | Body: bodyReader, 364 | }, nil 365 | }) 366 | }, 367 | wantErr: "failed to unmarshal response Body:", 368 | }, 369 | } 370 | 371 | for _, tt := range tests { 372 | t.Run(tt.name, func(t *testing.T) { 373 | cfClient, mockRT := newMockClient(t) 374 | if tt.beforeFn != nil { 375 | tt.beforeFn(mockRT) 376 | } 377 | 378 | var result map[string]any 379 | err := cfClient.GraphqlAPI(context.Background(), tt.query, tt.variables, &result) 380 | if tt.wantErr != "" { 381 | assert.Error(t, err) 382 | assert.Contains(t, err.Error(), tt.wantErr) 383 | return 384 | } 385 | 386 | assert.NoError(t, err) 387 | }) 388 | } 389 | } 390 | 391 | func TestGraphqlAPI_Generic(t *testing.T) { 392 | type TestData struct { 393 | ID string `json:"id"` 394 | Name string `json:"name"` 395 | } 396 | 397 | tests := []struct { 398 | name string 399 | query string 400 | variables any 401 | wantErr string 402 | beforeFn func(rt *mocks.MockRoundTripper) 403 | wantData *TestData 404 | }{ 405 | { 406 | name: "should return typed data successfully", 407 | query: "query { account { id name } }", 408 | beforeFn: func(rt *mocks.MockRoundTripper) { 409 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 410 | response := `{"data": {"account": {"id": "123", "name": "test"}}}` 411 | bodyReader := io.NopCloser(strings.NewReader(response)) 412 | return &http.Response{ 413 | StatusCode: 200, 414 | Body: bodyReader, 415 | }, nil 416 | }) 417 | }, 418 | wantData: &TestData{ID: "123", Name: "test"}, 419 | }, 420 | { 421 | name: "should handle GraphQL errors", 422 | query: "query { account { id name } }", 423 | beforeFn: func(rt *mocks.MockRoundTripper) { 424 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 425 | response := `{"data": {"account": {"id": "123", "name": "test"}}, "errors": [{"message": "field deprecated"}]}` 426 | bodyReader := io.NopCloser(strings.NewReader(response)) 427 | return &http.Response{ 428 | StatusCode: 200, 429 | Body: bodyReader, 430 | }, nil 431 | }) 432 | }, 433 | wantData: &TestData{ID: "123", Name: "test"}, 434 | wantErr: "field deprecated", 435 | }, 436 | { 437 | name: "should handle API errors", 438 | query: "invalid query", 439 | beforeFn: func(rt *mocks.MockRoundTripper) { 440 | rt.EXPECT().RoundTrip(mock.AnythingOfType("*http.Request")).RunAndReturn(func(req *http.Request) (*http.Response, error) { 441 | bodyReader := io.NopCloser(strings.NewReader(`{"error": "bad request"}`)) 442 | return &http.Response{ 443 | StatusCode: 400, 444 | Status: "400 Bad Request", 445 | Body: bodyReader, 446 | }, nil 447 | }) 448 | }, 449 | wantErr: "API error: 400 Bad Request: {\"error\": \"bad request\"}", 450 | }, 451 | } 452 | 453 | for _, tt := range tests { 454 | t.Run(tt.name, func(t *testing.T) { 455 | cfClient, mockRT := newMockClient(t) 456 | if tt.beforeFn != nil { 457 | tt.beforeFn(mockRT) 458 | } 459 | 460 | result, err := GraphqlAPI[TestData](context.Background(), cfClient, tt.query, tt.variables) 461 | if tt.wantErr != "" { 462 | assert.Error(t, err) 463 | assert.Contains(t, err.Error(), tt.wantErr) 464 | if tt.wantData != nil { 465 | assert.Equal(t, *tt.wantData, result) 466 | } 467 | return 468 | } 469 | 470 | assert.NoError(t, err) 471 | if tt.wantData != nil { 472 | assert.Equal(t, *tt.wantData, result) 473 | } 474 | }) 475 | } 476 | } 477 | 478 | func TestApiError_Error(t *testing.T) { 479 | err := &ApiError{ 480 | status: "404 Not Found", 481 | statusCode: 404, 482 | body: `{"error": "resource not found"}`, 483 | } 484 | 485 | expected := `API error: 404 Not Found: {"error": "resource not found"}` 486 | assert.Equal(t, expected, err.Error()) 487 | } 488 | 489 | func TestGraphqlErrorResponse_Error(t *testing.T) { 490 | tests := []struct { 491 | name string 492 | response *GraphqlErrorResponse 493 | want string 494 | }{ 495 | { 496 | name: "should return cached error string", 497 | response: &GraphqlErrorResponse{ 498 | concatenatedErrors: "cached error", 499 | Errors: []GraphqlError{ 500 | {Message: "error 1"}, 501 | {Message: "error 2"}, 502 | }, 503 | }, 504 | want: "cached error", 505 | }, 506 | { 507 | name: "should concatenate multiple errors", 508 | response: &GraphqlErrorResponse{ 509 | Errors: []GraphqlError{ 510 | {Message: "error 1"}, 511 | {Message: "error 2"}, 512 | {Message: "error 3"}, 513 | }, 514 | }, 515 | want: "error 1\nerror 2\nerror 3\n", 516 | }, 517 | { 518 | name: "should handle single error", 519 | response: &GraphqlErrorResponse{ 520 | Errors: []GraphqlError{ 521 | {Message: "single error"}, 522 | }, 523 | }, 524 | want: "single error\n", 525 | }, 526 | { 527 | name: "should handle no errors", 528 | response: &GraphqlErrorResponse{ 529 | Errors: []GraphqlError{}, 530 | }, 531 | want: "", 532 | }, 533 | } 534 | 535 | for _, tt := range tests { 536 | t.Run(tt.name, func(t *testing.T) { 537 | result := tt.response.Error() 538 | assert.Equal(t, tt.want, result) 539 | 540 | // Note: The caching doesn't work due to value receiver, but the method still works 541 | if len(tt.response.Errors) > 0 && tt.response.concatenatedErrors == "" { 542 | result2 := tt.response.Error() 543 | assert.Equal(t, result, result2) 544 | // The concatenatedErrors field won't be set due to value receiver 545 | } 546 | }) 547 | } 548 | } 549 | 550 | func TestGraphqlBaseResponse_HasErrors(t *testing.T) { 551 | tests := []struct { 552 | name string 553 | response GraphqlBaseResponse 554 | want bool 555 | }{ 556 | { 557 | name: "should return true when errors exist", 558 | response: GraphqlBaseResponse{ 559 | Errors: []GraphqlError{ 560 | {Message: "error 1"}, 561 | }, 562 | }, 563 | want: true, 564 | }, 565 | { 566 | name: "should return false when no errors", 567 | response: GraphqlBaseResponse{ 568 | Errors: []GraphqlError{}, 569 | }, 570 | want: false, 571 | }, 572 | { 573 | name: "should return false when errors is nil", 574 | response: GraphqlBaseResponse{ 575 | Errors: nil, 576 | }, 577 | want: false, 578 | }, 579 | } 580 | 581 | for _, tt := range tests { 582 | t.Run(tt.name, func(t *testing.T) { 583 | result := tt.response.HasErrors() 584 | assert.Equal(t, tt.want, result) 585 | }) 586 | } 587 | } 588 | 589 | func TestSetQueryParams(t *testing.T) { 590 | tests := []struct { 591 | name string 592 | query map[string]any 593 | wantErr string 594 | want map[string][]string 595 | }{ 596 | { 597 | name: "should handle string values", 598 | query: map[string]any{ 599 | "key1": "value1", 600 | "key2": "value2", 601 | }, 602 | want: map[string][]string{ 603 | "key1": {"value1"}, 604 | "key2": {"value2"}, 605 | }, 606 | }, 607 | { 608 | name: "should handle string slice values", 609 | query: map[string]any{ 610 | "tags": []string{"tag1", "tag2", "tag3"}, 611 | "types": []string{"type1"}, 612 | }, 613 | want: map[string][]string{ 614 | "tags": {"tag1", "tag2", "tag3"}, 615 | "types": {"type1"}, 616 | }, 617 | }, 618 | { 619 | name: "should handle mixed types", 620 | query: map[string]any{ 621 | "name": "test", 622 | "tags": []string{"tag1", "tag2"}, 623 | }, 624 | want: map[string][]string{ 625 | "name": {"test"}, 626 | "tags": {"tag1", "tag2"}, 627 | }, 628 | }, 629 | { 630 | name: "should return error for invalid type", 631 | query: map[string]any{ 632 | "invalid": 123, 633 | }, 634 | wantErr: "invalid query param type: int", 635 | }, 636 | { 637 | name: "should return error for invalid slice type", 638 | query: map[string]any{ 639 | "invalid": []int{1, 2, 3}, 640 | }, 641 | wantErr: "invalid query param type: []int", 642 | }, 643 | } 644 | 645 | for _, tt := range tests { 646 | t.Run(tt.name, func(t *testing.T) { 647 | q := url.Values{} 648 | err := setQueryParams(q, tt.query) 649 | 650 | if tt.wantErr != "" { 651 | assert.Error(t, err) 652 | assert.Contains(t, err.Error(), tt.wantErr) 653 | return 654 | } 655 | 656 | assert.NoError(t, err) 657 | for key, expectedValues := range tt.want { 658 | actualValues := q[key] 659 | assert.ElementsMatch(t, expectedValues, actualValues, "mismatch for key: %s", key) 660 | } 661 | }) 662 | } 663 | } 664 | 665 | func TestGraphqlVoidResponse(t *testing.T) { 666 | // Test that GraphqlVoidResponse can be instantiated 667 | var response GraphqlVoidResponse 668 | assert.NotNil(t, &response) 669 | } 670 | 671 | func TestCfClient_Integration(t *testing.T) { 672 | // Test that all the types implement the expected interfaces 673 | t.Run("GraphqlBaseResponse implements GraphqlResponse", func(t *testing.T) { 674 | var response GraphqlResponse = GraphqlBaseResponse{} 675 | assert.False(t, response.HasErrors()) 676 | 677 | response = GraphqlBaseResponse{Errors: []GraphqlError{{Message: "test"}}} 678 | assert.True(t, response.HasErrors()) 679 | }) 680 | 681 | t.Run("client configuration is preserved", func(t *testing.T) { 682 | originalClient := &http.Client{Timeout: 45 * time.Second} 683 | client := NewCfClient("https://api.codefresh.io", "token", "/custom", originalClient) 684 | 685 | // Test AppProxyClient preserves timeout 686 | proxyClient := client.AppProxyClient("https://proxy.codefresh.io", false) 687 | assert.Equal(t, originalClient.Timeout, proxyClient.client.Timeout) 688 | 689 | // Test insecure TLS configuration 690 | insecureProxyClient := client.AppProxyClient("https://proxy.codefresh.io", true) 691 | transport := insecureProxyClient.client.Transport.(*http.Transport) 692 | assert.True(t, transport.TLSClientConfig.InsecureSkipVerify) 693 | }) 694 | } 695 | -------------------------------------------------------------------------------- /scripts/codecov.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Apache License Version 2.0, January 2004 4 | # https://github.com/codecov/codecov-bash/blob/master/LICENSE 5 | 6 | 7 | set -e +o pipefail 8 | 9 | VERSION="20200602-f809a24" 10 | 11 | url="https://codecov.io" 12 | env="$CODECOV_ENV" 13 | service="" 14 | token="" 15 | search_in="" 16 | flags="" 17 | exit_with=0 18 | curlargs="" 19 | curlawsargs="" 20 | dump="0" 21 | clean="0" 22 | curl_s="-s" 23 | name="$CODECOV_NAME" 24 | include_cov="" 25 | exclude_cov="" 26 | ddp="$(echo ~)/Library/Developer/Xcode/DerivedData" 27 | xp="" 28 | files="" 29 | cacert="$CODECOV_CA_BUNDLE" 30 | gcov_ignore="-not -path './bower_components/**' -not -path './node_modules/**' -not -path './vendor/**'" 31 | gcov_include="" 32 | 33 | ft_gcov="1" 34 | ft_coveragepy="1" 35 | ft_fix="1" 36 | ft_search="1" 37 | ft_s3="1" 38 | ft_network="1" 39 | ft_xcodellvm="1" 40 | ft_xcodeplist="0" 41 | ft_gcovout="1" 42 | ft_html="0" 43 | 44 | _git_root=$(git rev-parse --show-toplevel 2>/dev/null || hg root 2>/dev/null || echo $PWD) 45 | git_root="$_git_root" 46 | remote_addr="" 47 | if [ "$git_root" = "$PWD" ]; 48 | then 49 | git_root="." 50 | fi 51 | 52 | url_o="" 53 | pr_o="" 54 | build_o="" 55 | commit_o="" 56 | search_in_o="" 57 | tag_o="" 58 | branch_o="" 59 | slug_o="" 60 | prefix_o="" 61 | 62 | commit="$VCS_COMMIT_ID" 63 | branch="$VCS_BRANCH_NAME" 64 | pr="$VCS_PULL_REQUEST" 65 | slug="$VCS_SLUG" 66 | tag="$VCS_TAG" 67 | build_url="$CI_BUILD_URL" 68 | build="$CI_BUILD_ID" 69 | job="$CI_JOB_ID" 70 | 71 | beta_xcode_partials="" 72 | 73 | proj_root="$git_root" 74 | gcov_exe="gcov" 75 | gcov_arg="" 76 | 77 | b="\033[0;36m" 78 | g="\033[0;32m" 79 | r="\033[0;31m" 80 | e="\033[0;90m" 81 | x="\033[0m" 82 | 83 | show_help() { 84 | cat << EOF 85 | 86 | Codecov Bash $VERSION 87 | 88 | Global report uploading tool for Codecov 89 | Documentation at https://docs.codecov.io/docs 90 | Contribute at https://github.com/codecov/codecov-bash 91 | 92 | 93 | -h Display this help and exit 94 | -f FILE Target file(s) to upload 95 | 96 | -f "path/to/file" only upload this file 97 | skips searching unless provided patterns below 98 | 99 | -f '!*.bar' ignore all files at pattern *.bar 100 | -f '*.foo' include all files at pattern *.foo 101 | Must use single quotes. 102 | This is non-exclusive, use -s "*.foo" to match specific paths. 103 | 104 | -s DIR Directory to search for coverage reports. 105 | Already searches project root and artifact folders. 106 | -t TOKEN Set the private repository token 107 | (option) set environment variable CODECOV_TOKEN=:uuid 108 | 109 | -t @/path/to/token_file 110 | -t uuid 111 | 112 | -n NAME Custom defined name of the upload. Visible in Codecov UI 113 | 114 | -e ENV Specify environment variables to be included with this build 115 | Also accepting environment variables: CODECOV_ENV=VAR,VAR2 116 | 117 | -e VAR,VAR2 118 | 119 | -X feature Toggle functionalities 120 | 121 | -X gcov Disable gcov 122 | -X coveragepy Disable python coverage 123 | -X fix Disable report fixing 124 | -X search Disable searching for reports 125 | -X xcode Disable xcode processing 126 | -X network Disable uploading the file network 127 | -X gcovout Disable gcov output 128 | -X html Enable coverage for HTML files 129 | 130 | -N The commit SHA of the parent for which you are uploading coverage. If not present, 131 | the parent will be determined using the API of your repository provider. 132 | When using the repository provider's API, the parent is determined via finding 133 | the closest ancestor to the commit. 134 | 135 | -R root dir Used when not in git/hg project to identify project root directory 136 | -F flag Flag the upload to group coverage metrics 137 | 138 | -F unittests This upload is only unittests 139 | -F integration This upload is only integration tests 140 | -F ui,chrome This upload is Chrome - UI tests 141 | 142 | -c Move discovered coverage reports to the trash 143 | -Z Exit with 1 if not successful. Default will Exit with 0 144 | 145 | -- xcode -- 146 | -D Custom Derived Data Path for Coverage.profdata and gcov processing 147 | Default '~/Library/Developer/Xcode/DerivedData' 148 | -J Specify packages to build coverage. Uploader will only build these packages. 149 | This can significantly reduces time to build coverage reports. 150 | 151 | -J 'MyAppName' Will match "MyAppName" and "MyAppNameTests" 152 | -J '^ExampleApp$' Will match only "ExampleApp" not "ExampleAppTests" 153 | 154 | -- gcov -- 155 | -g GLOB Paths to ignore during gcov gathering 156 | -G GLOB Paths to include during gcov gathering 157 | -p dir Project root directory 158 | Also used when preparing gcov 159 | -k prefix Prefix filepaths to help resolve path fixing: https://github.com/codecov/support/issues/472 160 | -x gcovexe gcov executable to run. Defaults to 'gcov' 161 | -a gcovargs extra arguments to pass to gcov 162 | 163 | -- Override CI Environment Variables -- 164 | These variables are automatically detected by popular CI providers 165 | 166 | -B branch Specify the branch name 167 | -C sha Specify the commit sha 168 | -P pr Specify the pull request number 169 | -b build Specify the build number 170 | -T tag Specify the git tag 171 | 172 | -- Enterprise -- 173 | -u URL Set the target url for Enterprise customers 174 | Not required when retrieving the bash uploader from your CCE 175 | (option) Set environment variable CODECOV_URL=https://my-hosted-codecov.com 176 | -r SLUG owner/repo slug used instead of the private repo token in Enterprise 177 | (option) set environment variable CODECOV_SLUG=:owner/:repo 178 | (option) set in your codecov.yml "codecov.slug" 179 | -S PATH File path to your cacert.pem file used to verify ssl with Codecov Enterprise (optional) 180 | (option) Set environment variable: CODECOV_CA_BUNDLE="/path/to/ca.pem" 181 | -U curlargs Extra curl arguments to communicate with Codecov. e.g., -U "--proxy http://http-proxy" 182 | -A curlargs Extra curl arguments to communicate with AWS. 183 | 184 | -- Debugging -- 185 | -d Don't upload, but dump upload file to stdout 186 | -K Remove color from the output 187 | -v Verbose mode 188 | 189 | EOF 190 | } 191 | 192 | 193 | say() { 194 | echo -e "$1" 195 | } 196 | 197 | 198 | urlencode() { 199 | echo "$1" | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3- | sed -e 's/%0A//' 200 | } 201 | 202 | 203 | swiftcov() { 204 | _dir=$(dirname "$1" | sed 's/\(Build\).*/\1/g') 205 | for _type in app framework xctest 206 | do 207 | find "$_dir" -name "*.$_type" | while read f 208 | do 209 | _proj=${f##*/} 210 | _proj=${_proj%."$_type"} 211 | if [ "$2" = "" ] || [ "$(echo "$_proj" | grep -i "$2")" != "" ]; 212 | then 213 | say " $g+$x Building reports for $_proj $_type" 214 | dest=$([ -f "$f/$_proj" ] && echo "$f/$_proj" || echo "$f/Contents/MacOS/$_proj") 215 | _proj_name=$(echo "$_proj" | sed -e 's/[[:space:]]//g') 216 | xcrun llvm-cov show $beta_xcode_partials -instr-profile "$1" "$dest" > "$_proj_name.$_type.coverage.txt" \ 217 | || say " ${r}x>${x} llvm-cov failed to produce results for $dest" 218 | fi 219 | done 220 | done 221 | } 222 | 223 | 224 | # Credits to: https://gist.github.com/pkuczynski/8665367 225 | parse_yaml() { 226 | local prefix=$2 227 | local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') 228 | sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ 229 | -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 | 230 | awk -F$fs '{ 231 | indent = length($1)/2; 232 | vname[indent] = $2; 233 | for (i in vname) {if (i > indent) {delete vname[i]}} 234 | if (length($3) > 0) { 235 | vn=""; if (indent > 0) {vn=(vn)(vname[0])("_")} 236 | printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3); 237 | } 238 | }' 239 | } 240 | 241 | 242 | if [ $# != 0 ]; 243 | then 244 | while getopts "a:A:b:B:cC:dD:e:f:F:g:G:hJ:k:Kn:p:P:r:R:y:s:S:t:T:u:U:vx:X:ZN:" o 245 | do 246 | case "$o" in 247 | "N") 248 | parent=$OPTARG 249 | ;; 250 | "a") 251 | gcov_arg=$OPTARG 252 | ;; 253 | "A") 254 | curlawsargs="$OPTARG" 255 | ;; 256 | "b") 257 | build_o="$OPTARG" 258 | ;; 259 | "B") 260 | branch_o="$OPTARG" 261 | ;; 262 | "c") 263 | clean="1" 264 | ;; 265 | "C") 266 | commit_o="$OPTARG" 267 | ;; 268 | "d") 269 | dump="1" 270 | ;; 271 | "D") 272 | ddp="$OPTARG" 273 | ;; 274 | "e") 275 | env="$env,$OPTARG" 276 | ;; 277 | "f") 278 | if [ "${OPTARG::1}" = "!" ]; 279 | then 280 | exclude_cov="$exclude_cov -not -path '${OPTARG:1}'" 281 | 282 | elif [[ "$OPTARG" = *"*"* ]]; 283 | then 284 | include_cov="$include_cov -or -name '$OPTARG'" 285 | 286 | else 287 | ft_search=0 288 | if [ "$files" = "" ]; 289 | then 290 | files="$OPTARG" 291 | else 292 | files="$files 293 | $OPTARG" 294 | fi 295 | fi 296 | ;; 297 | "F") 298 | if [ "$flags" = "" ]; 299 | then 300 | flags="$OPTARG" 301 | else 302 | flags="$flags,$OPTARG" 303 | fi 304 | ;; 305 | "g") 306 | gcov_ignore="$gcov_ignore -not -path '$OPTARG'" 307 | ;; 308 | "G") 309 | gcov_include="$gcov_include -path '$OPTARG'" 310 | ;; 311 | "h") 312 | show_help 313 | exit 0; 314 | ;; 315 | "J") 316 | ft_xcodellvm="1" 317 | ft_xcodeplist="0" 318 | if [ "$xp" = "" ]; 319 | then 320 | xp="$OPTARG" 321 | else 322 | xp="$xp\|$OPTARG" 323 | fi 324 | ;; 325 | "k") 326 | prefix_o=$(echo "$OPTARG" | sed -e 's:^/*::' -e 's:/*$::') 327 | ;; 328 | "K") 329 | b="" 330 | g="" 331 | r="" 332 | e="" 333 | x="" 334 | ;; 335 | "n") 336 | name="$OPTARG" 337 | ;; 338 | "p") 339 | proj_root="$OPTARG" 340 | ;; 341 | "P") 342 | pr_o="$OPTARG" 343 | ;; 344 | "r") 345 | slug_o="$OPTARG" 346 | ;; 347 | "R") 348 | git_root="$OPTARG" 349 | ;; 350 | "s") 351 | if [ "$search_in_o" = "" ]; 352 | then 353 | search_in_o="$OPTARG" 354 | else 355 | search_in_o="$search_in_o $OPTARG" 356 | fi 357 | ;; 358 | "S") 359 | cacert="--cacert \"$OPTARG\"" 360 | ;; 361 | "t") 362 | if [ "${OPTARG::1}" = "@" ]; 363 | then 364 | token=$(cat "${OPTARG:1}" | tr -d ' \n') 365 | else 366 | token="$OPTARG" 367 | fi 368 | ;; 369 | "T") 370 | tag_o="$OPTARG" 371 | ;; 372 | "u") 373 | url_o=$(echo "$OPTARG" | sed -e 's/\/$//') 374 | ;; 375 | "U") 376 | curlargs="$OPTARG" 377 | ;; 378 | "v") 379 | set -x 380 | curl_s="" 381 | ;; 382 | "x") 383 | gcov_exe=$OPTARG 384 | ;; 385 | "X") 386 | if [ "$OPTARG" = "gcov" ]; 387 | then 388 | ft_gcov="0" 389 | elif [ "$OPTARG" = "coveragepy" ] || [ "$OPTARG" = "py" ]; 390 | then 391 | ft_coveragepy="0" 392 | elif [ "$OPTARG" = "gcovout" ]; 393 | then 394 | ft_gcovout="0" 395 | elif [ "$OPTARG" = "xcodellvm" ]; 396 | then 397 | ft_xcodellvm="1" 398 | ft_xcodeplist="0" 399 | elif [ "$OPTARG" = "fix" ] || [ "$OPTARG" = "fixes" ]; 400 | then 401 | ft_fix="0" 402 | elif [ "$OPTARG" = "xcode" ]; 403 | then 404 | ft_xcodellvm="0" 405 | ft_xcodeplist="0" 406 | elif [ "$OPTARG" = "search" ]; 407 | then 408 | ft_search="0" 409 | elif [ "$OPTARG" = "xcodepartials" ]; 410 | then 411 | beta_xcode_partials="-use-color" 412 | elif [ "$OPTARG" = "network" ]; 413 | then 414 | ft_network="0" 415 | elif [ "$OPTARG" = "s3" ]; 416 | then 417 | ft_s3="0" 418 | elif [ "$OPTARG" = "html" ]; 419 | then 420 | ft_html="1" 421 | fi 422 | ;; 423 | "y") 424 | echo -e "${r}DeprecationWarning${x}: The -y flag is no longer supported by Codecov."` 425 | `"\n codecov.yml must be located underneath the root, dev/, or .github/ directories" 426 | ;; 427 | "Z") 428 | exit_with=1 429 | ;; 430 | esac 431 | done 432 | fi 433 | 434 | say " 435 | _____ _ 436 | / ____| | | 437 | | | ___ __| | ___ ___ _____ __ 438 | | | / _ \\ / _\` |/ _ \\/ __/ _ \\ \\ / / 439 | | |___| (_) | (_| | __/ (_| (_) \\ V / 440 | \\_____\\___/ \\__,_|\\___|\\___\\___/ \\_/ 441 | Bash-$VERSION 442 | 443 | " 444 | 445 | search_in="$proj_root" 446 | 447 | if [ "$JENKINS_URL" != "" ]; 448 | then 449 | say "$e==>$x Jenkins CI detected." 450 | # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project 451 | # https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables 452 | service="jenkins" 453 | 454 | if [ "$ghprbSourceBranch" != "" ]; 455 | then 456 | branch="$ghprbSourceBranch" 457 | elif [ "$GIT_BRANCH" != "" ]; 458 | then 459 | branch="$GIT_BRANCH" 460 | elif [ "$BRANCH_NAME" != "" ]; 461 | then 462 | branch="$BRANCH_NAME" 463 | fi 464 | 465 | if [ "$ghprbActualCommit" != "" ]; 466 | then 467 | commit="$ghprbActualCommit" 468 | elif [ "$GIT_COMMIT" != "" ]; 469 | then 470 | commit="$GIT_COMMIT" 471 | fi 472 | 473 | if [ "$ghprbPullId" != "" ]; 474 | then 475 | pr="$ghprbPullId" 476 | elif [ "$CHANGE_ID" != "" ]; 477 | then 478 | pr="$CHANGE_ID" 479 | fi 480 | 481 | build="$BUILD_NUMBER" 482 | build_url=$(urlencode "$BUILD_URL") 483 | 484 | elif [ "$CI" = "true" ] && [ "$TRAVIS" = "true" ] && [ "$SHIPPABLE" != "true" ]; 485 | then 486 | say "$e==>$x Travis CI detected." 487 | # https://docs.travis-ci.com/user/environment-variables/ 488 | service="travis" 489 | commit="${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT}" 490 | build="$TRAVIS_JOB_NUMBER" 491 | pr="$TRAVIS_PULL_REQUEST" 492 | job="$TRAVIS_JOB_ID" 493 | slug="$TRAVIS_REPO_SLUG" 494 | env="$env,TRAVIS_OS_NAME" 495 | tag="$TRAVIS_TAG" 496 | if [ "$TRAVIS_BRANCH" != "$TRAVIS_TAG" ]; 497 | then 498 | branch="$TRAVIS_BRANCH" 499 | fi 500 | 501 | language=$(compgen -A variable | grep "^TRAVIS_.*_VERSION$" | head -1) 502 | if [ "$language" != "" ]; 503 | then 504 | env="$env,${!language}" 505 | fi 506 | 507 | elif [ "$CODEBUILD_CI" = "true" ]; 508 | then 509 | say "$e==>$x AWS Codebuild detected." 510 | # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html 511 | service="codebuild" 512 | commit="$CODEBUILD_RESOLVED_SOURCE_VERSION" 513 | build="$CODEBUILD_BUILD_ID" 514 | branch="$(echo $CODEBUILD_WEBHOOK_HEAD_REF | sed 's/^refs\/heads\///')" 515 | if [ "${CODEBUILD_SOURCE_VERSION/pr}" = "$CODEBUILD_SOURCE_VERSION" ] ; then 516 | pr="false" 517 | else 518 | pr="$(echo $CODEBUILD_SOURCE_VERSION | sed 's/^pr\///')" 519 | fi 520 | job="$CODEBUILD_BUILD_ID" 521 | slug="$(echo $CODEBUILD_SOURCE_REPO_URL | sed 's/^.*:\/\/[^\/]*\///' | sed 's/\.git$//')" 522 | 523 | elif [ "$DOCKER_REPO" != "" ]; 524 | then 525 | say "$e==>$x Docker detected." 526 | # https://docs.docker.com/docker-cloud/builds/advanced/ 527 | service="docker" 528 | branch="$SOURCE_BRANCH" 529 | commit="$SOURCE_COMMIT" 530 | slug="$DOCKER_REPO" 531 | tag="$CACHE_TAG" 532 | env="$env,IMAGE_NAME" 533 | 534 | elif [ "$CI" = "true" ] && [ "$CI_NAME" = "codeship" ]; 535 | then 536 | say "$e==>$x Codeship CI detected." 537 | # https://www.codeship.io/documentation/continuous-integration/set-environment-variables/ 538 | service="codeship" 539 | branch="$CI_BRANCH" 540 | build="$CI_BUILD_NUMBER" 541 | build_url=$(urlencode "$CI_BUILD_URL") 542 | commit="$CI_COMMIT_ID" 543 | 544 | elif [ ! -z "$CF_BUILD_URL" ] && [ ! -z "$CF_BUILD_ID" ]; 545 | then 546 | say "$e==>$x Codefresh CI detected." 547 | # https://docs.codefresh.io/v1.0/docs/variables 548 | service="codefresh" 549 | branch="$CF_BRANCH" 550 | build="$CF_BUILD_ID" 551 | build_url=$(urlencode "$CF_BUILD_URL") 552 | commit="$CF_REVISION" 553 | 554 | elif [ "$TEAMCITY_VERSION" != "" ]; 555 | then 556 | say "$e==>$x TeamCity CI detected." 557 | # https://confluence.jetbrains.com/display/TCD8/Predefined+Build+Parameters 558 | # https://confluence.jetbrains.com/plugins/servlet/mobile#content/view/74847298 559 | if [ "$TEAMCITY_BUILD_BRANCH" = '' ]; 560 | then 561 | echo " Teamcity does not automatically make build parameters available as environment variables." 562 | echo " Add the following environment parameters to the build configuration" 563 | echo " env.TEAMCITY_BUILD_BRANCH = %teamcity.build.branch%" 564 | echo " env.TEAMCITY_BUILD_ID = %teamcity.build.id%" 565 | echo " env.TEAMCITY_BUILD_URL = %teamcity.serverUrl%/viewLog.html?buildId=%teamcity.build.id%" 566 | echo " env.TEAMCITY_BUILD_COMMIT = %system.build.vcs.number%" 567 | echo " env.TEAMCITY_BUILD_REPOSITORY = %vcsroot..url%" 568 | fi 569 | service="teamcity" 570 | branch="$TEAMCITY_BUILD_BRANCH" 571 | build="$TEAMCITY_BUILD_ID" 572 | build_url=$(urlencode "$TEAMCITY_BUILD_URL") 573 | if [ "$TEAMCITY_BUILD_COMMIT" != "" ]; 574 | then 575 | commit="$TEAMCITY_BUILD_COMMIT" 576 | else 577 | commit="$BUILD_VCS_NUMBER" 578 | fi 579 | remote_addr="$TEAMCITY_BUILD_REPOSITORY" 580 | 581 | elif [ "$CI" = "true" ] && [ "$CIRCLECI" = "true" ]; 582 | then 583 | say "$e==>$x Circle CI detected." 584 | # https://circleci.com/docs/environment-variables 585 | service="circleci" 586 | branch="$CIRCLE_BRANCH" 587 | build="$CIRCLE_BUILD_NUM" 588 | job="$CIRCLE_NODE_INDEX" 589 | if [ "$CIRCLE_PROJECT_REPONAME" != "" ]; 590 | then 591 | slug="$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME" 592 | else 593 | # git@github.com:owner/repo.git 594 | slug="${CIRCLE_REPOSITORY_URL##*:}" 595 | # owner/repo.git 596 | slug="${slug%%.git}" 597 | fi 598 | pr="$CIRCLE_PR_NUMBER" 599 | commit="$CIRCLE_SHA1" 600 | search_in="$search_in $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS" 601 | 602 | elif [ "$BUDDYBUILD_BRANCH" != "" ]; 603 | then 604 | say "$e==>$x buddybuild detected" 605 | # http://docs.buddybuild.com/v6/docs/custom-prebuild-and-postbuild-steps 606 | service="buddybuild" 607 | branch="$BUDDYBUILD_BRANCH" 608 | build="$BUDDYBUILD_BUILD_NUMBER" 609 | build_url="https://dashboard.buddybuild.com/public/apps/$BUDDYBUILD_APP_ID/build/$BUDDYBUILD_BUILD_ID" 610 | # BUDDYBUILD_TRIGGERED_BY 611 | if [ "$ddp" = "$(echo ~)/Library/Developer/Xcode/DerivedData" ]; 612 | then 613 | ddp="/private/tmp/sandbox/${BUDDYBUILD_APP_ID}/bbtest" 614 | fi 615 | 616 | elif [ "${bamboo_planRepository_revision}" != "" ]; 617 | then 618 | say "$e==>$x Bamboo detected" 619 | # https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html#Bamboovariables-Build-specificvariables 620 | service="bamboo" 621 | commit="${bamboo_planRepository_revision}" 622 | branch="${bamboo_planRepository_branch}" 623 | build="${bamboo_buildNumber}" 624 | build_url="${bamboo_buildResultsUrl}" 625 | remote_addr="${bamboo_planRepository_repositoryUrl}" 626 | 627 | elif [ "$CI" = "true" ] && [ "$BITRISE_IO" = "true" ]; 628 | then 629 | # http://devcenter.bitrise.io/faq/available-environment-variables/ 630 | say "$e==>$x Bitrise CI detected." 631 | service="bitrise" 632 | branch="$BITRISE_GIT_BRANCH" 633 | build="$BITRISE_BUILD_NUMBER" 634 | build_url=$(urlencode "$BITRISE_BUILD_URL") 635 | pr="$BITRISE_PULL_REQUEST" 636 | if [ "$GIT_CLONE_COMMIT_HASH" != "" ]; 637 | then 638 | commit="$GIT_CLONE_COMMIT_HASH" 639 | fi 640 | 641 | elif [ "$CI" = "true" ] && [ "$SEMAPHORE" = "true" ]; 642 | then 643 | say "$e==>$x Semaphore CI detected." 644 | # https://semaphoreapp.com/docs/available-environment-variables.html 645 | service="semaphore" 646 | branch="$BRANCH_NAME" 647 | build="$SEMAPHORE_BUILD_NUMBER" 648 | job="$SEMAPHORE_CURRENT_THREAD" 649 | pr="$PULL_REQUEST_NUMBER" 650 | slug="$SEMAPHORE_REPO_SLUG" 651 | commit="$REVISION" 652 | env="$env,SEMAPHORE_TRIGGER_SOURCE" 653 | 654 | elif [ "$CI" = "true" ] && [ "$BUILDKITE" = "true" ]; 655 | then 656 | say "$e==>$x Buildkite CI detected." 657 | # https://buildkite.com/docs/guides/environment-variables 658 | service="buildkite" 659 | branch="$BUILDKITE_BRANCH" 660 | build="$BUILDKITE_BUILD_NUMBER" 661 | job="$BUILDKITE_JOB_ID" 662 | build_url=$(urlencode "$BUILDKITE_BUILD_URL") 663 | slug="$BUILDKITE_PROJECT_SLUG" 664 | commit="$BUILDKITE_COMMIT" 665 | if [[ "$BUILDKITE_PULL_REQUEST" != "false" ]]; then 666 | pr="$BUILDKITE_PULL_REQUEST" 667 | fi 668 | tag="$BUILDKITE_TAG" 669 | 670 | elif [ "$CI" = "drone" ] || [ "$DRONE" = "true" ]; 671 | then 672 | say "$e==>$x Drone CI detected." 673 | # http://docs.drone.io/env.html 674 | # drone commits are not full shas 675 | service="drone.io" 676 | branch="$DRONE_BRANCH" 677 | build="$DRONE_BUILD_NUMBER" 678 | build_url=$(urlencode "${DRONE_BUILD_LINK}") 679 | pr="$DRONE_PULL_REQUEST" 680 | job="$DRONE_JOB_NUMBER" 681 | tag="$DRONE_TAG" 682 | 683 | elif [ "$HEROKU_TEST_RUN_BRANCH" != "" ]; 684 | then 685 | say "$e==>$x Heroku CI detected." 686 | # https://devcenter.heroku.com/articles/heroku-ci#environment-variables 687 | service="heroku" 688 | branch="$HEROKU_TEST_RUN_BRANCH" 689 | build="$HEROKU_TEST_RUN_ID" 690 | 691 | elif [[ "$CI" = "true" || "$CI" = "True" ]] && [[ "$APPVEYOR" = "true" || "$APPVEYOR" = "True" ]]; 692 | then 693 | say "$e==>$x Appveyor CI detected." 694 | # http://www.appveyor.com/docs/environment-variables 695 | service="appveyor" 696 | branch="$APPVEYOR_REPO_BRANCH" 697 | build=$(urlencode "$APPVEYOR_JOB_ID") 698 | pr="$APPVEYOR_PULL_REQUEST_NUMBER" 699 | job="$APPVEYOR_ACCOUNT_NAME%2F$APPVEYOR_PROJECT_SLUG%2F$APPVEYOR_BUILD_VERSION" 700 | slug="$APPVEYOR_REPO_NAME" 701 | commit="$APPVEYOR_REPO_COMMIT" 702 | build_url=$(urlencode "${APPVEYOR_URL}/project/${APPVEYOR_REPO_NAME}/builds/$APPVEYOR_BUILD_ID/job/${APPVEYOR_JOB_ID}") 703 | elif [ "$CI" = "true" ] && [ "$WERCKER_GIT_BRANCH" != "" ]; 704 | then 705 | say "$e==>$x Wercker CI detected." 706 | # http://devcenter.wercker.com/articles/steps/variables.html 707 | service="wercker" 708 | branch="$WERCKER_GIT_BRANCH" 709 | build="$WERCKER_MAIN_PIPELINE_STARTED" 710 | slug="$WERCKER_GIT_OWNER/$WERCKER_GIT_REPOSITORY" 711 | commit="$WERCKER_GIT_COMMIT" 712 | 713 | elif [ "$CI" = "true" ] && [ "$MAGNUM" = "true" ]; 714 | then 715 | say "$e==>$x Magnum CI detected." 716 | # https://magnum-ci.com/docs/environment 717 | service="magnum" 718 | branch="$CI_BRANCH" 719 | build="$CI_BUILD_NUMBER" 720 | commit="$CI_COMMIT" 721 | 722 | elif [ "$SHIPPABLE" = "true" ]; 723 | then 724 | say "$e==>$x Shippable CI detected." 725 | # http://docs.shippable.com/ci_configure/ 726 | service="shippable" 727 | branch=$([ "$HEAD_BRANCH" != "" ] && echo "$HEAD_BRANCH" || echo "$BRANCH") 728 | build="$BUILD_NUMBER" 729 | build_url=$(urlencode "$BUILD_URL") 730 | pr="$PULL_REQUEST" 731 | slug="$REPO_FULL_NAME" 732 | commit="$COMMIT" 733 | 734 | elif [ "$TDDIUM" = "true" ]; 735 | then 736 | say "Solano CI detected." 737 | # http://docs.solanolabs.com/Setup/tddium-set-environment-variables/ 738 | service="solano" 739 | commit="$TDDIUM_CURRENT_COMMIT" 740 | branch="$TDDIUM_CURRENT_BRANCH" 741 | build="$TDDIUM_TID" 742 | pr="$TDDIUM_PR_ID" 743 | 744 | elif [ "$GREENHOUSE" = "true" ]; 745 | then 746 | say "$e==>$x Greenhouse CI detected." 747 | # http://docs.greenhouseci.com/docs/environment-variables-files 748 | service="greenhouse" 749 | branch="$GREENHOUSE_BRANCH" 750 | build="$GREENHOUSE_BUILD_NUMBER" 751 | build_url=$(urlencode "$GREENHOUSE_BUILD_URL") 752 | pr="$GREENHOUSE_PULL_REQUEST" 753 | commit="$GREENHOUSE_COMMIT" 754 | search_in="$search_in $GREENHOUSE_EXPORT_DIR" 755 | 756 | elif [ "$GITLAB_CI" != "" ]; 757 | then 758 | say "$e==>$x GitLab CI detected." 759 | # http://doc.gitlab.com/ce/ci/variables/README.html 760 | service="gitlab" 761 | branch="${CI_BUILD_REF_NAME:-$CI_COMMIT_REF_NAME}" 762 | build="${CI_BUILD_ID:-$CI_JOB_ID}" 763 | remote_addr="${CI_BUILD_REPO:-$CI_REPOSITORY_URL}" 764 | commit="${CI_BUILD_REF:-$CI_COMMIT_SHA}" 765 | slug="${CI_PROJECT_PATH}" 766 | 767 | elif [ "$GITHUB_ACTION" != "" ]; 768 | then 769 | say "$e==>$x GitHub Actions detected." 770 | 771 | # https://github.com/features/actions 772 | service="github-actions" 773 | 774 | # https://help.github.com/en/articles/virtual-environments-for-github-actions#environment-variables 775 | branch="${GITHUB_REF#refs/heads/}" 776 | if [ "$GITHUB_HEAD_REF" != "" ]; 777 | then 778 | # PR refs are in the format: refs/pull/7/merge 779 | pr="${GITHUB_REF#refs/pull/}" 780 | pr="${pr%/merge}" 781 | branch="${GITHUB_HEAD_REF}" 782 | fi 783 | commit="${GITHUB_SHA}" 784 | slug="${GITHUB_REPOSITORY}" 785 | build="${GITHUB_RUN_ID}" 786 | build_url=$(urlencode "http://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}") 787 | 788 | elif [ "$SYSTEM_TEAMFOUNDATIONSERVERURI" != "" ]; 789 | then 790 | say "$e==>$x Azure Pipelines detected." 791 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=vsts 792 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&viewFallbackFrom=vsts&tabs=yaml 793 | service="azure_pipelines" 794 | commit="$BUILD_SOURCEVERSION" 795 | build="$BUILD_BUILDNUMBER" 796 | if [ -z "$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" ]; 797 | then 798 | pr="$SYSTEM_PULLREQUEST_PULLREQUESTID" 799 | else 800 | pr="$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" 801 | fi 802 | project="${SYSTEM_TEAMPROJECT}" 803 | server_uri="${SYSTEM_TEAMFOUNDATIONSERVERURI}" 804 | job="${BUILD_BUILDID}" 805 | branch="$BUILD_SOURCEBRANCHNAME" 806 | build_url=$(urlencode "${SYSTEM_TEAMFOUNDATIONSERVERURI}${SYSTEM_TEAMPROJECT}/_build/results?buildId=${BUILD_BUILDID}") 807 | elif [ "$CI" = "true" ] && [ "$BITBUCKET_BUILD_NUMBER" != "" ]; 808 | then 809 | say "$e==>$x Bitbucket detected." 810 | # https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html 811 | service="bitbucket" 812 | branch="$BITBUCKET_BRANCH" 813 | build="$BITBUCKET_BUILD_NUMBER" 814 | slug="$BITBUCKET_REPO_OWNER/$BITBUCKET_REPO_SLUG" 815 | job="$BITBUCKET_BUILD_NUMBER" 816 | pr="$BITBUCKET_PR_ID" 817 | commit="$BITBUCKET_COMMIT" 818 | # See https://jira.atlassian.com/browse/BCLOUD-19393 819 | if [ "${#commit}" = 12 ]; 820 | then 821 | commit=$(git rev-parse "$BITBUCKET_COMMIT") 822 | fi 823 | elif [ "$CI" = "true" ] && [ "$BUDDY" = "true" ]; 824 | then 825 | say "$e==>$x Buddy CI detected." 826 | # https://buddy.works/docs/pipelines/environment-variables 827 | service="buddy" 828 | branch="$BUDDY_EXECUTION_BRANCH" 829 | build="$BUDDY_EXECUTION_ID" 830 | build_url=$(urlencode "$BUDDY_EXECUTION_URL") 831 | commit="$BUDDY_EXECUTION_REVISION" 832 | pr="$BUDDY_EXECUTION_PULL_REQUEST_NO" 833 | tag="$BUDDY_EXECUTION_TAG" 834 | slug="$BUDDY_REPO_SLUG" 835 | 836 | elif [ "$CIRRUS_CI" != "" ]; 837 | then 838 | say "$e==>$x Cirrus CI detected." 839 | # https://cirrus-ci.org/guide/writing-tasks/#environment-variables 840 | service="cirrus-ci" 841 | slug="$CIRRUS_REPO_FULL_NAME" 842 | branch="$CIRRUS_BRANCH" 843 | pr="$CIRRUS_PR" 844 | commit="$CIRRUS_CHANGE_IN_REPO" 845 | build="$CIRRUS_TASK_ID" 846 | job="$CIRRUS_TASK_NAME" 847 | 848 | else 849 | say "${r}x>${x} No CI provider detected." 850 | say " Testing inside Docker? ${b}http://docs.codecov.io/docs/testing-with-docker${x}" 851 | say " Testing with Tox? ${b}https://docs.codecov.io/docs/python#section-testing-with-tox${x}" 852 | 853 | fi 854 | 855 | say " ${e}project root:${x} $git_root" 856 | 857 | # find branch, commit, repo from git command 858 | if [ "$GIT_BRANCH" != "" ]; 859 | then 860 | branch="$GIT_BRANCH" 861 | 862 | elif [ "$branch" = "" ]; 863 | then 864 | branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || hg branch 2>/dev/null || echo "") 865 | if [ "$branch" = "HEAD" ]; 866 | then 867 | branch="" 868 | fi 869 | fi 870 | 871 | if [ "$commit_o" = "" ]; 872 | then 873 | # merge commit -> actual commit 874 | mc= 875 | if [ -n "$pr" ] && [ "$pr" != false ]; 876 | then 877 | mc=$(git show --no-patch --format="%P" 2>/dev/null || echo "") 878 | fi 879 | if [[ "$mc" =~ ^[a-z0-9]{40}[[:space:]][a-z0-9]{40}$ ]]; 880 | then 881 | say " Fixing merge commit SHA" 882 | commit=$(echo "$mc" | cut -d' ' -f2) 883 | elif [ "$GIT_COMMIT" != "" ]; 884 | then 885 | commit="$GIT_COMMIT" 886 | elif [ "$commit" = "" ]; 887 | then 888 | commit=$(git log -1 --format="%H" 2>/dev/null || hg id -i --debug 2>/dev/null | tr -d '+' || echo "") 889 | fi 890 | else 891 | commit="$commit_o" 892 | fi 893 | 894 | if [ "$CODECOV_TOKEN" != "" ] && [ "$token" = "" ]; 895 | then 896 | say "${e}-->${x} token set from env" 897 | token="$CODECOV_TOKEN" 898 | fi 899 | 900 | if [ "$CODECOV_URL" != "" ] && [ "$url_o" = "" ]; 901 | then 902 | say "${e}-->${x} url set from env" 903 | url_o=$(echo "$CODECOV_URL" | sed -e 's/\/$//') 904 | fi 905 | 906 | if [ "$CODECOV_SLUG" != "" ]; 907 | then 908 | say "${e}-->${x} slug set from env" 909 | slug_o="$CODECOV_SLUG" 910 | 911 | elif [ "$slug" = "" ]; 912 | then 913 | if [ "$remote_addr" = "" ]; 914 | then 915 | remote_addr=$(git config --get remote.origin.url || hg paths default || echo '') 916 | fi 917 | if [ "$remote_addr" != "" ]; 918 | then 919 | if echo "$remote_addr" | grep -q "//"; then 920 | # https 921 | slug=$(echo "$remote_addr" | cut -d / -f 4,5 | sed -e 's/\.git$//') 922 | else 923 | # ssh 924 | slug=$(echo "$remote_addr" | cut -d : -f 2 | sed -e 's/\.git$//') 925 | fi 926 | fi 927 | if [ "$slug" = "/" ]; 928 | then 929 | slug="" 930 | fi 931 | fi 932 | 933 | yaml=$(cd "$git_root" && \ 934 | git ls-files "*codecov.yml" "*codecov.yaml" 2>/dev/null \ 935 | || hg locate "*codecov.yml" "*codecov.yaml" 2>/dev/null \ 936 | || cd $proj_root && find . -maxdepth 1 -type f -name '*codecov.y*ml' 2>/dev/null \ 937 | || echo '') 938 | yaml=$(echo "$yaml" | head -1) 939 | 940 | if [ "$yaml" != "" ]; 941 | then 942 | say " ${e}Yaml found at:${x} $yaml" 943 | if [[ "$yaml" != /* ]]; then 944 | # relative path for yaml file given, assume relative to the repo root 945 | yaml="$git_root/$yaml" 946 | fi 947 | config=$(parse_yaml "$yaml" || echo '') 948 | 949 | # TODO validate the yaml here 950 | 951 | if [ "$(echo "$config" | grep 'codecov_token="')" != "" ] && [ "$token" = "" ]; 952 | then 953 | say "${e}-->${x} token set from yaml" 954 | token="$(echo "$config" | grep 'codecov_token="' | sed -e 's/codecov_token="//' | sed -e 's/"\.*//')" 955 | fi 956 | 957 | if [ "$(echo "$config" | grep 'codecov_url="')" != "" ] && [ "$url_o" = "" ]; 958 | then 959 | say "${e}-->${x} url set from yaml" 960 | url_o="$(echo "$config" | grep 'codecov_url="' | sed -e 's/codecov_url="//' | sed -e 's/"\.*//')" 961 | fi 962 | 963 | if [ "$(echo "$config" | grep 'codecov_slug="')" != "" ] && [ "$slug_o" = "" ]; 964 | then 965 | say "${e}-->${x} slug set from yaml" 966 | slug_o="$(echo "$config" | grep 'codecov_slug="' | sed -e 's/codecov_slug="//' | sed -e 's/"\.*//')" 967 | fi 968 | else 969 | say " ${g}Yaml not found, that's ok! Learn more at${x} ${b}http://docs.codecov.io/docs/codecov-yaml${x}" 970 | 971 | fi 972 | 973 | if [ "$branch_o" != "" ]; 974 | then 975 | branch=$(urlencode "$branch_o") 976 | else 977 | branch=$(urlencode "$branch") 978 | fi 979 | 980 | query="branch=$branch\ 981 | &commit=$commit\ 982 | &build=$([ "$build_o" = "" ] && echo "$build" || echo "$build_o")\ 983 | &build_url=$build_url\ 984 | &name=$(urlencode "$name")\ 985 | &tag=$([ "$tag_o" = "" ] && echo "$tag" || echo "$tag_o")\ 986 | &slug=$([ "$slug_o" = "" ] && urlencode "$slug" || urlencode "$slug_o")\ 987 | &service=$service\ 988 | &flags=$flags\ 989 | &pr=$([ "$pr_o" = "" ] && echo "${pr##\#}" || echo "${pr_o##\#}")\ 990 | &job=$job" 991 | 992 | if [ ! -z "$project" ] && [ ! -z "$server_uri" ]; 993 | then 994 | query=$(echo "$query&project=$project&server_uri=$server_uri" | tr -d ' ') 995 | fi 996 | 997 | if [ "$parent" != "" ]; 998 | then 999 | query=$(echo "parent=$parent&$query" | tr -d ' ') 1000 | fi 1001 | 1002 | if [ "$ft_search" = "1" ]; 1003 | then 1004 | # detect bower comoponents location 1005 | bower_components="bower_components" 1006 | bower_rc=$(cd "$git_root" && cat .bowerrc 2>/dev/null || echo "") 1007 | if [ "$bower_rc" != "" ]; 1008 | then 1009 | bower_components=$(echo "$bower_rc" | tr -d '\n' | grep '"directory"' | cut -d'"' -f4 | sed -e 's/\/$//') 1010 | if [ "$bower_components" = "" ]; 1011 | then 1012 | bower_components="bower_components" 1013 | fi 1014 | fi 1015 | 1016 | # Swift Coverage 1017 | if [ "$ft_xcodellvm" = "1" ] && [ -d "$ddp" ]; 1018 | then 1019 | say "${e}==>${x} Processing Xcode reports via llvm-cov" 1020 | say " DerivedData folder: $ddp" 1021 | profdata_files=$(find "$ddp" -name '*.profdata' 2>/dev/null || echo '') 1022 | if [ "$profdata_files" != "" ]; 1023 | then 1024 | # xcode via profdata 1025 | if [ "$xp" = "" ]; 1026 | then 1027 | # xp=$(xcodebuild -showBuildSettings 2>/dev/null | grep -i "^\s*PRODUCT_NAME" | sed -e 's/.*= \(.*\)/\1/') 1028 | # say " ${e}->${x} Speed up Xcode processing by adding ${e}-J '$xp'${x}" 1029 | say " ${g}hint${x} Speed up Swift processing by using use ${g}-J 'AppName'${x} (regexp accepted)" 1030 | say " ${g}hint${x} This will remove Pods/ from your report. Also ${b}https://docs.codecov.io/docs/ignoring-paths${x}" 1031 | fi 1032 | while read -r profdata; 1033 | do 1034 | if [ "$profdata" != "" ]; 1035 | then 1036 | swiftcov "$profdata" "$xp" 1037 | fi 1038 | done <<< "$profdata_files" 1039 | else 1040 | say " ${e}->${x} No Swift coverage found" 1041 | fi 1042 | 1043 | # Obj-C Gcov Coverage 1044 | if [ "$ft_gcov" = "1" ]; 1045 | then 1046 | say " ${e}->${x} Running $gcov_exe for Obj-C" 1047 | if [ "$ft_gcovout" = "0" ]; 1048 | then 1049 | # suppress gcov output 1050 | bash -c "find $ddp -type f -name '*.gcda' $gcov_include $gcov_ignore -exec $gcov_exe -p $gcov_arg {} +" >/dev/null 2>&1 || true 1051 | else 1052 | bash -c "find $ddp -type f -name '*.gcda' $gcov_include $gcov_ignore -exec $gcov_exe -p $gcov_arg {} +" || true 1053 | fi 1054 | fi 1055 | fi 1056 | 1057 | if [ "$ft_xcodeplist" = "1" ] && [ -d "$ddp" ]; 1058 | then 1059 | say "${e}==>${x} Processing Xcode plists" 1060 | plists_files=$(find "$ddp" -name '*.xccoverage' 2>/dev/null || echo '') 1061 | if [ "$plists_files" != "" ]; 1062 | then 1063 | while read -r plist; 1064 | do 1065 | if [ "$plist" != "" ]; 1066 | then 1067 | say " ${g}Found${x} plist file at $plist" 1068 | plutil -convert xml1 -o "$(basename "$plist").plist" -- $plist 1069 | fi 1070 | done <<< "$plists_files" 1071 | fi 1072 | fi 1073 | 1074 | # Gcov Coverage 1075 | if [ "$ft_gcov" = "1" ]; 1076 | then 1077 | say "${e}==>${x} Running $gcov_exe in $proj_root ${e}(disable via -X gcov)${x}" 1078 | if [ "$ft_gcovout" = "0" ]; 1079 | then 1080 | # suppress gcov output 1081 | bash -c "find $proj_root -type f -name '*.gcno' $gcov_include $gcov_ignore -exec $gcov_exe -pb $gcov_arg {} +" >/dev/null 2>&1 || true 1082 | else 1083 | bash -c "find $proj_root -type f -name '*.gcno' $gcov_include $gcov_ignore -exec $gcov_exe -pb $gcov_arg {} +" || true 1084 | fi 1085 | else 1086 | say "${e}==>${x} gcov disabled" 1087 | fi 1088 | 1089 | # Python Coverage 1090 | if [ "$ft_coveragepy" = "1" ]; 1091 | then 1092 | if [ ! -f coverage.xml ]; 1093 | then 1094 | if which coverage >/dev/null 2>&1; 1095 | then 1096 | say "${e}==>${x} Python coveragepy exists ${e}disable via -X coveragepy${x}" 1097 | 1098 | dotcoverage=$(find "$git_root" -name '.coverage' -or -name '.coverage.*' | head -1 || echo '') 1099 | if [ "$dotcoverage" != "" ]; 1100 | then 1101 | cd "$(dirname "$dotcoverage")" 1102 | if [ ! -f .coverage ]; 1103 | then 1104 | say " ${e}->${x} Running coverage combine" 1105 | coverage combine -a 1106 | fi 1107 | say " ${e}->${x} Running coverage xml" 1108 | if [ "$(coverage xml -i)" != "No data to report." ]; 1109 | then 1110 | files="$files 1111 | $PWD/coverage.xml" 1112 | else 1113 | say " ${r}No data to report.${x}" 1114 | fi 1115 | cd "$proj_root" 1116 | else 1117 | say " ${r}No .coverage file found.${x}" 1118 | fi 1119 | else 1120 | say "${e}==>${x} Python coveragepy not found" 1121 | fi 1122 | fi 1123 | else 1124 | say "${e}==>${x} Python coveragepy disabled" 1125 | fi 1126 | 1127 | if [ "$search_in_o" != "" ]; 1128 | then 1129 | # location override 1130 | search_in="$search_in_o" 1131 | fi 1132 | 1133 | say "$e==>$x Searching for coverage reports in:" 1134 | for _path in $search_in 1135 | do 1136 | say " ${g}+${x} $_path" 1137 | done 1138 | 1139 | patterns="find $search_in \( \ 1140 | -name vendor \ 1141 | -or -name htmlcov \ 1142 | -or -name virtualenv \ 1143 | -or -name js/generated/coverage \ 1144 | -or -name .virtualenv \ 1145 | -or -name virtualenvs \ 1146 | -or -name .virtualenvs \ 1147 | -or -name .env \ 1148 | -or -name .envs \ 1149 | -or -name env \ 1150 | -or -name .yarn-cache \ 1151 | -or -name envs \ 1152 | -or -name .venv \ 1153 | -or -name .venvs \ 1154 | -or -name venv \ 1155 | -or -name venvs \ 1156 | -or -name .git \ 1157 | -or -name .hg \ 1158 | -or -name .tox \ 1159 | -or -name __pycache__ \ 1160 | -or -name '.egg-info*' \ 1161 | -or -name '$bower_components' \ 1162 | -or -name node_modules \ 1163 | -or -name 'conftest_*.c.gcov' \ 1164 | \) -prune -or \ 1165 | -type f \( -name '*coverage*.*' \ 1166 | -or -name 'nosetests.xml' \ 1167 | -or -name 'jacoco*.xml' \ 1168 | -or -name 'clover.xml' \ 1169 | -or -name 'report.xml' \ 1170 | -or -name '*.codecov.*' \ 1171 | -or -name 'codecov.*' \ 1172 | -or -name 'cobertura.xml' \ 1173 | -or -name 'excoveralls.json' \ 1174 | -or -name 'luacov.report.out' \ 1175 | -or -name 'coverage-final.json' \ 1176 | -or -name 'naxsi.info' \ 1177 | -or -name 'lcov.info' \ 1178 | -or -name 'lcov.dat' \ 1179 | -or -name '*.lcov' \ 1180 | -or -name '*.clover' \ 1181 | -or -name 'cover.out' \ 1182 | -or -name 'gcov.info' \ 1183 | -or -name '*.gcov' \ 1184 | -or -name '*.lst' \ 1185 | $include_cov \) \ 1186 | $exclude_cov \ 1187 | -not -name '*.profdata' \ 1188 | -not -name 'coverage-summary.json' \ 1189 | -not -name 'phpunit-code-coverage.xml' \ 1190 | -not -name '*/classycle/report.xml' \ 1191 | -not -name 'remapInstanbul.coverage*.json' \ 1192 | -not -name 'phpunit-coverage.xml' \ 1193 | -not -name '*codecov.yml' \ 1194 | -not -name '*.serialized' \ 1195 | -not -name '.coverage*' \ 1196 | -not -name '.*coveragerc' \ 1197 | -not -name '*.sh' \ 1198 | -not -name '*.bat' \ 1199 | -not -name '*.ps1' \ 1200 | -not -name '*.env' \ 1201 | -not -name '*.cmake' \ 1202 | -not -name '*.dox' \ 1203 | -not -name '*.ec' \ 1204 | -not -name '*.rst' \ 1205 | -not -name '*.h' \ 1206 | -not -name '*.scss' \ 1207 | -not -name '*.o' \ 1208 | -not -name '*.proto' \ 1209 | -not -name '*.sbt' \ 1210 | -not -name '*.xcoverage.*' \ 1211 | -not -name '*.gz' \ 1212 | -not -name '*.conf' \ 1213 | -not -name '*.p12' \ 1214 | -not -name '*.csv' \ 1215 | -not -name '*.rsp' \ 1216 | -not -name '*.m4' \ 1217 | -not -name '*.pem' \ 1218 | -not -name '*~' \ 1219 | -not -name '*.exe' \ 1220 | -not -name '*.am' \ 1221 | -not -name '*.template' \ 1222 | -not -name '*.cp' \ 1223 | -not -name '*.bw' \ 1224 | -not -name '*.crt' \ 1225 | -not -name '*.log' \ 1226 | -not -name '*.cmake' \ 1227 | -not -name '*.pth' \ 1228 | -not -name '*.in' \ 1229 | -not -name '*.jar*' \ 1230 | -not -name '*.pom*' \ 1231 | -not -name '*.png' \ 1232 | -not -name '*.jpg' \ 1233 | -not -name '*.sql' \ 1234 | -not -name '*.jpeg' \ 1235 | -not -name '*.svg' \ 1236 | -not -name '*.gif' \ 1237 | -not -name '*.csv' \ 1238 | -not -name '*.snapshot' \ 1239 | -not -name '*.mak*' \ 1240 | -not -name '*.bash' \ 1241 | -not -name '*.data' \ 1242 | -not -name '*.py' \ 1243 | -not -name '*.class' \ 1244 | -not -name '*.xcconfig' \ 1245 | -not -name '*.ec' \ 1246 | -not -name '*.coverage' \ 1247 | -not -name '*.pyc' \ 1248 | -not -name '*.cfg' \ 1249 | -not -name '*.egg' \ 1250 | -not -name '*.ru' \ 1251 | -not -name '*.css' \ 1252 | -not -name '*.less' \ 1253 | -not -name '*.pyo' \ 1254 | -not -name '*.whl' \ 1255 | -not -name '*.html' \ 1256 | -not -name '*.ftl' \ 1257 | -not -name '*.erb' \ 1258 | -not -name '*.rb' \ 1259 | -not -name '*.js' \ 1260 | -not -name '*.jade' \ 1261 | -not -name '*.db' \ 1262 | -not -name '*.md' \ 1263 | -not -name '*.cpp' \ 1264 | -not -name '*.gradle' \ 1265 | -not -name '*.tar.tz' \ 1266 | -not -name '*.scss' \ 1267 | -not -name 'include.lst' \ 1268 | -not -name 'fullLocaleNames.lst' \ 1269 | -not -name 'inputFiles.lst' \ 1270 | -not -name 'createdFiles.lst' \ 1271 | -not -name 'scoverage.measurements.*' \ 1272 | -not -name 'test_*_coverage.txt' \ 1273 | -not -name 'testrunner-coverage*' \ 1274 | -print 2>/dev/null" 1275 | files=$(eval "$patterns" || echo '') 1276 | 1277 | elif [ "$include_cov" != "" ]; 1278 | then 1279 | files=$(eval "find $search_in -type f \( ${include_cov:5} \)$exclude_cov 2>/dev/null" || echo '') 1280 | fi 1281 | 1282 | num_of_files=$(echo "$files" | wc -l | tr -d ' ') 1283 | if [ "$num_of_files" != '' ] && [ "$files" != '' ]; 1284 | then 1285 | say " ${e}->${x} Found $num_of_files reports" 1286 | fi 1287 | 1288 | # no files found 1289 | if [ "$files" = "" ]; 1290 | then 1291 | say "${r}-->${x} No coverage report found." 1292 | say " Please visit ${b}http://docs.codecov.io/docs/supported-languages${x}" 1293 | exit ${exit_with}; 1294 | fi 1295 | 1296 | if [ "$ft_network" == "1" ]; 1297 | then 1298 | say "${e}==>${x} Detecting git/mercurial file structure" 1299 | network=$(cd "$git_root" && git ls-files 2>/dev/null || hg locate 2>/dev/null || echo "") 1300 | if [ "$network" = "" ]; 1301 | then 1302 | network=$(find "$git_root" \( \ 1303 | -name virtualenv \ 1304 | -name .virtualenv \ 1305 | -name virtualenvs \ 1306 | -name .virtualenvs \ 1307 | -name '*.png' \ 1308 | -name '*.gif' \ 1309 | -name '*.jpg' \ 1310 | -name '*.jpeg' \ 1311 | -name '*.md' \ 1312 | -name .env \ 1313 | -name .envs \ 1314 | -name env \ 1315 | -name envs \ 1316 | -name .venv \ 1317 | -name .venvs \ 1318 | -name venv \ 1319 | -name venvs \ 1320 | -name .git \ 1321 | -name .egg-info \ 1322 | -name shunit2-2.1.6 \ 1323 | -name vendor \ 1324 | -name __pycache__ \ 1325 | -name node_modules \ 1326 | -path '*/$bower_components/*' \ 1327 | -path '*/target/delombok/*' \ 1328 | -path '*/build/lib/*' \ 1329 | -path '*/js/generated/coverage/*' \ 1330 | \) -prune -or \ 1331 | -type f -print 2>/dev/null || echo '') 1332 | fi 1333 | 1334 | if [ "$prefix_o" != "" ]; 1335 | then 1336 | network=$(echo "$network" | awk "{print \"$prefix_o/\"\$0}") 1337 | fi 1338 | fi 1339 | 1340 | upload_file=`mktemp /tmp/codecov.XXXXXX` 1341 | adjustments_file=`mktemp /tmp/codecov.adjustments.XXXXXX` 1342 | 1343 | cleanup() { 1344 | rm -f $upload_file $adjustments_file $upload_file.gz 1345 | } 1346 | 1347 | trap cleanup INT ABRT TERM 1348 | 1349 | if [ "$env" != "" ]; 1350 | then 1351 | inc_env="" 1352 | say "${e}==>${x} Appending build variables" 1353 | for varname in $(echo "$env" | tr ',' ' ') 1354 | do 1355 | if [ "$varname" != "" ]; 1356 | then 1357 | say " ${g}+${x} $varname" 1358 | inc_env="${inc_env}${varname}=$(eval echo "\$${varname}") 1359 | " 1360 | fi 1361 | done 1362 | 1363 | echo "$inc_env<<<<<< ENV" >> $upload_file 1364 | fi 1365 | 1366 | # Append git file list 1367 | # write discovered yaml location 1368 | echo "$yaml" >> $upload_file 1369 | if [ "$ft_network" == "1" ]; 1370 | then 1371 | i="woff|eot|otf" # fonts 1372 | i="$i|gif|png|jpg|jpeg|psd" # images 1373 | i="$i|ptt|pptx|numbers|pages|md|txt|xlsx|docx|doc|pdf|csv" # docs 1374 | i="$i|yml|yaml|.gitignore" # supporting docs 1375 | 1376 | if [ "$ft_html" != "1" ]; 1377 | then 1378 | i="$i|html" 1379 | fi 1380 | 1381 | echo "$network" | grep -vwE "($i)$" >> $upload_file 1382 | fi 1383 | echo "<<<<<< network" >> $upload_file 1384 | 1385 | fr=0 1386 | say "${e}==>${x} Reading reports" 1387 | while IFS='' read -r file; 1388 | do 1389 | # read the coverage file 1390 | if [ "$(echo "$file" | tr -d ' ')" != '' ]; 1391 | then 1392 | if [ -f "$file" ]; 1393 | then 1394 | report_len=$(wc -c < "$file") 1395 | if [ "$report_len" -ne 0 ]; 1396 | then 1397 | say " ${g}+${x} $file ${e}bytes=$(echo "$report_len" | tr -d ' ')${x}" 1398 | # append to to upload 1399 | _filename=$(basename "$file") 1400 | if [ "${_filename##*.}" = 'gcov' ]; 1401 | then 1402 | echo "# path=$(echo "$file.reduced" | sed "s|^$git_root/||")" >> $upload_file 1403 | # get file name 1404 | head -1 "$file" >> $upload_file 1405 | # 1. remove source code 1406 | # 2. remove ending bracket lines 1407 | # 3. remove whitespace 1408 | # 4. remove contextual lines 1409 | # 5. remove function names 1410 | awk -F': *' '{print $1":"$2":"}' "$file" \ 1411 | | sed '\/: *} *$/d' \ 1412 | | sed 's/^ *//' \ 1413 | | sed '/^-/d' \ 1414 | | sed 's/^function.*/func/' >> $upload_file 1415 | else 1416 | echo "# path=$(echo "$file" | sed "s|^$git_root/||")" >> $upload_file 1417 | cat "$file" >> $upload_file 1418 | fi 1419 | echo "<<<<<< EOF" >> $upload_file 1420 | fr=1 1421 | if [ "$clean" = "1" ]; 1422 | then 1423 | rm "$file" 1424 | fi 1425 | else 1426 | say " ${r}-${x} Skipping empty file $file" 1427 | fi 1428 | else 1429 | say " ${r}-${x} file not found at $file" 1430 | fi 1431 | fi 1432 | done <<< "$(echo -e "$files")" 1433 | 1434 | if [ "$fr" = "0" ]; 1435 | then 1436 | say "${r}-->${x} No coverage data found." 1437 | say " Please visit ${b}http://docs.codecov.io/docs/supported-languages${x}" 1438 | say " search for your projects language to learn how to collect reports." 1439 | exit ${exit_with}; 1440 | fi 1441 | 1442 | if [ "$ft_fix" = "1" ]; 1443 | then 1444 | say "${e}==>${x} Appending adjustments" 1445 | say " ${b}https://docs.codecov.io/docs/fixing-reports${x}" 1446 | 1447 | empty_line='^[[:space:]]*$' 1448 | # // 1449 | syntax_comment='^[[:space:]]*//.*' 1450 | # /* or */ 1451 | syntax_comment_block='^[[:space:]]*(\/\*|\*\/)[[:space:]]*$' 1452 | # { or } 1453 | syntax_bracket='^[[:space:]]*[\{\}][[:space:]]*(//.*)?$' 1454 | # [ or ] 1455 | syntax_list='^[[:space:]]*[][][[:space:]]*(//.*)?$' 1456 | 1457 | skip_dirs="-not -path '*/$bower_components/*' \ 1458 | -not -path '*/node_modules/*'" 1459 | 1460 | cut_and_join() { 1461 | awk 'BEGIN { FS=":" } 1462 | $3 ~ /\/\*/ || $3 ~ /\*\// { print $0 ; next } 1463 | $1!=key { if (key!="") print out ; key=$1 ; out=$1":"$2 ; next } 1464 | { out=out","$2 } 1465 | END { print out }' 2>/dev/null 1466 | } 1467 | 1468 | if echo "$network" | grep -m1 '.kt$' 1>/dev/null; 1469 | then 1470 | # skip brackets and comments 1471 | find "$git_root" -type f \ 1472 | -name '*.kt' \ 1473 | -exec \ 1474 | grep -nIHE -e $syntax_bracket \ 1475 | -e $syntax_comment_block {} \; \ 1476 | | cut_and_join \ 1477 | >> $adjustments_file \ 1478 | || echo '' 1479 | 1480 | # last line in file 1481 | find "$git_root" -type f \ 1482 | -name '*.kt' -exec \ 1483 | wc -l {} \; \ 1484 | | while read l; do echo "EOF: $l"; done \ 1485 | 2>/dev/null \ 1486 | >> $adjustments_file \ 1487 | || echo '' 1488 | 1489 | fi 1490 | 1491 | if echo "$network" | grep -m1 '.go$' 1>/dev/null; 1492 | then 1493 | # skip empty lines, comments, and brackets 1494 | find "$git_root" -not -path '*/vendor/*' \ 1495 | -type f \ 1496 | -name '*.go' \ 1497 | -exec \ 1498 | grep -nIHE \ 1499 | -e $empty_line \ 1500 | -e $syntax_comment \ 1501 | -e $syntax_comment_block \ 1502 | -e $syntax_bracket \ 1503 | {} \; \ 1504 | | cut_and_join \ 1505 | >> $adjustments_file \ 1506 | || echo '' 1507 | fi 1508 | 1509 | if echo "$network" | grep -m1 '.dart$' 1>/dev/null; 1510 | then 1511 | # skip brackets 1512 | find "$git_root" -type f \ 1513 | -name '*.dart' \ 1514 | -exec \ 1515 | grep -nIHE \ 1516 | -e $syntax_bracket \ 1517 | {} \; \ 1518 | | cut_and_join \ 1519 | >> $adjustments_file \ 1520 | || echo '' 1521 | fi 1522 | 1523 | if echo "$network" | grep -m1 '.php$' 1>/dev/null; 1524 | then 1525 | # skip empty lines, comments, and brackets 1526 | find "$git_root" -not -path "*/vendor/*" \ 1527 | -type f \ 1528 | -name '*.php' \ 1529 | -exec \ 1530 | grep -nIHE \ 1531 | -e $syntax_list \ 1532 | -e $syntax_bracket \ 1533 | -e '^[[:space:]]*\);[[:space:]]*(//.*)?$' \ 1534 | {} \; \ 1535 | | cut_and_join \ 1536 | >> $adjustments_file \ 1537 | || echo '' 1538 | fi 1539 | 1540 | if echo "$network" | grep -m1 '\(.cpp\|.h\|.cxx\|.c\|.hpp\|.m\|.swift\)$' 1>/dev/null; 1541 | then 1542 | # skip brackets 1543 | find "$git_root" -type f \ 1544 | $skip_dirs \ 1545 | \( \ 1546 | -name '*.h' \ 1547 | -or -name '*.cpp' \ 1548 | -or -name '*.cxx' \ 1549 | -or -name '*.m' \ 1550 | -or -name '*.c' \ 1551 | -or -name '*.hpp' \ 1552 | -or -name '*.swift' \ 1553 | \) -exec \ 1554 | grep -nIHE \ 1555 | -e $empty_line \ 1556 | -e $syntax_bracket \ 1557 | -e '// LCOV_EXCL' \ 1558 | {} \; \ 1559 | | cut_and_join \ 1560 | >> $adjustments_file \ 1561 | || echo '' 1562 | 1563 | # skip brackets 1564 | find "$git_root" -type f \ 1565 | $skip_dirs \ 1566 | \( \ 1567 | -name '*.h' \ 1568 | -or -name '*.cpp' \ 1569 | -or -name '*.cxx' \ 1570 | -or -name '*.m' \ 1571 | -or -name '*.c' \ 1572 | -or -name '*.hpp' \ 1573 | -or -name '*.swift' \ 1574 | \) -exec \ 1575 | grep -nIH '// LCOV_EXCL' \ 1576 | {} \; \ 1577 | >> $adjustments_file \ 1578 | || echo '' 1579 | 1580 | fi 1581 | 1582 | found=$(cat $adjustments_file | tr -d ' ') 1583 | 1584 | if [ "$found" != "" ]; 1585 | then 1586 | say " ${g}+${x} Found adjustments" 1587 | echo "# path=fixes" >> $upload_file 1588 | cat $adjustments_file >> $upload_file 1589 | echo "<<<<<< EOF" >> $upload_file 1590 | rm -rf $adjustments_file 1591 | else 1592 | say " ${e}->${x} No adjustments found" 1593 | fi 1594 | fi 1595 | 1596 | if [ "$url_o" != "" ]; 1597 | then 1598 | url="$url_o" 1599 | fi 1600 | 1601 | if [ "$dump" != "0" ]; 1602 | then 1603 | # trim whitespace from query 1604 | say " ${e}->${x} Dumping upload file (no upload)" 1605 | echo "$url/upload/v4?$(echo "package=bash-$VERSION&token=$token&$query" | tr -d ' ')" 1606 | cat $upload_file 1607 | else 1608 | 1609 | say "${e}==>${x} Gzipping contents" 1610 | gzip -nf9 $upload_file 1611 | 1612 | query=$(echo "${query}" | tr -d ' ') 1613 | say "${e}==>${x} Uploading reports" 1614 | say " ${e}url:${x} $url" 1615 | say " ${e}query:${x} $query" 1616 | 1617 | # Full query without token (to display on terminal output) 1618 | queryNoToken=$(echo "package=bash-$VERSION&token=secret&$query" | tr -d ' ') 1619 | # now add token to query 1620 | query=$(echo "package=bash-$VERSION&token=$token&$query" | tr -d ' ') 1621 | 1622 | if [ "$ft_s3" = "1" ]; 1623 | then 1624 | i="0" 1625 | while [ $i -lt 4 ] 1626 | do 1627 | i=$[$i+1] 1628 | say " ${e}->${x} Pinging Codecov" 1629 | say "$url/upload/v4?$queryNoToken" 1630 | res=$(curl $curl_s -X POST $curlargs $cacert \ 1631 | -H 'X-Reduced-Redundancy: false' \ 1632 | -H 'X-Content-Type: application/x-gzip' \ 1633 | "$url/upload/v4?$query" || true) 1634 | # a good replay is "https://codecov.io" + "\n" + "https://codecov.s3.amazonaws.com/..." 1635 | status=$(echo "$res" | head -1 | grep 'HTTP ' | cut -d' ' -f2) 1636 | if [ "$status" = "" ]; 1637 | then 1638 | s3target=$(echo "$res" | sed -n 2p) 1639 | say " ${e}->${x} Uploading" 1640 | 1641 | 1642 | s3=$(curl $curl_s -fiX PUT $curlawsargs \ 1643 | --data-binary @$upload_file.gz \ 1644 | -H 'Content-Type: application/x-gzip' \ 1645 | -H 'Content-Encoding: gzip' \ 1646 | "$s3target" || true) 1647 | 1648 | 1649 | if [ "$s3" != "" ]; 1650 | then 1651 | say " ${g}->${x} View reports at ${b}$(echo "$res" | sed -n 1p)${x}" 1652 | exit 0 1653 | else 1654 | say " ${r}X>${x} Failed to upload" 1655 | fi 1656 | elif [ "$status" = "400" ]; 1657 | then 1658 | # 400 Error 1659 | say "${g}${res}${x}" 1660 | exit ${exit_with} 1661 | fi 1662 | say " ${e}->${x} Sleeping for 30s and trying again..." 1663 | sleep 30 1664 | done 1665 | fi 1666 | 1667 | say " ${e}->${x} Uploading to Codecov" 1668 | i="0" 1669 | while [ $i -lt 4 ] 1670 | do 1671 | i=$[$i+1] 1672 | 1673 | res=$(curl $curl_s -X POST $curlargs $cacert \ 1674 | --data-binary @$upload_file.gz \ 1675 | -H 'Content-Type: text/plain' \ 1676 | -H 'Content-Encoding: gzip' \ 1677 | -H 'X-Content-Encoding: gzip' \ 1678 | -H 'Accept: text/plain' \ 1679 | "$url/upload/v2?$query" || echo 'HTTP 500') 1680 | # HTTP 200 1681 | # http://.... 1682 | status=$(echo "$res" | head -1 | cut -d' ' -f2) 1683 | if [ "$status" = "" ]; 1684 | then 1685 | say " View reports at ${b}$(echo "$res" | head -2 | tail -1)${x}" 1686 | exit 0 1687 | 1688 | elif [ "${status:0:1}" = "5" ]; 1689 | then 1690 | say " ${e}->${x} Sleeping for 30s and trying again..." 1691 | sleep 30 1692 | 1693 | else 1694 | say " ${g}${res}${x}" 1695 | exit 0 1696 | exit ${exit_with} 1697 | fi 1698 | 1699 | done 1700 | 1701 | say " ${r}X> Failed to upload coverage reports${x}" 1702 | fi 1703 | 1704 | exit ${exit_with} 1705 | --------------------------------------------------------------------------------