diff --git a/api/api.go b/api/api.go index f94c64401..3d283930c 100644 --- a/api/api.go +++ b/api/api.go @@ -521,7 +521,28 @@ func OrbQuery(cl *graphql.Client, configPath string, ownerId string) (*ConfigRes return nil, err } - query := ` + 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, @@ -531,26 +552,91 @@ func OrbQuery(cl *graphql.Client, configPath string, ownerId string) (*ConfigRes } }` - request := graphql.NewRequest(query) - request.Var("config", config) + request := graphql.NewRequest(query) + request.Var("config", configContent) + + if ownerId != "" { + request.Var("owner", ownerId) + } + + request.SetToken(cl.Token) + return request, nil + } if ownerId != "" { - request.Var("owner", ownerId) + return nil, errors.Errorf("Your version of server does not support validating orbs that refer private orbs") } + 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 +} - err = cl.Run(request, &response) +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 nil, errors.Wrap(err, "Unable to validate config") + return false } - if len(response.OrbConfig.ConfigResponse.Errors) > 0 { - return nil, response.OrbConfig.ConfigResponse.Errors + 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 + } + } + } } - return &response.OrbConfig.ConfigResponse, nil + // else return false, ownerId is not supported + + return false } // OrbImportVersion publishes a new version of an orb using the provided source and id. @@ -1239,18 +1325,18 @@ func OrbSetOrbListStatus(cl *graphql.Client, namespace string, orb string, list var response OrbSetOrbListStatusResponse query := ` - mutation($orbId: UUID!, $list: Boolean!) { - setOrbListStatus( - orbId: $orbId, - list: $list - ) { - listed - errors { - message - type - } - } +mutation($orbId: UUID!, $list: Boolean!) { + setOrbListStatus( + orbId: $orbId, + list: $list + ) { + listed + errors { + message + type } + } +} ` request := graphql.NewRequest(query) diff --git a/clitest/data/orb_with_private.yml b/clitest/data/orb_with_private.yml new file mode 100644 index 000000000..504688570 --- /dev/null +++ b/clitest/data/orb_with_private.yml @@ -0,0 +1,11 @@ +version: 2.1 + +orbs: + vuln-scanner: cci-internal/snyk-vuln-scanner@0.6.2 + +jobs: + some-job: + executor: vuln-scanner/default + steps: + - run: + command: echo "Hello world" diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 2ab23c889..3d2f586d5 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -109,6 +109,8 @@ See a full explanation and documentation on orbs here: https://circleci.com/docs It("works", func() { By("setting up a mock server") + mockOrbIntrospection(true, "", tempSettings) + gqlResponse := `{ "orbConfig": { "sourceYaml": "{}", @@ -145,6 +147,7 @@ See a full explanation and documentation on orbs here: https://circleci.com/docs Status: http.StatusOK, Request: string(expected), Response: gqlResponse}) + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) @@ -175,6 +178,8 @@ See a full explanation and documentation on orbs here: https://circleci.com/docs By("setting up a mock server") orb.Write([]byte(`{}`)) + mockOrbIntrospection(true, "", tempSettings) + gqlResponse := `{ "orbConfig": { "sourceYaml": "{}", @@ -222,6 +227,9 @@ See a full explanation and documentation on orbs here: https://circleci.com/docs It("works", func() { By("setting up a mock server") + + mockOrbIntrospection(true, "", tempSettings) + gqlResponse := `{ "orbConfig": { "sourceYaml": "{}", @@ -255,6 +263,8 @@ See a full explanation and documentation on orbs here: https://circleci.com/docs It("prints errors if invalid", func() { By("setting up a mock server") + mockOrbIntrospection(true, "", tempSettings) + gqlResponse := `{ "orbConfig": { "sourceYaml": "hello world", @@ -300,6 +310,8 @@ See a full explanation and documentation on orbs here: https://circleci.com/docs It("works", func() { By("setting up a mock server") + mockOrbIntrospection(true, "", tempSettings) + gqlResponse := `{ "orbConfig": { "outputYaml": "hello world", @@ -332,6 +344,8 @@ See a full explanation and documentation on orbs here: https://circleci.com/docs It("prints errors if invalid", func() { By("setting up a mock server") + mockOrbIntrospection(true, "", tempSettings) + gqlResponse := `{ "orbConfig": { "outputYaml": "hello world", @@ -1469,13 +1483,29 @@ You can now register versions of %s using %s.`, } }`, list) - expectedOrbRequest := fmt.Sprintf(`{ - "query": "\n\t\tmutation($orbId: UUID!, $list: Boolean!) {\n\t\t\tsetOrbListStatus(\n\t\t\t\torbId: $orbId,\n\t\t\t\tlist: $list\n\t\t\t) {\n\t\t\t\tlisted\n\t\t\t\terrors { \n\t\t\t\t\tmessage\n\t\t\t\t\ttype \n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t", - "variables": { - "list": %t, - "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - }`, list) + orbRequest := map[string]interface{}{ + "query": ` +mutation($orbId: UUID!, $list: Boolean!) { + setOrbListStatus( + orbId: $orbId, + list: $list + ) { + listed + errors { + message + type + } + } +} + `, + "variables": map[string]interface{}{ + "list": list, + "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87", + }, + } + + expectedOrbRequest, err := json.Marshal(orbRequest) + Expect(err).ToNot(HaveOccurred()) tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ Status: http.StatusOK, @@ -1484,7 +1514,7 @@ You can now register versions of %s using %s.`, tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ Status: http.StatusOK, - Request: expectedOrbRequest, + Request: string(expectedOrbRequest), Response: gqlOrbResponse}) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -1537,13 +1567,29 @@ You can now register versions of %s using %s.`, } }` - expectedOrbRequest := fmt.Sprintf(`{ - "query": "\n\t\tmutation($orbId: UUID!, $list: Boolean!) {\n\t\t\tsetOrbListStatus(\n\t\t\t\torbId: $orbId,\n\t\t\t\tlist: $list\n\t\t\t) {\n\t\t\t\tlisted\n\t\t\t\terrors { \n\t\t\t\t\tmessage\n\t\t\t\t\ttype \n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t", - "variables": { - "list": %t, - "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - }`, list) + orbRequest := map[string]interface{}{ + "query": ` +mutation($orbId: UUID!, $list: Boolean!) { + setOrbListStatus( + orbId: $orbId, + list: $list + ) { + listed + errors { + message + type + } + } +} + `, + "variables": map[string]interface{}{ + "list": list, + "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87", + }, + } + + expectedOrbRequest, err := json.Marshal(orbRequest) + Expect(err).ToNot(HaveOccurred()) tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ Status: http.StatusOK, @@ -1552,7 +1598,7 @@ You can now register versions of %s using %s.`, tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ Status: http.StatusOK, - Request: expectedOrbRequest, + Request: string(expectedOrbRequest), Response: gqlOrbResponse}) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -1686,13 +1732,29 @@ You can now register versions of %s using %s.`, } }` - expectedOrbRequest := fmt.Sprintf(`{ - "query": "\n\t\tmutation($orbId: UUID!, $list: Boolean!) {\n\t\t\tsetOrbListStatus(\n\t\t\t\torbId: $orbId,\n\t\t\t\tlist: $list\n\t\t\t) {\n\t\t\t\tlisted\n\t\t\t\terrors { \n\t\t\t\t\tmessage\n\t\t\t\t\ttype \n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t", - "variables": { - "list": %t, - "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - }`, list) + orbRequest := map[string]interface{}{ + "query": ` +mutation($orbId: UUID!, $list: Boolean!) { + setOrbListStatus( + orbId: $orbId, + list: $list + ) { + listed + errors { + message + type + } + } +} + `, + "variables": map[string]interface{}{ + "list": list, + "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87", + }, + } + + expectedOrbRequest, err := json.Marshal(orbRequest) + Expect(err).ToNot(HaveOccurred()) tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ Status: http.StatusOK, @@ -1701,7 +1763,7 @@ You can now register versions of %s using %s.`, tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ Status: http.StatusOK, - Request: expectedOrbRequest, + Request: string(expectedOrbRequest), Response: gqlOrbResponse}) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -3453,3 +3515,60 @@ func mockOrbSource(source, orbVersion, token string, tempSettings *clitest.TempS Response: response, }) } + +func mockOrbIntrospection(isValid bool, token string, tempSettings *clitest.TempSettings) { + args := []map[string]interface{}{ + { + "name": "orbYaml", + }, + } + if isValid { + args = append(args, map[string]interface{}{ + "name": "ownerId", + }) + } + + responseStruct := map[string]interface{}{ + "__schema": map[string]interface{}{ + "queryType": map[string]interface{}{ + "fields": []map[string]interface{}{ + { + "name": "orbConfig", + "args": args, + }, + }, + }, + }, + } + response, err := json.Marshal(responseStruct) + Expect(err).ToNot(HaveOccurred()) + + requestStruct := map[string]interface{}{ + "query": ` +query ValidateOrb { + __schema { + queryType { + fields(includeDeprecated: true) { + name + args { + name + __typename + type { + name + } + } + } + } + } +}`, + "variables": map[string]interface{}{}, + } + request, err := json.Marshal(requestStruct) + Expect(err).ToNot(HaveOccurred()) + + tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ + Status: http.StatusOK, + Request: string(request), + Response: string(response), + }) +}