Skip to content

Commit

Permalink
⭐️ proto compiler, service implementation and client implementation (#1)
Browse files Browse the repository at this point in the history
Signed-off-by: Christoph Hartmann <[email protected]>
  • Loading branch information
chris-rock authored Jul 17, 2022
1 parent 6f2d44a commit c2d868b
Show file tree
Hide file tree
Showing 32 changed files with 3,688 additions and 0 deletions.
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.PHONY: install
install:
go install ./protoc-gen-rangerrpc

.PHONY: generate/examples
generate/examples:
go generate ./examples/pingpong

.PHONY: run/example/server
run/example/server: install generate/examples
go run examples/pingpong/server/main.go

.PHONY: run/example/client
run/example/client: generate/examples
go run examples/pingpong/client/main.go
92 changes: 92 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package ranger

import (
"bytes"
"context"
"io/ioutil"
"net/http"

"github.com/cockroachdb/errors"
"github.com/rs/zerolog/log"
"go.mondoo.com/ranger-rpc/status"
"google.golang.org/protobuf/proto"
)

// Client is the client for the Ranger service. It is used as the base client for all service calls.
type Client struct{}

type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}

// DoClientRequest makes a request to the Ranger service.
// It will marshal the proto.Message into the request body, do the request and then parse the response into the
// response proto.Message.
func (c *Client) DoClientRequest(ctx context.Context, client HTTPClient, url string, in, out proto.Message) (err error) {
reqBodyBytes, err := proto.Marshal(in)
if err != nil {
return errors.Wrap(err, "failed to marshal proto request")
}

if err = ctx.Err(); err != nil {
return errors.Wrap(err, "aborted because context was done")
}

header := make(http.Header)
header.Set("Accept", "application/protobuf")
header.Set("Content-Type", "application/protobuf")

log.Debug().Str("url", url).Msg("call service")
reader := bytes.NewReader(reqBodyBytes)
req, err := http.NewRequest("POST", url, reader)
if err != nil {
return err
}
req = req.WithContext(ctx)
req.Header = header

// do http call
resp, err := client.Do(req)
if err != nil {
return errors.Wrap(err, "failed to do request")
}

defer func() {
cerr := resp.Body.Close()
if err == nil && cerr != nil {
err = errors.Wrap(cerr, "failed to close response body")
}
}()

if err = ctx.Err(); err != nil {
return errors.Wrap(err, "aborted because context was done")
}

if resp.StatusCode != 200 {
// TODO wrap body in error
spb, err := parseStatus(resp.Body)
if err == nil {
log.Debug().Str("body", spb.Message).Int("status", resp.StatusCode).Msg("non-ok http request")
return status.FromProto(spb).Err()
} else {
payload, err := ioutil.ReadAll(reader)
if err != nil {
log.Error().Err(err).Msg("could not parse http body")
}
return status.New(status.CodeFromHTTPStatus(resp.StatusCode), string(payload)).Err()
}
}

if err = ctx.Err(); err != nil {
return errors.Wrap(err, "aborted because context was done")
}

respBodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "failed to read response body")
}
if err = proto.Unmarshal(respBodyBytes, out); err != nil {
return errors.Wrap(err, "failed to unmarshal proto response")
}
return nil
}
244 changes: 244 additions & 0 deletions codes/codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*
*
* Copyright 2014 gRPC authors.
*
* 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 codes defines the canonical error codes used by gRPC. It is
// consistent across various languages.
package codes

import (
"fmt"
"strconv"
)

// A Code is an unsigned 32-bit error code as defined in the gRPC spec.
type Code uint32

