Skip to content

Commit

Permalink
Match specs against a BigQuery table of operations
Browse files Browse the repository at this point in the history
  • Loading branch information
timburks committed Jul 24, 2023
1 parent 0d4fee8 commit 7fde448
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 55 deletions.
49 changes: 49 additions & 0 deletions cmd/registry-bigquery/common/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package common

import (
"context"
"net/http"
"time"

"cloud.google.com/go/bigquery"
"google.golang.org/api/googleapi"
)

// Used by subcommands, now provides a single common timestap for a command invocation.
var Now = time.Now()

// Get a BigQuery dataset by name and create it if it doesn't exist.
func GetOrCreateDataset(ctx context.Context, client *bigquery.Client, name string) (*bigquery.Dataset, error) {
dataset := client.Dataset(name)
if err := dataset.Create(ctx, nil); err != nil {
switch v := err.(type) {
case *googleapi.Error:
if v.Code != http.StatusConflict { // already exists
return nil, err
}
default:
return nil, err
}
}
return dataset, nil
}

// Get a BigQuery table by name and create it if it doesn't exist.
func GetOrCreateTable(ctx context.Context, dataset *bigquery.Dataset, name string, prototype interface{}) (*bigquery.Table, error) {
table := dataset.Table(name)
schema, err := bigquery.InferSchema(prototype)
if err != nil {
return nil, err
}
if err := table.Create(ctx, &bigquery.TableMetadata{Schema: schema}); err != nil {
switch v := err.(type) {
case *googleapi.Error:
if v.Code != http.StatusConflict { // already exists
return nil, err
}
default:
return nil, err
}
}
return table, nil
}
45 changes: 0 additions & 45 deletions cmd/registry-bigquery/index/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@
package index

import (
"context"
"net/http"
"time"

"cloud.google.com/go/bigquery"
"github.com/spf13/cobra"
"google.golang.org/api/googleapi"
)

