Skip to content

Commit

Permalink
Merge pull request #545 from CircleCI-Public/list-private-orbs
Browse files Browse the repository at this point in the history
Listing Orbs now optionally accepts the '--private' flag
  • Loading branch information
kelvinkfli authored Feb 2, 2021
2 parents ee76c04 + e5ce47d commit e0dcdd7
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 33 deletions.
14 changes: 11 additions & 3 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1550,15 +1550,15 @@ query namespaceOrbs ($namespace: String, $after: String!) {
// ListNamespaceOrbs queries the API to find all orbs belonging to the given
// namespace.
// Returns a collection of Orb objects containing their relevant data.
func ListNamespaceOrbs(cl *graphql.Client, namespace string) (*OrbsForListing, error) {
func ListNamespaceOrbs(cl *graphql.Client, namespace string, isPrivate bool) (*OrbsForListing, error) {
l := log.New(os.Stderr, "", 0)

query := `
query namespaceOrbs ($namespace: String, $after: String!) {
query namespaceOrbs ($namespace: String, $after: String!, $view: OrbListViewType) {
registryNamespace(name: $namespace) {
name
id
orbs(first: 20, after: $after) {
orbs(first: 20, after: $after, view: $view) {
edges {
cursor
node {
Expand Down Expand Up @@ -1586,10 +1586,18 @@ query namespaceOrbs ($namespace: String, $after: String!) {
var result NamespaceOrbResponse
currentCursor := ""

view := "PUBLIC_ONLY"
if isPrivate {
view = "PRIVATE_ONLY"
}

for {
request := graphql.NewRequest(query)
request.SetToken(cl.Token)
request.Var("after", currentCursor)
request.Var("namespace", namespace)
request.Var("view", view)

orbs.Namespace = namespace

err := cl.Run(request, &result)
Expand Down
66 changes: 40 additions & 26 deletions cmd/orb.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func newOrbCommand(config *settings.Config) *cobra.Command {
listCommand.PersistentFlags().BoolVarP(&opts.listUncertified, "uncertified", "u", false, "include uncertified orbs")
listCommand.PersistentFlags().BoolVar(&opts.listJSON, "json", false, "print output as json instead of human-readable")
listCommand.PersistentFlags().BoolVarP(&opts.listDetails, "details", "d", false, "output all the commands, executors, and jobs, along with a tree of their parameters")
listCommand.PersistentFlags().BoolVarP(&opts.private, "private", "", false, "exclusively list private orbs within a namespace")
if err := listCommand.PersistentFlags().MarkHidden("json"); err != nil {
panic(err)
}
Expand Down Expand Up @@ -488,38 +489,47 @@ func orbToSimpleString(orb api.OrbWithData) string {
return buffer.String()
}

func orbCollectionToString(orbCollection *api.OrbsForListing, opts orbOptions) (string, error) {
var result string

func formatListOrbsResult(list api.OrbsForListing, opts orbOptions) (string, error) {
if opts.listJSON {
orbJSON, err := json.MarshalIndent(orbCollection, "", " ")
orbJSON, err := json.MarshalIndent(list, "", " ")
if err != nil {
return "", errors.Wrapf(err, "Failed to convert to convert to JSON")
return "", errors.Wrapf(err, "Failed to convert to JSON")
}
result = string(orbJSON)
} else {
result += fmt.Sprintf("Orbs found: %d. ", len(orbCollection.Orbs))
if opts.listUncertified {
result += "Includes all certified and uncertified orbs.\n\n"

return string(orbJSON), nil
}

// Construct messaging based on provided options.
var b strings.Builder
b.WriteString(fmt.Sprintf("Orbs found: %d. ", len(list.Orbs)))

switch {
case opts.private:
b.WriteString("Showing only private orbs.\n\n")
case opts.listUncertified:
b.WriteString("Includes all certified and uncertified orbs.\n\n")
default:
b.WriteString("Showing only certified orbs.\nAdd --uncertified for a list of all orbs.\n\n")
}

for _, o := range list.Orbs {
if opts.listDetails {
b.WriteString(orbToDetailedString(o))
} else {
result += "Showing only certified orbs.\nAdd --uncertified for a list of all orbs.\n\n"
b.WriteString(orbToSimpleString(o))
}
for _, orb := range orbCollection.Orbs {
if opts.listDetails {
result += (orbToDetailedString(orb))
} else {
result += (orbToSimpleString(orb))
}
}
result += "\nIn order to see more details about each orb, type: `circleci orb info orb-namespace/orb-name`\n"
result += "\nSearch, filter, and view sources for all Orbs online at https://circleci.com/developer/orbs/"
}

return result, nil
if !opts.private {
b.WriteString("\nIn order to see more details about each orb, type: `circleci orb info orb-namespace/orb-name`\n")
b.WriteString("\nSearch, filter, and view sources for all Orbs online at https://circleci.com/developer/orbs/")
}

return b.String(), nil
}

func logOrbs(orbCollection *api.OrbsForListing, opts orbOptions) error {
result, err := orbCollectionToString(orbCollection, opts)
func logOrbs(orbCollection api.OrbsForListing, opts orbOptions) error {
result, err := formatListOrbsResult(orbCollection, opts)
if err != nil {
return err
}
Expand Down Expand Up @@ -584,6 +594,10 @@ func listOrbs(opts orbOptions) error {
return listNamespaceOrbs(opts)
}

if opts.private {
return errors.New("Namespace must be provided when listing private orbs")
}

orbs, err := api.ListOrbs(opts.cl, opts.listUncertified)
if err != nil {
return errors.Wrapf(err, "Failed to list orbs")
Expand All @@ -593,13 +607,13 @@ func listOrbs(opts orbOptions) error {
orbs.SortBy(opts.sortBy)
}

return logOrbs(orbs, opts)
return logOrbs(*orbs, opts)
}

func listNamespaceOrbs(opts orbOptions) error {
namespace := opts.args[0]

orbs, err := api.ListNamespaceOrbs(opts.cl, namespace)
orbs, err := api.ListNamespaceOrbs(opts.cl, namespace, opts.private)
if err != nil {
return errors.Wrapf(err, "Failed to list orbs in namespace `%s`", namespace)
}
Expand All @@ -608,7 +622,7 @@ func listNamespaceOrbs(opts orbOptions) error {
orbs.SortBy(opts.sortBy)
}

return logOrbs(orbs, opts)
return logOrbs(*orbs, opts)
}

func validateOrb(opts orbOptions) error {
Expand Down
104 changes: 100 additions & 4 deletions cmd/orb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1990,11 +1990,11 @@ Search, filter, and view sources for all Orbs online at https://circleci.com/dev
By("setting up a mock server")

query := `
query namespaceOrbs ($namespace: String, $after: String!) {
query namespaceOrbs ($namespace: String, $after: String!, $view: OrbListViewType) {
registryNamespace(name: $namespace) {
name
id
orbs(first: 20, after: $after) {
orbs(first: 20, after: $after, view: $view) {
edges {
cursor
node {
Expand All @@ -2021,13 +2021,15 @@ query namespaceOrbs ($namespace: String, $after: String!) {
firstRequest := graphql.NewRequest(query)
firstRequest.Variables["after"] = ""
firstRequest.Variables["namespace"] = "circleci"
firstRequest.Variables["view"] = "PUBLIC_ONLY"

firstRequestEncoded, err := firstRequest.Encode()
Expect(err).ShouldNot(HaveOccurred())

secondRequest := graphql.NewRequest(query)
secondRequest.Variables["after"] = "circleci/codecov-clojure"
secondRequest.Variables["namespace"] = "circleci"
secondRequest.Variables["view"] = "PUBLIC_ONLY"

secondRequestEncoded, err := secondRequest.Encode()
Expect(err).ShouldNot(HaveOccurred())
Expand Down Expand Up @@ -2104,11 +2106,11 @@ query namespaceOrbs ($namespace: String, $after: String!) {
By("setting up a mock server")

query := `
query namespaceOrbs ($namespace: String, $after: String!) {
query namespaceOrbs ($namespace: String, $after: String!, $view: OrbListViewType) {
registryNamespace(name: $namespace) {
name
id
orbs(first: 20, after: $after) {
orbs(first: 20, after: $after, view: $view) {
edges {
cursor
node {
Expand Down Expand Up @@ -2136,6 +2138,7 @@ query namespaceOrbs ($namespace: String, $after: String!) {
request := graphql.NewRequest(query)
request.Variables["after"] = ""
request.Variables["namespace"] = "nonexist"
request.Variables["view"] = "PUBLIC_ONLY"

encodedRequest, err := request.Encode()
Expect(err).ShouldNot(HaveOccurred())
Expand All @@ -2158,7 +2161,100 @@ query namespaceOrbs ($namespace: String, $after: String!) {
Eventually(session).Should(clitest.ShouldFail())
Expect(tempSettings.TestServer.ReceivedRequests()).Should(HaveLen(1))
})
})

Describe("when listing private orbs", func() {
BeforeEach(func() {
By("setting up a mock server")

query := `
query namespaceOrbs ($namespace: String, $after: String!, $view: OrbListViewType) {
registryNamespace(name: $namespace) {
name
id
orbs(first: 20, after: $after, view: $view) {
edges {
cursor
node {
versions {
source
version
}
name
statistics {
last30DaysBuildCount,
last30DaysProjectCount,
last30DaysOrganizationCount
}
}
}
totalCount
pageInfo {
hasNextPage
}
}
}
}
`

request := graphql.NewRequest(query)
request.Variables["after"] = ""
request.Variables["namespace"] = "circleci"
request.Variables["view"] = "PRIVATE_ONLY"

encodedRequest, err := request.Encode()
Expect(err).ShouldNot(HaveOccurred())

tmpBytes := golden.Get(GinkgoT(), filepath.FromSlash("gql_orb_list_with_namespace/second_response.json"))
mockResponse := string(tmpBytes)

tempSettings.AppendPostHandler("", clitest.MockRequestResponse{
Status: http.StatusOK,
Request: encodedRequest.String(),
Response: mockResponse,
})
})

It("returns an error when private is provided without a namespace", func() {
command = exec.Command(pathCLI,
"orb", "list",
"--private",
"--skip-update-check",
"--host", tempSettings.TestServer.URL(),
)

By("running the command")
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Err).Should(gbytes.Say("Namespace must be provided when listing private orbs"))
Eventually(session).Should(clitest.ShouldFail())
Expect(tempSettings.TestServer.ReceivedRequests()).Should(HaveLen(0))
})
It("successfully returns private orbs within a given namespace", func() {
command = exec.Command(pathCLI,
"orb", "list", "circleci",
"--private",
"--skip-update-check",
"--host", tempSettings.TestServer.URL(),
)

By("running the command")
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expect(err).ShouldNot(HaveOccurred())
stdout := session.Wait().Out.Contents()
Expect(string(stdout)).To(Equal(`Orbs found: 5. Showing only private orbs.
circleci/delete-me (Not published)
circleci/delete-me-too (Not published)
circleci/gradle (0.0.1)
circleci/heroku (Not published)
circleci/rollbar (0.0.1)
`))
Expect(tempSettings.TestServer.ReceivedRequests()).Should(HaveLen(1))
})
})

Describe("when creating an orb without a token", func() {
Expand Down

0 comments on commit e0dcdd7

Please sign in to comment.