Skip to content

Commit

Permalink
Add bazel macros for protobuf_rules_gen (#29)
Browse files Browse the repository at this point in the history
* Make build rules public.

* Add bazel macros for generating firestore security rules.

* Update the readme.

* Use newer bazel version on travis.

* - Add golden test for the bazel example.
- Move bazel definitions to a subdirectory.
- Update readme formatting.

* Fix merge.

* Address code review comments.

* Address code review comments.
  • Loading branch information
ribrdb authored and rockwotj committed Nov 30, 2018
1 parent 99af1c3 commit 62ff446
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ os:
- osx

env:
- V=0.9.0
- V=0.14.1

before_install:
- OS=linux
Expand Down
4 changes: 4 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ py_test(
"$(location @com_google_protobuf//:protoc)",
"$(location //proto:firebase_rules_options_proto_file)",
"$(location @com_google_protobuf//:descriptor_proto)",
"$(location //example:example.rules)",
"$(location //example/testdata:golden.rules)",
"$(locations :testdata)",
],
data = [
"//example/testdata:golden.rules",
"//example:example.rules",
":testdata",
"//firebase_rules_generator:protoc-gen-firebase_rules",
"//proto:firebase_rules_options_proto_file",
Expand Down
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function istutorial_Person_PhoneTypeEnum(resource) {
// Start your rules...
```

## Building
## Standalone usage

1) Install [Bazel](http://www.bazel.io/docs/install.html).

Expand All @@ -194,6 +194,41 @@ function istutorial_Person_PhoneTypeEnum(resource) {
3) A sample invocation of the plugin, `protoc-gen-firebase_rules`, is available
in `example_usage.sh`. This script can be run from the command line.

## Using with bazel

It's easy to use protobuf_rules_gen if your project already uses Bazel.

1) Add protobuf_rules_gen to your WORKSPACE:

```python
proto_gen_firebase_rules_commit = "TODO"
http_archive(
name = "proto_gen_firebase_rules",
sha256 = "TODO",
strip_prefix = "protobuf-rules-gen-" + proto_gen_firebase_rules_commit,
url = "http://github.com/FirebaseExtended/protobuf-rules-gen/archive/" + proto_gen_firebase_rules_commit + ".tar.gz",
)

load("@proto_gen_firebase_rules//bazel:repositories.bzl", "protobuf_rules_gen_repositories")
protobuf_rules_gen_repositories()
```

2) Update your BUILD file:
```python
load("@proto_gen_firebase_rules//bazel:defs.bzl", "firestore_rules_proto_library", "firestore_rules_binary")
```

There are three rules available:
- firestore_rules_proto_library generates a .rules file from the protobuf
schema
- firestore_rules_binary combines multiple .rules files (e.g. the auto
generated rules with your ACLs that use them)
- firestore_rules_library wraps up one or more .rules files so that a
firestore_rules_binary can depend on it.

See example/BUILD for an example of how to use these rules.


## Releasing

1) Build the `proto-gen-firebase_rules` binary via `bazel build //...`
Expand Down
16 changes: 2 additions & 14 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
workspace(name = "proto_gen_firebase_rules")

protobuf_commit = "099d99759101c295244c24d8954ec85b8ac65ce3"
load("//bazel:repositories.bzl", "protobuf_rules_gen_repositories")

http_archive(
name = "com_google_protobuf",
sha256 = "c0ab1b088e220c1d56446f34001f0178e590270efdef1c46a77da4b9faa9d7b0",
strip_prefix = "protobuf-" + protobuf_commit,
url = "https://github.com/google/protobuf/archive/" + protobuf_commit + ".tar.gz",
)

http_archive(
name = "com_google_protobuf_cc",
sha256 = "c0ab1b088e220c1d56446f34001f0178e590270efdef1c46a77da4b9faa9d7b0",
strip_prefix = "protobuf-" + protobuf_commit,
url = "https://github.com/google/protobuf/archive/" + protobuf_commit + ".tar.gz",
)
protobuf_rules_gen_repositories()
1 change: 1 addition & 0 deletions bazel/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

86 changes: 86 additions & 0 deletions bazel/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
load("@com_google_protobuf//:protobuf.bzl", "proto_gen")

def _outputs(srcs, ext):
return [s[:-len(".proto")] + ".pb" + ext for s in srcs]

def firestore_rules_library_impl(ctx):
files = depset(order="postorder", direct=ctx.files.srcs, transitive=[d.fsrules.files for d in ctx.attr.deps])
return struct(fsrules=struct(files=files))

def firestore_rules_binary_impl(ctx):
files = depset(order="postorder", direct=ctx.files.srcs, transitive=[d.fsrules.files for d in ctx.attr.deps])
args = ctx.actions.args()
args.add_all(files)
ctx.actions.run_shell(outputs=[ctx.outputs.bin], inputs=files, command='cat >"%s" "$@"' % ctx.outputs.bin.path, arguments=[args])

firestore_rules_library = rule(
attrs = {
"srcs": attr.label_list(
allow_files = True,
allow_empty = False,
),
"deps": attr.label_list(providers = ["fsrules"]),
},
implementation = firestore_rules_library_impl,
)

firestore_rules_binary = rule(
attrs = {
"srcs": attr.label_list(
allow_files = True,
allow_empty = False,
),
"deps": attr.label_list(providers = ["fsrules"]),
},
outputs = {
"bin": "%{name}.rules",
},
implementation = firestore_rules_binary_impl,
)

def firestore_rules_proto_library(
name,
srcs = [],
deps = [],
include = None,
protoc = "@com_google_protobuf//:protoc",
**kargs):
"""Bazel rule to generate firestore security rules from protobuf schema
Args:
name: the name of the firestore_rules_proto_library.
srcs: the .proto files of the firestore_rules_proto_library.
deps: a list of dependency labels; must be firestore_rules_proto_library.
include: a string indicating the include path of the .proto files.
protoc: the label of the protocol compiler to generate the sources.
**kargs: other keyword arguments that are passed to ts_library.
"""
outs = _outputs(srcs, ".rules")

includes = []
if include != None:
includes = [include]

plugin = "//firebase_rules_generator:protoc-gen-firebase_rules"

proto_gen(
name = name + "_genproto_rules",
srcs = srcs,
deps = [s + "_genproto_rules" for s in deps]+ ["//proto:firebase_rules_options_genproto_rules"],
includes = includes,
protoc = protoc,
outs = outs,
visibility = ["//visibility:public"],
plugin = plugin,
plugin_language = "protoc-gen-firebase_rules",
plugin_options = ["bazel"],
)

firestore_rules_library(
name = name,
srcs = outs,
deps = deps,
**kargs
)

13 changes: 13 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
protobuf_commit = "099d99759101c295244c24d8954ec85b8ac65ce3"

protobuf_sha256 = "c0ab1b088e220c1d56446f34001f0178e590270efdef1c46a77da4b9faa9d7b0"


def protobuf_rules_gen_repositories():
if "com_google_protobuf" not in native.existing_rules():
native.http_archive(
name = "com_google_protobuf",
sha256 = protobuf_sha256,
strip_prefix = "protobuf-" + protobuf_commit,
url = "https://github.com/google/protobuf/archive/" + protobuf_commit + ".tar.gz",
)
13 changes: 13 additions & 0 deletions example/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@proto_gen_firebase_rules//bazel:defs.bzl", "firestore_rules_proto_library", "firestore_rules_binary")

firestore_rules_proto_library(
name = "schema",
srcs = ["schema.proto"],
)

firestore_rules_binary(
name = "example",
srcs = ["main.rules"],
deps = [":schema"],
visibility = ["//visibility:public"],
)
9 changes: 9 additions & 0 deletions example/main.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if true;
allow write: if isPersonMessage(request.resource.data) &&
request.auth.uid == userId;
}
}
}
19 changes: 19 additions & 0 deletions example/schema.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
syntax = "proto3";
package example;

