Skip to content

Commit

Permalink
Merge pull request #81 from CircleCI-Public/CIRCLE-12813-add_namespac…
Browse files Browse the repository at this point in the history
…e_filter_to_orb-list

Circle 12813 add namespace filter to orb list
  • Loading branch information
Zachary Scott authored Aug 31, 2018
2 parents 16f4c0b + c38515e commit e0cabca
Show file tree
Hide file tree
Showing 36 changed files with 4,740 additions and 54 deletions.
4 changes: 4 additions & 0 deletions .gometalinter.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"goimports": {"Command": "goimports -l -w"}
},

"Exclude": [
"md_docs/*"
],

"Enable": [
"deadcode",
"errcheck",
Expand Down
29 changes: 29 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 135 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"bytes"
"context"
"io/ioutil"
"net/url"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/Masterminds/semver"
"github.com/pkg/errors"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"
)

// GQLResponseErrors is a slice of errors returned by the GraphQL server. Each
Expand Down Expand Up @@ -79,6 +81,37 @@ type CreateOrbResponse struct {
GQLResponseErrors
}

// Orb is a struct for containing the yaml-unmarshaled contents of an orb
type Orb struct {
Commands map[string]struct{}
Jobs map[string]struct{}
Executors map[string]struct{}
}

func addOrbElementsToBuffer(buf *bytes.Buffer, name string, elems map[string]struct{}) {
var err error
if len(elems) > 0 {
_, err = buf.WriteString(fmt.Sprintf(" %s:\n", name))
for key := range elems {
_, err = buf.WriteString(fmt.Sprintf(" - %s\n", key))
}
}
// This should never occur, but the linter made me do it :shrug:
if err != nil {
panic(err)
}
}

func (orb Orb) String() string {
var buffer bytes.Buffer

addOrbElementsToBuffer(&buffer, "Commands", orb.Commands)
addOrbElementsToBuffer(&buffer, "Jobs", orb.Jobs)
addOrbElementsToBuffer(&buffer, "Executors", orb.Executors)

return buffer.String()
}