const (
// OK is returned on success.
OK Code = 0

// Canceled indicates the operation was canceled (typically by the caller).
//
// The gRPC framework will generate this error code when cancellation
// is requested.
Canceled Code = 1

// Unknown error. An example of where this error may be returned is
// if a Status value received from another address space belongs to
// an error-space that is not known in this address space. Also
// errors raised by APIs that do not return enough error information
// may be converted to this error.
//
// The gRPC framework will generate this error code in the above two
// mentioned cases.
Unknown Code = 2

// InvalidArgument indicates client specified an invalid argument.
// Note that this differs from FailedPrecondition. It indicates arguments
// that are problematic regardless of the state of the system
// (e.g., a malformed file name).
//
// This error code will not be generated by the gRPC framework.
InvalidArgument Code = 3

// DeadlineExceeded means operation expired before completion.
// For operations that change the state of the system, this error may be
// returned even if the operation has completed successfully. For
// example, a successful response from a server could have been delayed
// long enough for the deadline to expire.
//
// The gRPC framework will generate this error code when the deadline is
// exceeded.
DeadlineExceeded Code = 4

// NotFound means some requested entity (e.g., file or directory) was
// not found.
//
// This error code will not be generated by the gRPC framework.
NotFound Code = 5

// AlreadyExists means an attempt to create an entity failed because one
// already exists.
//
// This error code will not be generated by the gRPC framework.
AlreadyExists Code = 6

// PermissionDenied indicates the caller does not have permission to
// execute the specified operation. It must not be used for rejections
// caused by exhausting some resource (use ResourceExhausted
// instead for those errors). It must not be
// used if the caller cannot be identified (use Unauthenticated
// instead for those errors).
//
// This error code will not be generated by the gRPC core framework,
// but expect authentication middleware to use it.
PermissionDenied Code = 7

// ResourceExhausted indicates some resource has been exhausted, perhaps
// a per-user quota, or perhaps the entire file system is out of space.
//
// This error code will be generated by the gRPC framework in
// out-of-memory and server overload situations, or when a message is
// larger than the configured maximum size.
ResourceExhausted Code = 8

// FailedPrecondition indicates operation was rejected because the
// system is not in a state required for the operation's execution.
// For example, directory to be deleted may be non-empty, an rmdir
// operation is applied to a non-directory, etc.
//
// A litmus test that may help a service implementor in deciding
// between FailedPrecondition, Aborted, and Unavailable:
// (a) Use Unavailable if the client can retry just the failing call.
// (b) Use Aborted if the client should retry at a higher-level
// (e.g., restarting a read-modify-write sequence).
// (c) Use FailedPrecondition if the client should not retry until
// the system state has been explicitly fixed. E.g., if an "rmdir"
// fails because the directory is non-empty, FailedPrecondition
// should be returned since the client should not retry unless
// they have first fixed up the directory by deleting files from it.
// (d) Use FailedPrecondition if the client performs conditional
// REST Get/Update/Delete on a resource and the resource on the
// server does not match the condition. E.g., conflicting
// read-modify-write on the same resource.
//
// This error code will not be generated by the gRPC framework.
FailedPrecondition Code = 9

// Aborted indicates the operation was aborted, typically due to a
// concurrency issue like sequencer check failures, transaction aborts,
// etc.
//
// See litmus test above for deciding between FailedPrecondition,
// Aborted, and Unavailable.
//
// This error code will not be generated by the gRPC framework.
Aborted Code = 10

// OutOfRange means operation was attempted past the valid range.
// E.g., seeking or reading past end of file.
//
// Unlike InvalidArgument, this error indicates a problem that may
// be fixed if the system state changes. For example, a 32-bit file
// system will generate InvalidArgument if asked to read at an
// offset that is not in the range [0,2^32-1], but it will generate
// OutOfRange if asked to read from an offset past the current
// file size.
//
// There is a fair bit of overlap between FailedPrecondition and
// OutOfRange. We recommend using OutOfRange (the more specific
// error) when it applies so that callers who are iterating through
// a space can easily look for an OutOfRange error to detect when
// they are done.
//
// This error code will not be generated by the gRPC framework.
OutOfRange Code = 11

// Unimplemented indicates operation is not implemented or not
// supported/enabled in this service.
//
// This error code will be generated by the gRPC framework. Most
// commonly, you will see this error code when a method implementation
// is missing on the server. It can also be generated for unknown
// compression algorithms or a disagreement as to whether an RPC should
// be streaming.
Unimplemented Code = 12

// Internal errors. Means some invariants expected by underlying
// system has been broken. If you see one of these errors,
// something is very broken.
//
// This error code will be generated by the gRPC framework in several
// internal error conditions.
Internal Code = 13

// Unavailable indicates the service is currently unavailable.
// This is a most likely a transient condition and may be corrected
// by retrying with a backoff. Note that it is not always safe to retry
// non-idempotent operations.
//
// See litmus test above for deciding between FailedPrecondition,
// Aborted, and Unavailable.
//
// This error code will be generated by the gRPC framework during
// abrupt shutdown of a server process or network connection.
Unavailable Code = 14

// DataLoss indicates unrecoverable data loss or corruption.
//
// This error code will not be generated by the gRPC framework.
DataLoss Code = 15

// Unauthenticated indicates the request does not have valid
// authentication credentials for the operation.
//
// The gRPC framework will generate this error code when the
// authentication metadata is invalid or a Credentials callback fails,
// but also expect authentication middleware to generate it.
Unauthenticated Code = 16

_maxCode = 17
)

var strToCode = map[string]Code{
`"OK"`: OK,
`"CANCELLED"`:/* [sic] */ Canceled,
`"UNKNOWN"`: Unknown,
`"INVALID_ARGUMENT"`: InvalidArgument,
`"DEADLINE_EXCEEDED"`: DeadlineExceeded,
`"NOT_FOUND"`: NotFound,
`"ALREADY_EXISTS"`: AlreadyExists,
`"PERMISSION_DENIED"`: PermissionDenied,
`"RESOURCE_EXHAUSTED"`: ResourceExhausted,
`"FAILED_PRECONDITION"`: FailedPrecondition,
`"ABORTED"`: Aborted,
`"OUT_OF_RANGE"`: OutOfRange,
`"UNIMPLEMENTED"`: Unimplemented,
`"INTERNAL"`: Internal,
`"UNAVAILABLE"`: Unavailable,
`"DATA_LOSS"`: DataLoss,
`"UNAUTHENTICATED"`: Unauthenticated,
}

// UnmarshalJSON unmarshals b into the Code.
func (c *Code) UnmarshalJSON(b []byte) error {
// From json.Unmarshaler: By convention, to approximate the behavior of
// Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as
// a no-op.
if string(b) == "null" {
return nil
}
if c == nil {
return fmt.Errorf("nil receiver passed to UnmarshalJSON")
}

if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil {
if ci >= _maxCode {
return fmt.Errorf("invalid code: %q", ci)
}

*c = Code(ci)
return nil
}

if jc, ok := strToCode[string(b)]; ok {
*c = jc
return nil
}
return fmt.Errorf("invalid code: %q", string(b))
}
Loading

0 comments on commit c2d868b

Please sign in to comment.