func Command() *cobra.Command {
Expand All @@ -35,42 +29,3 @@ func Command() *cobra.Command {
cmd.AddCommand(serversCommand())
return cmd
}

// Used by subcommands, now provides a single common timestap for a command invocation.
var now = time.Now()

// Get a BigQuery dataset by name and create it if it doesn't exist.
func getOrCreateDataset(ctx context.Context, client *bigquery.Client, name string) (*bigquery.Dataset, error) {
dataset := client.Dataset(name)
if err := dataset.Create(ctx, nil); err != nil {
switch v := err.(type) {
case *googleapi.Error:
if v.Code != http.StatusConflict { // already exists
return nil, err
}
default:
return nil, err
}
}
return dataset, nil
}

// Get a BigQuery table by name and create it if it doesn't exist.
func getOrCreateTable(ctx context.Context, dataset *bigquery.Dataset, name string, prototype interface{}) (*bigquery.Table, error) {
table := dataset.Table(name)
schema, err := bigquery.InferSchema(prototype)
if err != nil {
return nil, err
}
if err := table.Create(ctx, &bigquery.TableMetadata{Schema: schema}); err != nil {
switch v := err.(type) {
case *googleapi.Error:
if v.Code != http.StatusConflict { // already exists
return nil, err
}
default:
return nil, err
}
}
return table, nil
}
7 changes: 4 additions & 3 deletions cmd/registry-bigquery/index/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"cloud.google.com/go/bigquery"
"github.com/apigee/registry-experimental/cmd/registry-bigquery/common"
"github.com/apigee/registry-experimental/pkg/yamlquery"
"github.com/apigee/registry/pkg/connection"
"github.com/apigee/registry/pkg/mime"
Expand Down Expand Up @@ -62,11 +63,11 @@ func infoCommand() *cobra.Command {
if err != nil {
return err
}
ds, err := getOrCreateDataset(ctx, client, dataset)
ds, err := common.GetOrCreateDataset(ctx, client, dataset)
if err != nil {
return err
}
table, err := getOrCreateTable(ctx, ds, "info", info{})
table, err := common.GetOrCreateTable(ctx, ds, "info", info{})
if err != nil {
return err
}
Expand Down Expand Up @@ -161,7 +162,7 @@ func (v *infoVisitor) getOpenAPIInfo(specName names.Spec, b []byte) error {
Api: specName.ApiID,
Version: specName.VersionID,
Spec: specName.SpecID,
Timestamp: now,
Timestamp: common.Now,
})
}
return nil
Expand Down
7 changes: 4 additions & 3 deletions cmd/registry-bigquery/index/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"cloud.google.com/go/bigquery"
"github.com/apigee/registry-experimental/cmd/registry-bigquery/common"
"github.com/apigee/registry-experimental/pkg/yamlquery"
"github.com/apigee/registry/pkg/connection"
"github.com/apigee/registry/pkg/mime"
Expand Down Expand Up @@ -63,11 +64,11 @@ func operationsCommand() *cobra.Command {
if err != nil {
return err
}
ds, err := getOrCreateDataset(ctx, client, dataset)
ds, err := common.GetOrCreateDataset(ctx, client, dataset)
if err != nil {
return err
}
table, err := getOrCreateTable(ctx, ds, "operations", operation{})
table, err := common.GetOrCreateTable(ctx, ds, "operations", operation{})
if err != nil {
return err
}
Expand Down Expand Up @@ -175,7 +176,7 @@ func (v *operationsVisitor) getOpenAPIOperations(specName names.Spec, b []byte)
Api: specName.ApiID,
Version: specName.VersionID,
Spec: specName.SpecID,
Timestamp: now,
Timestamp: common.Now,
})

}
Expand Down
9 changes: 5 additions & 4 deletions cmd/registry-bigquery/index/servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"cloud.google.com/go/bigquery"
"github.com/apigee/registry-experimental/cmd/registry-bigquery/common"
"github.com/apigee/registry-experimental/pkg/yamlquery"
"github.com/apigee/registry/pkg/connection"
"github.com/apigee/registry/pkg/mime"
Expand Down Expand Up @@ -62,11 +63,11 @@ func serversCommand() *cobra.Command {
if err != nil {
return err
}
ds, err := getOrCreateDataset(ctx, client, dataset)
ds, err := common.GetOrCreateDataset(ctx, client, dataset)
if err != nil {
return err
}
table, err := getOrCreateTable(ctx, ds, "servers", server{})
table, err := common.GetOrCreateTable(ctx, ds, "servers", server{})
if err != nil {
return err
}
Expand Down Expand Up @@ -162,7 +163,7 @@ func (v *serversVisitor) getOpenAPIServers(specName names.Spec, b []byte) error
Api: specName.ApiID,
Version: specName.VersionID,
Spec: specName.SpecID,
Timestamp: now,
Timestamp: common.Now,
})
}
}
Expand All @@ -180,7 +181,7 @@ func (v *serversVisitor) getOpenAPIServers(specName names.Spec, b []byte) error
Api: specName.ApiID,
Version: specName.VersionID,
Spec: specName.SpecID,
Timestamp: now,
Timestamp: common.Now,
})
}
return nil
Expand Down
2 changes: 2 additions & 0 deletions cmd/registry-bigquery/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"os"