message Person {
string name = 1;
string email = 2;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}

PhoneNumber phone = 3;
}
1 change: 1 addition & 0 deletions example/testdata/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(["golden.rules"])
29 changes: 29 additions & 0 deletions example/testdata/golden.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @@START_GENERATED_FUNCTIONS@@
function isPersonMessage(resource) {
return resource.keys().hasAll([]) &&
(resource.keys().hasOnly(['phone','email','name'])) &&
((!resource.keys().hasAny(['name'])) || (resource.name is string)) &&
((!resource.keys().hasAny(['email'])) || (resource.email is string)) &&
((!resource.keys().hasAny(['phone'])) || (isPerson_PhoneNumberMessage(resource.phone)));
}
function isPerson_PhoneNumberMessage(resource) {
return resource.keys().hasAll([]) &&
(resource.keys().hasOnly(['type','number'])) &&
((!resource.keys().hasAny(['number'])) || (resource.number is string)) &&
((!resource.keys().hasAny(['type'])) || (isPerson_PhoneTypeEnum(resource.type)));
}
function isPerson_PhoneTypeEnum(resource) {
return resource == 0 ||
resource == 1 ||
resource == 2;
}
// @@END_GENERATED_FUNCTIONS@@
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if true;
allow write: if isPersonMessage(request.resource.data) &&
request.auth.uid == userId;
}
}
}
3 changes: 2 additions & 1 deletion firebase_rules_generator/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

