Skip to content

Commit

Permalink
refactor: Moved the orb validation in a orb API module
Browse files Browse the repository at this point in the history
  • Loading branch information
JulesFaucherre committed Aug 8, 2023
1 parent 5b87507 commit 3dd9730
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 132 deletions.
127 changes: 0 additions & 127 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,133 +512,6 @@ func WhoamiQuery(cl *graphql.Client) (*WhoamiResponse, error) {
return &response, nil
}

// OrbQuery validated and processes an orb.
func OrbQuery(cl *graphql.Client, configPath string, ownerId string) (*ConfigResponse, error) {
var response OrbConfigResponse

config, err := loadYaml(configPath)
if err != nil {
return nil, err
}

request, err := makeOrbRequest(cl, config, ownerId)
if err != nil {
return nil, err
}

err = cl.Run(request, &response)
if err != nil {
return nil, errors.Wrap(err, "Unable to validate config")
}

if len(response.OrbConfig.ConfigResponse.Errors) > 0 {
return nil, response.OrbConfig.ConfigResponse.Errors
}

return &response.OrbConfig.ConfigResponse, nil
}

func makeOrbRequest(cl *graphql.Client, configContent string, ownerId string) (*graphql.Request, error) {
handlesOwner := orbQueryHandleOwnerId(cl)

if handlesOwner {
query := `
query ValidateOrb ($config: String!, $owner: UUID) {
orbConfig(orbYaml: $config, ownerId: $owner) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`

request := graphql.NewRequest(query)
request.Var("config", configContent)

if ownerId != "" {
request.Var("owner", ownerId)
}

request.SetToken(cl.Token)
return request, nil
}

if ownerId != "" {
return nil, errors.Errorf("Your version of Server does not support validating orbs that refer to other private orbs. Please see the README for more information on server compatibility: https://github.com/CircleCI-Public/circleci-cli#server-compatibility")
}
query := `
query ValidateOrb ($config: String!) {
orbConfig(orbYaml: $config) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`

request := graphql.NewRequest(query)
request.Var("config", configContent)

request.SetToken(cl.Token)
return request, nil
}

type OrbIntrospectionResponse struct {
Schema struct {
Query struct {
Fields []struct {
Name string `json:"name"`
Args []struct {
Name string `json:"name"`
} `json:"args"`
} `json:"fields"`
} `json:"queryType"`
} `json:"__schema"`
}

func orbQueryHandleOwnerId(cl *graphql.Client) bool {
query := `
query ValidateOrb {
__schema {
queryType {
fields(includeDeprecated: true) {
name
args {
name
__typename
type {
name
}
}
}
}
}
}`
request := graphql.NewRequest(query)
response := OrbIntrospectionResponse{}
err := cl.Run(request, &response)
if err != nil {
return false
}

request.SetToken(cl.Token)

// Find the orbConfig query method, look at its arguments, if it has the "ownerId" argument, return true
for _, field := range response.Schema.Query.Fields {
if field.Name == "orbConfig" {
for _, arg := range field.Args {
if arg.Name == "ownerId" {
return true
}
}
}
}

// else return false, ownerId is not supported

return false
}

// OrbImportVersion publishes a new version of an orb using the provided source and id.
func OrbImportVersion(cl *graphql.Client, orbSrc string, orbID string, orbVersion string) (*Orb, error) {
var response OrbImportVersionResponse
Expand Down
105 changes: 105 additions & 0 deletions api/orb/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package orb

import (
"fmt"
"sync"

"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/CircleCI-Public/circleci-cli/settings"
)

var (
once sync.Once
client Client
)

// ConfigResponse is a structure that matches the result of the GQL
// query, so that we can use mapstructure to convert from
// nested maps to a strongly typed struct.
type QueryResponse struct {
OrbConfig struct {
api.ConfigResponse
}
}

type Client interface {
OrbQuery(configPath string, ownerId string) (*api.ConfigResponse, error)
}

func GetClient(config *settings.Config) Client {
once.Do(func() {
createClient(config)
})
return client
}

func createClient(config *settings.Config) {
gql := graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)

ok, err := orbQueryHandleOwnerId(gql)
if err != nil {
fmt.Printf("While requesting orb server: %s", err)
return
} else if ok {
client = &latestClient{gql}
} else {
client = &deprecatedClient{gql}
}
}

type OrbIntrospectionResponse struct {
Schema struct {
Query struct {
Fields []struct {
Name string `json:"name"`
Args []struct {
Name string `json:"name"`
} `json:"args"`
} `json:"fields"`
} `json:"queryType"`
} `json:"__schema"`
}