"github.com/apigee/registry-experimental/cmd/registry-bigquery/index"
"github.com/apigee/registry-experimental/cmd/registry-bigquery/match"
"github.com/apigee/registry/pkg/log"
"github.com/google/uuid"
"github.com/spf13/cobra"
Expand All @@ -33,6 +34,7 @@ func main() {

cmd := &cobra.Command{}
cmd.AddCommand(index.Command())
cmd.AddCommand(match.Command())
if err := cmd.ExecuteContext(ctx); err != nil {
os.Exit(1)
}
Expand Down
174 changes: 174 additions & 0 deletions cmd/registry-bigquery/match/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2023 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package match

import (
"context"
"fmt"
"strings"
"time"

"cloud.google.com/go/bigquery"
"github.com/apigee/registry-experimental/pkg/yamlquery"
"github.com/apigee/registry/pkg/connection"
"github.com/apigee/registry/pkg/mime"
"github.com/apigee/registry/pkg/names"
"github.com/apigee/registry/pkg/visitor"
"github.com/apigee/registry/rpc"
"github.com/spf13/cobra"
"google.golang.org/api/iterator"
"gopkg.in/yaml.v3"
)

func Command() *cobra.Command {
var filter string
var project string
var dataset string
var batchSize int
cmd := &cobra.Command{
Use: "match PATTERN",
Short: "Match API specs with a BigQuery index of API information",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
c, err := connection.ActiveConfig()
if err != nil {
return err
}
pattern := c.FQName(args[0])
adminClient, err := connection.NewAdminClientWithSettings(ctx, c)
if err != nil {
return err
}
registryClient, err := connection.NewRegistryClientWithSettings(ctx, c)
if err != nil {
return err
}
if project == "" {
project = c.Project
}
client, err := bigquery.NewClient(ctx, project)
if err != nil {
return err
}
v := &matchVisitor{
registryClient: registryClient,
bigQueryClient: client,
}
err = visitor.Visit(ctx, v, visitor.VisitorOptions{
RegistryClient: registryClient,
AdminClient: adminClient,
Pattern: pattern,
Filter: filter,
ImplicitProject: &rpc.Project{Name: "projects/implicit"},
})
if err != nil {
return err
}

return nil
},
}
cmd.Flags().StringVar(&filter, "filter", "", "Filter selected resources")
cmd.Flags().StringVar(&project, "project", "", "Project to use for BigQuery upload (defaults to registry project)")
cmd.Flags().StringVar(&dataset, "dataset", "registry", "BigQuery dataset name")
cmd.Flags().IntVar(&batchSize, "batch-size", 10000, "Batch size to use when uploading records to BigQuery")
return cmd
}

type matchVisitor struct {
visitor.Unsupported
registryClient connection.RegistryClient
bigQueryClient *bigquery.Client
}

func (v *matchVisitor) SpecHandler() visitor.SpecHandler {
return func(ctx context.Context, message *rpc.ApiSpec) error {
fmt.Printf("%s\n", message.Name)
specName, err := names.ParseSpec(message.Name)
if err != nil {
return err
}
return visitor.GetSpec(ctx, v.registryClient, specName, true,
func(ctx context.Context, spec *rpc.ApiSpec) error {
if mime.IsOpenAPIv2(spec.MimeType) || mime.IsOpenAPIv3(spec.MimeType) {
err := v.matchOpenAPI(ctx, specName, spec.Contents)
if err != nil {
return err
}
}
return nil
})
}
}

func (v *matchVisitor) matchOpenAPI(ctx context.Context, specName names.Spec, b []byte) error {
var doc yaml.Node
err := yaml.Unmarshal(b, &doc)
if err != nil {
return err
}
paths := yamlquery.QueryNode(&doc, "paths")
if paths != nil {
for i := 0; i < len(paths.Content); i += 2 {
path := paths.Content[i].Value
fields := paths.Content[i+1]
for j := 0; j < len(fields.Content); j += 2 {
fieldName := fields.Content[j].Value
// Skip any fields (summary, description, etc) that aren't methods.
if fieldName != "get" &&
fieldName != "put" &&
fieldName != "post" &&
fieldName != "delete" &&
fieldName != "options" &&
fieldName != "patch" {
continue
}
method := strings.ToUpper(fieldName)
fmt.Printf("\n%s %s\n", method, path)
query := fmt.Sprintf(
`SELECT * FROM openapi_directory.operations WHERE path like "%s" and method = "%s"`,
path,
method)
q := v.bigQueryClient.Query(query)
it, err := q.Read(ctx)
if err != nil {
return err
}
for {
var values operation
err = it.Next(&values)
if err == iterator.Done {
break
}
if err != nil {
return err
}
fmt.Printf("apis/%s/versions/%s/specs/%s\n", values.Api, values.Version, values.Spec)
}
}
}
}
return nil
}

type operation struct {
Path string
Method string
Api string
Version string
Spec string
Timestamp time.Time
}

0 comments on commit 7fde448

Please sign in to comment.