package(default_visibility = ["//visibility:public"])

cc_library(
name = "generator",
srcs = ["generator.cc"],
Expand All @@ -27,7 +29,6 @@ cc_binary(
name = "protoc-gen-firebase_rules",
srcs = ["main.cc"],
linkstatic = 1,
visibility = ["//visibility:public"],
deps = [
":generator",
"@com_google_protobuf//:protobuf",
Expand Down
12 changes: 11 additions & 1 deletion firebase_rules_generator/generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

#define RULES_FILE "firestore.rules"

using google::protobuf::StrCat;
using google::protobuf::StripSuffixString;

namespace google {
namespace firebase {
namespace rules {
Expand Down Expand Up @@ -214,7 +217,14 @@ bool RulesGenerator::Generate(const protobuf::FileDescriptor *file,
const std::string &parameter,
protobuf::compiler::GeneratorContext *context,
std::string *error) const {
protobuf::io::Printer printer(context->Open(RULES_FILE), '$');
std::string filename;
if (parameter == "bazel") {
filename = StrCat(StripSuffixString(file->name(), ".proto"),
".pb.rules");
} else {
filename = RULES_FILE;
}
protobuf::io::Printer printer(context->Open(filename), '$');

// Start by adding a comment
printer.Print("// @@START_GENERATED_FUNCTIONS@@\n");
Expand Down
11 changes: 10 additions & 1 deletion integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ def check_rules_output(test_file, expected, actual):
firebase_protos = path.dirname(sys.argv[3])
google_protos = path.dirname(sys.argv[4])

testdata = sys.argv[5:]
example_rules = sys.argv[5]
example_golden = sys.argv[6]

testdata = sys.argv[7:]


def run_testcase(proto_file, output):
Expand Down Expand Up @@ -82,3 +85,9 @@ def run_testcase(proto_file, output):
proto_file = check_proto_file(testdata[i])
rules_out = check_rules_file(testdata[i + 1])
run_testcase(proto_file, rules_out)


check_rules_output(
'example.rules',
read_file(example_golden),
read_file(example_rules))
19 changes: 19 additions & 0 deletions proto/BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load("@com_google_protobuf//:protobuf.bzl", "proto_gen")

proto_library(
name = "firebase_rules_options_proto",
srcs = [
Expand All @@ -14,3 +16,20 @@ filegroup(
],
visibility = ["//visibility:public"],
)

proto_gen(
name = "firebase_rules_options_genproto_rules",
srcs = [
"firebase_rules_options.proto",
],
protoc = "@com_google_protobuf//:protoc",
visibility = ["//visibility:public"],
deps = [":well_known_protos"],
)

proto_gen(
name = "well_known_protos",
srcs = ["@com_google_protobuf//:well_known_protos"],
protoc = "@com_google_protobuf//:protoc",
visibility = ["//visibility:public"],
)

0 comments on commit 62ff446

Please sign in to comment.