Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

poc for aepc #1

Merged
merged 10 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
9 changes: 9 additions & 0 deletions DEVELOPMENT.md
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 ./aepc/schema/resourcedefinition.proto --go_opt paths=source_relative --go_out=.`
2. `go build main.go`
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
# 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
```
131 changes: 131 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// 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/reader"
"github.com/aep-dev/aepc/schema"
"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)
}

proto, _ := proto.WriteServiceToProto(s)

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 := reader.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
}
16 changes: 16 additions & 0 deletions examples/bookstore.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";
package tutorial;
option go_package = "proto";

service BookStore {}

message Book {

// represents the isbn.
string isbn = 3;
message Properties {}
message Status {}

Properties properties = 1;
Status status = 2;
}
29 changes: 29 additions & 0 deletions examples/bookstore.proto.output.proto
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 {
toumorokoshi marked this conversation as resolved.
Show resolved Hide resolved
string path = 1;
}

message CreatebookRequest {
toumorokoshi marked this conversation as resolved.
Show resolved Hide resolved
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/*}" };
}
}
29 changes: 29 additions & 0 deletions examples/bookstore.yaml.output
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/*}" };
}
}
3 changes: 3 additions & 0 deletions examples/bookstore.yaml.output.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: NewBookStore
resources:
- kind: book
41 changes: 41 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module github.com/aep-dev/aepc

go 1.20

require (
github.com/golang/glog v1.1.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1
github.com/jhump/protoreflect v1.15.2
github.com/spf13/cobra v1.7.0
google.golang.org/api v0.139.0
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.31.0
)

require gopkg.in/yaml.v2 v2.4.0 // indirect

require (
cloud.google.com/go/compute v1.23.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/bufbuild/protocompile v0.6.0 // indirect
github.com/ghodss/yaml v1.0.0
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.12.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
)
Loading