func orbQueryHandleOwnerId(gql *graphql.Client) (bool, error) {
query := `
query ValidateOrb {
__schema {
queryType {
fields(includeDeprecated: true) {
name
args {
name
__typename
type {
name
}
}
}
}
}
}`
request := graphql.NewRequest(query)
response := OrbIntrospectionResponse{}
err := gql.Run(request, &response)
if err != nil {
return false, err
}

request.SetToken(gql.Token)

// Find the orbConfig query method, look at its arguments, if it has the "ownerId" argument, return true
for _, field := range response.Schema.Query.Fields {
if field.Name == "orbConfig" {
for _, arg := range field.Args {
if arg.Name == "ownerId" {
return true, nil
}
}
}
}

// else return false, ownerId is not supported

return false, nil
}
50 changes: 50 additions & 0 deletions api/orb/deprecated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package orb

import (
"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/pkg/errors"
)

type deprecatedClient struct {
gql *graphql.Client
}

func (deprecated *deprecatedClient) OrbQuery(configPath string, ownerId string) (*api.ConfigResponse, error) {
if ownerId != "" {
return nil, errors.New("Your version of Server does not support validating orbs that refer to other private orbs. Please see the README for more information on server compatibility: https://github.com/CircleCI-Public/circleci-cli#server-compatibility")
}

var response QueryResponse

configContent, err := loadYaml(configPath)
if err != nil {
return nil, err
}

query := `
query ValidateOrb ($config: String!) {
orbConfig(orbYaml: $config) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`

request := graphql.NewRequest(query)
request.Var("config", configContent)

request.SetToken(deprecated.gql.Token)

err = deprecated.gql.Run(request, &response)
if err != nil {
return nil, errors.Wrap(err, "Unable to validate config")
}

if len(response.OrbConfig.ConfigResponse.Errors) > 0 {
return nil, response.OrbConfig.ConfigResponse.Errors
}

return &response.OrbConfig.ConfigResponse, nil
}
48 changes: 48 additions & 0 deletions api/orb/latest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package orb

import (
"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/pkg/errors"
)

type latestClient struct {
gql *graphql.Client
}

func (latest *latestClient) OrbQuery(configPath string, ownerId string) (*api.ConfigResponse, error) {
var response QueryResponse

configContent, err := loadYaml(configPath)
if err != nil {
return nil, err
}

query := `
query ValidateOrb ($config: String!, $owner: UUID) {
orbConfig(orbYaml: $config, ownerId: $owner) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`
request := graphql.NewRequest(query)
request.Var("config", configContent)

if ownerId != "" {
request.Var("owner", ownerId)
}
request.SetToken(latest.gql.Token)

err = latest.gql.Run(request, &response)
if err != nil {
return nil, errors.Wrap(err, "Unable to validate config")
}

if len(response.OrbConfig.ConfigResponse.Errors) > 0 {
return nil, response.OrbConfig.ConfigResponse.Errors
}

return &response.OrbConfig.ConfigResponse, nil
}
24 changes: 24 additions & 0 deletions api/orb/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package orb

import (
"io"
"os"

"github.com/pkg/errors"
)

func loadYaml(path string) (string, error) {
var err error
var config []byte
if path == "-" {
config, err = io.ReadAll(os.Stdin)
} else {
config, err = os.ReadFile(path)
}

if err != nil {
return "", errors.Wrapf(err, "Could not load config file at %s", path)
}

return string(config), nil
}
13 changes: 11 additions & 2 deletions cmd/orb.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/collaborators"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/CircleCI-Public/circleci-cli/api/orb"
"github.com/CircleCI-Public/circleci-cli/filetree"
"github.com/CircleCI-Public/circleci-cli/process"
"github.com/CircleCI-Public/circleci-cli/prompt"
Expand Down Expand Up @@ -732,7 +733,11 @@ func validateOrb(opts orbOptions, org orbOrgOptions) error {
return fmt.Errorf("failed to get the appropriate org-id: %s", err.Error())
}

_, err = api.OrbQuery(opts.cl, opts.args[0], orgId)
client := orb.GetClient(opts.cfg)
if client == nil {
return fmt.Errorf("Unable to validate orb")
}
_, err = client.OrbQuery(opts.args[0], orgId)

if err != nil {
return err
Expand All @@ -754,7 +759,11 @@ func processOrb(opts orbOptions, org orbOrgOptions) error {
return fmt.Errorf("failed to get the appropriate org-id: %s", err.Error())
}

response, err := api.OrbQuery(opts.cl, opts.args[0], orgId)
client := orb.GetClient(opts.cfg)
if client == nil {
return fmt.Errorf("Unable to validate orb")
}
response, err := client.OrbQuery(opts.args[0], orgId)

if err != nil {
return err
Expand Down
Loading

0 comments on commit 3dd9730

Please sign in to comment.