Skip to content

Commit

Permalink
Merge pull request #362 from CircleCI-Public/experimental-context-api
Browse files Browse the repository at this point in the history
[CIRCLE-24198] Add commands for managing contexts
  • Loading branch information
marcomorain authored Feb 26, 2020
2 parents 7f7e4e2 + 2e3457d commit 9ebc42b
Show file tree
Hide file tree
Showing 11 changed files with 766 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ jobs:
CGO_ENABLED: 1
steps:
- checkout
- run:
# Uploading to codecov has been failing due to HTTP 2.0 issues.
# https://app.circleci.com/jobs/github/CircleCI-Public/circleci-cli/6480
# curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
# The issue seems to be on the server-side, so force HTTP 1.1
name: 'cURL: Force HTTP 1.1'
command: echo '--http1.1' >> ~/.curlrc
- gomod
- run: make cover
- store_artifacts:
Expand Down
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ func getOrganization(cl *client.Client, organizationName string, organizationVcs
request.SetToken(cl.Token)

request.Var("organizationName", organizationName)
request.Var("organizationVcs", organizationVcs)
request.Var("organizationVcs", strings.ToUpper(organizationVcs))

err := cl.Run(request, &response)

Expand Down
13 changes: 13 additions & 0 deletions api/api_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package api_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestApi(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Api Suite")
}
288 changes: 288 additions & 0 deletions api/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
// Go functions that expose the Context-related calls in the GraphQL API.
package api

import (
"fmt"
"strings"

"github.com/CircleCI-Public/circleci-cli/client"
"github.com/pkg/errors"
)

type Resource struct {
Variable string
CreatedAt string
TruncatedValue string
}

type CircleCIContext struct {
ID string
Name string
CreatedAt string
Groups struct {
}
Resources []Resource
}

type ContextsQueryResponse struct {
Organization struct {
Id string
Contexts struct {
Edges []struct {
Node CircleCIContext
}
}
}
}

func improveVcsTypeError(err error) error {
if responseErrors, ok := err.(client.ResponseErrorsCollection); ok {
if len(responseErrors) > 0 {
details := responseErrors[0].Extensions
if details.EnumType == "VCSType" {
allowedValues := strings.ToLower(strings.Join(details.AllowedValues[:], ", "))
return fmt.Errorf("Invalid vcs-type '%s' provided, expected one of %s", strings.ToLower(details.Value), allowedValues)
}
}
}
return err
}

func CreateContext(cl *client.Client, vcsType, orgName, contextName string) error {

org, err := getOrganization(cl, orgName, vcsType)

if err != nil {
return err
}

query := `
mutation CreateContext($input: CreateContextInput!) {
createContext(input: $input) {
...CreateButton
}
}
fragment CreateButton on CreateContextPayload {
error {
type
}
}
`

var input struct {
OwnerId string `json:"ownerId"`
OwnerType string `json:"ownerType"`
ContextName string `json:"contextName"`
}

input.OwnerId = org.Organization.ID
input.OwnerType = "ORGANIZATION"
input.ContextName = contextName

request := client.NewRequest(query)
request.SetToken(cl.Token)
request.Var("input", input)

var response struct {
CreateContext struct {
Error struct {
Type string
}
}
}

if err = cl.Run(request, &response); err != nil {
return improveVcsTypeError(err)
}

if response.CreateContext.Error.Type != "" {
return fmt.Errorf("Error creating context: %s", response.CreateContext.Error.Type)
}

return nil
}

func ListContexts(cl *client.Client, orgName, vcsType string) (*ContextsQueryResponse, error) {

query := `
query ContextsQuery($orgName: String!, $vcsType: VCSType!) {
organization(name: $orgName, vcsType: $vcsType) {
id
contexts {
edges {
node {
...Context
}
}
}
}
}
fragment Context on Context {
id
name
createdAt
groups {
edges {
node {
...SecurityGroups
}
}
}
resources {
...EnvVars
}
}
fragment EnvVars on EnvironmentVariable {
variable
createdAt
truncatedValue
}
fragment SecurityGroups on Group {
id
name
}
`

request := client.NewRequest(query)
request.SetToken(cl.Token)

request.Var("orgName", orgName)
request.Var("vcsType", strings.ToUpper(vcsType))

var response ContextsQueryResponse
err := cl.Run(request, &response)
return &response, errors.Wrapf(improveVcsTypeError(err), "failed to load context list")
}

func DeleteEnvironmentVariable(cl *client.Client, contextId, variableName string) error {
query := `
mutation DeleteEnvVar($input: RemoveEnvironmentVariableInput!) {
removeEnvironmentVariable(input: $input) {
context {
id
resources {
...EnvVars
}
}
}
}
fragment EnvVars on EnvironmentVariable {
variable
createdAt
truncatedValue
}`

var input struct {
ContextId string `json:"contextId"`
Variable string `json:"variable"`
}

input.ContextId = contextId
input.Variable = variableName

request := client.NewRequest(query)
request.SetToken(cl.Token)
request.Var("input", input)

var response struct {
RemoveEnvironmentVariable struct {
Context CircleCIContext
}
}

err := cl.Run(request, &response)
return errors.Wrap(improveVcsTypeError(err), "failed to delete environment varaible")
}

func StoreEnvironmentVariable(cl *client.Client, contextId, variableName, secretValue string) error {
query := `
mutation CreateEnvVar($input: StoreEnvironmentVariableInput!) {
storeEnvironmentVariable(input: $input) {
context {
id
resources {
...EnvVars
}
}
...CreateEnvVarButton
}
}
fragment EnvVars on EnvironmentVariable {
variable
createdAt
truncatedValue
}
fragment CreateEnvVarButton on StoreEnvironmentVariablePayload {
error {
type
}
}`

request := client.NewRequest(query)
request.SetToken(cl.Token)

var input struct {
ContextId string `json:"contextId"`
Variable string `json:"variable"`
Value string `json:"value"`
}

input.ContextId = contextId
input.Variable = variableName
input.Value = secretValue

request.Var("input", input)

var response struct {
StoreEnvironmentVariable struct {
Context CircleCIContext
Error struct {
Type string
}
}
}

if err := cl.Run(request, &response); err != nil {
return errors.Wrap(improveVcsTypeError(err), "failed to store environment varaible in context")
}

if response.StoreEnvironmentVariable.Error.Type != "" {
return fmt.Errorf("Error storing environment variable: %s", response.StoreEnvironmentVariable.Error.Type)
}

return nil
}

func DeleteContext(cl *client.Client, contextId string) error {
query := `
mutation DeleteContext($input: DeleteContextInput!) {
deleteContext(input: $input) {
clientMutationId
}
}`

request := client.NewRequest(query)
request.SetToken(cl.Token)

var input struct {
ContextId string `json:"contextId"`
}

input.ContextId = contextId
request.Var("input", input)

var response struct {
}

err := cl.Run(request, &response)

return errors.Wrap(improveVcsTypeError(err), "failed to delete context")
}
Loading

0 comments on commit 9ebc42b

Please sign in to comment.