// ToError returns all GraphQL errors for a single response concatenated, or
// nil.
func (response GQLResponseErrors) ToError() error {
Expand Down Expand Up @@ -675,3 +708,105 @@ func OrbSource(ctx context.Context, logger *logger.Logger, namespace string, orb

return response.Orb.Versions[0].Source, nil
}

// ListNamespaceOrbs queries the API to find all orbs belonging to the given
// namespace. Prints the orbs and their jobs and commands to the supplied
// logger.
func ListNamespaceOrbs(ctx context.Context, logger *logger.Logger, namespace string) ([]Orb, error) {
// Define 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 namespaceOrbResponse struct {
RegistryNamespace struct {
Name string
Orbs struct {
Edges []struct {
Cursor string
Node struct {
Name string
Versions []struct {
Version string
Source string
}
}
}
TotalCount int
PageInfo struct {
HasNextPage bool
}
}
}
}

query := `
query namespaceOrbs ($namespace: String, $after: String!) {
registryNamespace(name: $namespace) {
name
orbs(first: 20, after: $after) {
edges {
cursor
node {
versions {
source
version
}
name
}
}
totalCount
pageInfo {
hasNextPage
}
}
}
}
`
var orbs []Orb

address, err := GraphQLServerAddress(EnvEndpointHost())
if err != nil {
return orbs, err
}
graphQLclient := client.NewClient(address, logger)

var result namespaceOrbResponse
currentCursor := ""

for {
request := client.NewAuthorizedRequest(viper.GetString("token"), query)
request.Var("after", currentCursor)
request.Var("namespace", namespace)

err := graphQLclient.Run(ctx, request, &result)
if err != nil {
return orbs, errors.Wrap(err, "GraphQL query failed")
}

NamespaceOrbs:
for i := range result.RegistryNamespace.Orbs.Edges {
edge := result.RegistryNamespace.Orbs.Edges[i]
currentCursor = edge.Cursor
if len(edge.Node.Versions) > 0 {
v := edge.Node.Versions[0]

// Print the orb name and first version returned by the API
logger.Infof("%s (%s)", edge.Node.Name, v.Version)

// Parse the orb source to print its commands, executors and jobs
var o Orb
err := yaml.Unmarshal([]byte(edge.Node.Versions[0].Source), &o)
if err != nil {
logger.Error(fmt.Sprintf("Corrupt Orb %s %s", edge.Node.Name, v.Version), err)
continue NamespaceOrbs
}
orbs = append(orbs, o)
}
}

if !result.RegistryNamespace.Orbs.PageInfo.HasNextPage {
break
}
}

return orbs, nil
}
2 changes: 1 addition & 1 deletion cmd/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func diagnostic(cmd *cobra.Command, args []string) error {

token := viper.GetString("token")

Logger.Infoln("\n---\nCircleCI CLI Diagnostics\n---\n")
Logger.Infoln("\n---\nCircleCI CLI Diagnostics\n---")
Logger.Infof("Config found: %v\n", viper.ConfigFileUsed())

Logger.Infof("GraphQL API address: %s\n", address)
Expand Down
71 changes: 22 additions & 49 deletions cmd/orb.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"bytes"
"context"
"fmt"
"strings"
Expand All @@ -24,10 +23,13 @@ var orbAnnotations = map[string]string{
func newOrbCommand() *cobra.Command {

listCommand := &cobra.Command{
Use: "list",
Short: "List orbs",
RunE: listOrbs,
Use: "list NAMESPACE",
Short: "List orbs",
Args: cobra.MaximumNArgs(1),
RunE: listOrbs,
Annotations: make(map[string]string),
}
listCommand.Annotations["NAMESPACE"] = orbAnnotations["NAMESPACE"] + " (Optional)"

validateCommand := &cobra.Command{
Use: "validate PATH",
Expand Down Expand Up @@ -141,51 +143,10 @@ func newOrbCommand() *cobra.Command {
return orbCommand
}

type orb struct {
Commands map[string]struct{}
Jobs map[string]struct{}
Executors map[string]struct{}
}

func addOrbElementsToBuffer(buf *bytes.Buffer, name string, elems map[string]struct{}) error {
var err error

if len(elems) > 0 {
_, err = buf.WriteString(fmt.Sprintf(" %s:\n", name))
if err != nil {
return err
}
for key := range elems {
_, err = buf.WriteString(fmt.Sprintf(" - %s\n", key))
if err != nil {
return err
}
}
}

return err
}

func (orb orb) String() string {
var buffer bytes.Buffer

err := addOrbElementsToBuffer(&buffer, "Commands", orb.Commands)
// FIXME: refactor this to handle the error
if err != nil {
panic(err)
}
err = addOrbElementsToBuffer(&buffer, "Jobs", orb.Jobs)
if err != nil {
panic(err)
}
err = addOrbElementsToBuffer(&buffer, "Executors", orb.Executors)
if err != nil {
panic(err)
}
return buffer.String()
}

func listOrbs(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return listNamespaceOrbs(args[0])
}

ctx := context.Background()

Expand Down Expand Up @@ -260,7 +221,7 @@ query ListOrbs ($after: String!) {

Logger.Infof("%s (%s)", edge.Node.Name, v.Version)

var o orb
var o api.Orb

err := yaml.Unmarshal([]byte(edge.Node.Versions[0].Source), &o)

Expand All @@ -281,6 +242,18 @@ query ListOrbs ($after: String!) {
return nil
}

func listNamespaceOrbs(namespace string) error {
ctx := context.Background()
orbs, err := api.ListNamespaceOrbs(ctx, Logger, namespace)
if err != nil {
return errors.Wrapf(err, "Failed to list orbs in namespace %s", namespace)
}
for _, o := range orbs {
Logger.Info(o.String())
}
return nil
}

func validateOrb(cmd *cobra.Command, args []string) error {
ctx := context.Background()

Expand Down
Loading

0 comments on commit e0cabca

Please sign in to comment.