-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
poc for aepc - with resource definition support - parameter support - proto writer - proto / json / yaml reader
- Loading branch information
Showing
19 changed files
with
1,834 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Development | ||
|
||
## Building aepc | ||
|
||
The standard GoLang toolchain is used, with the addition of protobuf for | ||
compiling the resource definition. | ||
|
||
1. `protoc ./schema/resourcedefinition.proto --go_opt paths=source_relative --go_out=.` | ||
2. `go build main.go` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,30 @@ | ||
# aepc | ||
# aepc | ||
|
||
Generates AEP-compliant RPCs from proto messages. See the main README.md for usage. | ||
|
||
## Purpose | ||
|
||
aepc is designed to primarily work off of a resource model: rather than having individual RPCs / methods on a resource, the user declares *resources* that live under a *service*. The common operations against a resource (Create, Read, Update, List, and Delete) are generatable based on the desired control plane standard, such as the AEP standard or custom resource definitions for the Kubernetes Resource Model. | ||
|
||
## Design | ||
|
||
aepc works off of an internal "hub" representation of a resource, while each of the consumers and producers is a "spoke", using the resource information for generation of service, clients, or documentation: | ||
|
||
```mermaid | ||
flowchart LR | ||
hub("unified service and resource hub") | ||
protoResources("proto messages") | ||
proto("protobuf") | ||
crd("Custom Resource Definitions (K8S)") | ||
http("HTTP REST APIs") | ||
protoResources --> hub | ||
hub --> proto | ||
hub --> http | ||
hub --> crd | ||
``` | ||
|
||
## User Guide | ||
|
||
``` | ||
go run main.go -i ./examples/bookstore.yaml -o ./examples/bookstore.yaml.output.proto | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// Copyright 2023 Yusuke Fredrick Tsutsumi | ||
// | ||
// 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 | ||
// | ||
// https://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 cmd | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/ghodss/yaml" | ||
|
||
"github.com/aep-dev/aepc/loader" | ||
"github.com/aep-dev/aepc/parser" | ||
"github.com/aep-dev/aepc/schema" | ||
"github.com/aep-dev/aepc/validator" | ||
"github.com/aep-dev/aepc/writer/proto" | ||
"github.com/spf13/cobra" | ||
"google.golang.org/protobuf/encoding/protojson" | ||
) | ||
|
||
func NewCommand() *cobra.Command { | ||
var inputFile string | ||
var outputFile string | ||
|
||
c := &cobra.Command{ | ||
Use: "aepc", | ||
Short: "aepc compiles resource representations to full proto rpcs", | ||
Long: "aepc compiles resource representations to full proto rpcs", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
// TODO: error handling | ||
s := &schema.Service{} | ||
input, err := readFile(inputFile) | ||
fmt.Printf("input: %s\n", string(input)) | ||
if err != nil { | ||
log.Fatalf("unable to read file: %v", err) | ||
} | ||
ext := filepath.Ext(inputFile) | ||
err = unmarshal(ext, input, s) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
errors := validator.ValidateService(s) | ||
if len(errors) > 0 { | ||
log.Fatalf("error validating service: %v", errors) | ||
} | ||
ps, err := parser.NewParsedService(s) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
proto, _ := proto.WriteServiceToProto(ps) | ||
|
||
err = writeFile(outputFile, proto) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
fmt.Printf("output file: %s\n", outputFile) | ||
fmt.Printf("output proto: %s\n", proto) | ||
}, | ||
} | ||
c.Flags().StringVarP(&inputFile, "input", "i", "", "input files with resource") | ||
c.Flags().StringVarP(&outputFile, "output", "o", "", "output file to use") | ||
return c | ||
} | ||
|
||
func unmarshal(ext string, b []byte, s *schema.Service) error { | ||
switch ext { | ||
case ".proto": | ||
if err := loader.ReadServiceFromProto(b, s); err != nil { | ||
return fmt.Errorf("unable to decode proto %q: %w", string(b), err) | ||
} | ||
case ".yaml": | ||
asJson, err := yaml.YAMLToJSON(b) | ||
if err != nil { | ||
return fmt.Errorf("unable to decode yaml to JSON %q: %w", string(b), err) | ||
} | ||
if err := protojson.Unmarshal(asJson, s); err != nil { | ||
log.Fatal(fmt.Errorf("unable to decode proto %q: %w", string(b), err)) | ||
} | ||
case ".json": | ||
if err := protojson.Unmarshal(b, s); err != nil { | ||
return fmt.Errorf("unable to decode json %q: %w", string(b), err) | ||
} | ||
default: | ||
return fmt.Errorf("extension %v is unsupported", ext) | ||
} | ||
return nil | ||
} | ||
|
||
func readFile(fileName string) ([]byte, error) { | ||
var value []byte | ||
f, err := os.OpenFile(fileName, os.O_RDONLY, 0644) | ||
if err != nil { | ||
return nil, err | ||
} | ||
bytesRead := 1 | ||
for bytesRead > 0 { | ||
readBytes := make([]byte, 10000) | ||
bytesRead, err = f.Read(readBytes) | ||
if bytesRead > 0 { | ||
value = append(value, readBytes[:bytesRead]...) | ||
} | ||
if err != io.EOF && err != nil { | ||
return nil, err | ||
} | ||
} | ||
err = f.Close() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return value, nil | ||
} | ||
|
||
func writeFile(fileName string, value []byte) error { | ||
f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = f.Write(value) | ||
if err != nil { | ||
return err | ||
} | ||
err = f.Close() | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// NOTE: since we are missing some proto annotations that | ||
// must be added, the proto representation is outdated. See bookstore.yaml | ||
syntax = "proto3"; | ||
package tutorial; | ||
option go_package = "proto"; | ||
|
||
// bookstore.examples.com | ||
service Bookstore { | ||
} | ||
|
||
message Book { | ||
|
||
// represents the isbn. | ||
string isbn = 3; | ||
message Properties {} | ||
message Status {} | ||
|
||
Properties properties = 1; | ||
Status status = 2; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
syntax = "proto3"; | ||
|
||
import "google/api/annotations.proto"; | ||
|
||
option go_package = "/newbookstore"; | ||
|
||
message book { | ||
string path = 1; | ||
} | ||
|
||
message CreatebookRequest { | ||
string id = 1; | ||
|
||
book resource = 2; | ||
} | ||
|
||
message ReadbookRequest { | ||
string path = 1; | ||
} | ||
|
||
service NewBookStore { | ||
rpc Createbook ( CreatebookRequest ) returns ( book ) { | ||
option (google.api.http) = { post: "/book" }; | ||
} | ||
|
||
rpc Readbook ( ReadbookRequest ) returns ( book ) { | ||
option (google.api.http) = { get: "/{path=book/*}" }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
name: "bookstore.example.com" | ||
resources: | ||
- kind: "Book" | ||
properties: | ||
isbn: | ||
type: STRING | ||
number: 1 | ||
parents: | ||
- "bookstore.example.com/Publisher" | ||
methods: | ||
create: {} | ||
read: {} | ||
update: {} | ||
delete: {} | ||
list: {} | ||
- kind: "Publisher" | ||
methods: | ||
read: {} | ||
list: {} | ||
- kind: "Author" | ||
properties: | ||
name: | ||
type: STRING | ||
number: 1 | ||
parents: | ||
- "Publisher" | ||
methods: | ||
read: {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
syntax = "proto3"; | ||
|
||
import "google/api/annotations.proto"; | ||
|
||
import "google/protobuf/empty.proto"; | ||
|
||
option go_package = "/bookstore"; | ||
|
||
message Author { | ||
string path = 10000; | ||
|
||
string name = 1; | ||
} | ||
|
||
message ReadAuthorRequest { | ||
string path = 1; | ||
} | ||
|
||
message Book { | ||
string path = 10000; | ||
|
||
string isbn = 1; | ||
} | ||
|
||
message CreateBookRequest { | ||
string id = 1; | ||
|
||
Book resource = 2; | ||
} | ||
|
||
message ReadBookRequest { | ||
string path = 1; | ||
} | ||
|
||
message UpdateBookRequest { | ||
string path = 1; | ||
|
||
UpdateBookRequest resource = 2; | ||
} | ||
|
||
message DeleteBookRequest { | ||
string path = 1; | ||
} | ||
|
||
message ListBookRequest { | ||
string path = 1; | ||
} | ||
|
||
message ListBookResponse { | ||
repeated Book resources = 1; | ||
} | ||
|
||
message Publisher { | ||
string path = 10000; | ||
} | ||
|
||
message ReadPublisherRequest { | ||
string path = 1; | ||
} | ||
|
||
message ListPublisherRequest { | ||
string path = 1; | ||
} | ||
|
||
message ListPublisherResponse { | ||
repeated Publisher resources = 1; | ||
} | ||
|
||
service Bookstore { | ||
rpc ReadAuthor ( ReadAuthorRequest ) returns ( Author ) { | ||
option (google.api.http) = { get: "/{path=publisher/*/author/*}" }; | ||
} | ||
|
||
rpc CreateBook ( CreateBookRequest ) returns ( Book ) { | ||
option (google.api.http) = { post: "/{parent=publisher/*}/book" }; | ||
} | ||
|
||
rpc ReadBook ( ReadBookRequest ) returns ( Book ) { | ||
option (google.api.http) = { get: "/{path=publisher/*/book/*}" }; | ||
} | ||
|
||
rpc UpdateBook ( UpdateBookRequest ) returns ( Book ) { | ||
option (google.api.http) = { get: "/{resource.name=publisher/*/book/*}" }; | ||
} | ||
|
||
rpc DeleteBook ( DeleteBookRequest ) returns ( google.protobuf.Empty ) { | ||
option (google.api.http) = { delete: "/{path=publisher/*/book/*}" }; | ||
} | ||
|
||
rpc ListBook ( ListBookRequest ) returns ( ListBookResponse ) { | ||
option (google.api.http) = { get: "/{parent=publisher/*}/book" }; | ||
} | ||
|
||
rpc ReadPublisher ( ReadPublisherRequest ) returns ( Publisher ) { | ||
option (google.api.http) = { get: "/{path=publisher/*}" }; | ||
} | ||
|
||
rpc ListPublisher ( ListPublisherRequest ) returns ( ListPublisherResponse ) { | ||
option (google.api.http) = { get: "/publisher" }; | ||
} | ||
} |
Oops, something went wrong.