├── tools ├── BUILD.bazel └── rules_go_macos.patch ├── .bazelversion ├── .gitignore ├── .gitattributes ├── _examples ├── wasm │ ├── demo.gif │ ├── go.mod │ ├── README.md │ ├── go.sum │ ├── addressbook.proto │ ├── main.go │ ├── index.html │ └── addressbook │ │ └── addressbook.pb.go ├── envoy │ ├── start-envoy.sh │ ├── go.mod │ ├── address.cfg │ ├── envoy.yaml │ ├── envoy.cfg │ ├── cluster.cfg │ ├── listener.cfg │ └── main.go ├── k8s │ ├── nginx.cfg │ ├── app.cfg │ ├── go.mod │ └── go.sum └── repl │ ├── go.mod │ ├── main.go │ └── go.sum ├── internal └── test_proto │ ├── package.go │ ├── BUILD │ ├── test_proto_v2.proto │ └── test_proto_v3.proto ├── debugger ├── export_test.go ├── BUILD.bazel ├── example_test.go ├── debugger_test.go ├── README.md ├── frame.go └── vars.go ├── .bazelrc ├── go ├── urlmodule │ ├── BUILD │ ├── urlmodule.go │ └── urlmodule_test.go ├── assertmodule │ ├── fail.go │ ├── BUILD │ └── assert.go ├── hashmodule │ ├── BUILD │ ├── hashmodule_test.go │ └── hashmodule.go ├── yamlmodule │ ├── BUILD │ ├── json_write.go │ ├── yamlmodule.go │ └── yamlmodule_test.go ├── parallelmodule │ ├── BUILD.bazel │ ├── thread.go │ └── parallelmodule.go └── protomodule │ ├── iter.go │ ├── BUILD │ ├── iter_test.go │ ├── merge.go │ ├── protomodule_package.go │ ├── protomodule_enum.go │ ├── protomodule_message_type.go │ ├── protomodule_list.go │ ├── protomodule_map.go │ ├── bench_test.go │ └── protomodule.go ├── go.mod ├── AUTHORS ├── export_test.go ├── CONTRIBUTORS ├── BUILD.bazel ├── .github └── workflows │ └── tests.yaml ├── MODULE.bazel ├── go.sum ├── reader.go ├── docs └── protobuf.asciidoc └── README.md /tools/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 8.4.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-* 2 | *.pb.go 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sky linguist-language=Python 2 | -------------------------------------------------------------------------------- /_examples/wasm/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe/skycfg/HEAD/_examples/wasm/demo.gif -------------------------------------------------------------------------------- /internal/test_proto/package.go: -------------------------------------------------------------------------------- 1 | package test_proto 2 | 3 | //go:generate protoc --go_out=paths=source_relative:. test_proto_v2.proto test_proto_v3.proto 4 | -------------------------------------------------------------------------------- /_examples/envoy/start-envoy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec docker run -i -p10001:10001 -p10000:10000 -p9901:9901 -v`pwd`:/etc/envoy -it envoyproxy/envoy:v1.14-latest 3 | -------------------------------------------------------------------------------- /debugger/export_test.go: -------------------------------------------------------------------------------- 1 | package debugger 2 | 3 | // Export functions for testing. 4 | 5 | import ( 6 | "reflect" 7 | ) 8 | 9 | func SafeEq(a, b any) bool { 10 | return safeEq(reflect.ValueOf(a), reflect.ValueOf(b)) 11 | } 12 | -------------------------------------------------------------------------------- /_examples/wasm/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stripe/skycfg/_examples/wasm 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.0 7 | github.com/stripe/skycfg v0.0.0 8 | google.golang.org/protobuf v1.33.0 9 | gopkg.in/yaml.v2 v2.2.1 10 | ) 11 | 12 | replace github.com/stripe/skycfg => ../../ 13 | -------------------------------------------------------------------------------- /_examples/envoy/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stripe/skycfg/_examples/envoy 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/cncf/udpa/go v0.0.0-20200909154343-1f710aca26a9 // indirect 7 | github.com/envoyproxy/go-control-plane v0.9.6 8 | github.com/golang/protobuf v1.5.0 9 | github.com/sirupsen/logrus v1.6.0 10 | github.com/stripe/skycfg v0.0.0-20200930043820-677b39f98fc6 11 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e 12 | google.golang.org/grpc v1.27.0 13 | google.golang.org/protobuf v1.33.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | build --incompatible_strict_action_env 2 | 3 | common --incompatible_enable_proto_toolchain_resolution 4 | 5 | # https://github.com/aspect-build/toolchains_protoc?tab=readme-ov-file#ensure-protobuf-and-grpc-never-built 6 | common --per_file_copt=external/.*protobuf.*@--PROTOBUF_WAS_NOT_SUPPOSED_TO_BE_BUILT 7 | common --host_per_file_copt=external/.*protobuf.*@--PROTOBUF_WAS_NOT_SUPPOSED_TO_BE_BUILT 8 | common --per_file_copt=external/.*grpc.*@--GRPC_WAS_NOT_SUPPOSED_TO_BE_BUILT 9 | common --host_per_file_copt=external/.*grpc.*@--GRPC_WAS_NOT_SUPPOSED_TO_BE_BUILT 10 | -------------------------------------------------------------------------------- /go/urlmodule/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "urlmodule", 5 | srcs = ["urlmodule.go"], 6 | importpath = "github.com/stripe/skycfg/go/urlmodule", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@net_starlark_go//starlark", 10 | "@net_starlark_go//starlarkstruct", 11 | ], 12 | ) 13 | 14 | go_test( 15 | name = "urlmodule_test", 16 | srcs = ["urlmodule_test.go"], 17 | embed = [":urlmodule"], 18 | deps = ["@net_starlark_go//starlark"], 19 | ) 20 | -------------------------------------------------------------------------------- /go/assertmodule/fail.go: -------------------------------------------------------------------------------- 1 | package assertmodule 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go.starlark.net/starlark" 7 | ) 8 | 9 | var Fail = starlark.NewBuiltin("fail", failImpl) 10 | 11 | func failImpl(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 12 | var msg string 13 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &msg); err != nil { 14 | return nil, err 15 | } 16 | callStack := t.CallStack() 17 | callStack.Pop() 18 | return nil, fmt.Errorf("[%s] %s\n%s", callStack.At(0).Pos, msg, callStack.String()) 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stripe/skycfg 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/spaolacci/murmur3 v1.1.0 7 | github.com/stretchr/testify v1.10.0 8 | go.starlark.net v0.0.0-20250623223156-8bf495bf4e9a 9 | golang.org/x/sync v0.11.0 10 | google.golang.org/protobuf v1.36.3 11 | gopkg.in/yaml.v2 v2.2.8 12 | ) 13 | 14 | require ( 15 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | golang.org/x/sys v0.1.0 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go/hashmodule/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "hashmodule", 5 | srcs = ["hashmodule.go"], 6 | importpath = "github.com/stripe/skycfg/go/hashmodule", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_spaolacci_murmur3//:murmur3", 10 | "@net_starlark_go//starlark", 11 | "@net_starlark_go//starlarkstruct", 12 | ], 13 | ) 14 | 15 | go_test( 16 | name = "hashmodule_test", 17 | srcs = ["hashmodule_test.go"], 18 | embed = [":hashmodule"], 19 | deps = ["@net_starlark_go//starlark"], 20 | ) 21 | -------------------------------------------------------------------------------- /_examples/wasm/README.md: -------------------------------------------------------------------------------- 1 | # Skycfg in WebAssembly 2 | 3 | ![A short demo of Skycfg running in a browser interactively via WebAssembly](demo.gif) 4 | 5 | Please run the following to build the appropriate WASM file (adapted from [Go Wiki: WebAssembly](https://go.dev/wiki/WebAssembly#javascript-goosjs-port)). 6 | ```sh 7 | GOOS=js GOARCH=wasm go build -o skycfg.wasm 8 | cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" . 9 | ``` 10 | 11 | Then, serve this directory using any HTTP server. Example using Python's [http.server](https://docs.python.org/3/library/http.server.html): 12 | ```sh 13 | python -m http.server 8000 14 | ``` 15 | 16 | Then, you can visit http://127.0.0.1:8000/ to view the demo. 17 | -------------------------------------------------------------------------------- /tools/rules_go_macos.patch: -------------------------------------------------------------------------------- 1 | --- a/go/private/go_toolchain.bzl 2025-08-19 17:40:56.000000000 -0700 2 | +++ b/go/private/go_toolchain.bzl 2025-11-12 21:48:21.186200804 -0800 3 | @@ -111,6 +111,11 @@ 4 | cgo_link_flags.extend(["-shared", "-Wl,-all_load"]) 5 | if host_goos == "linux": 6 | cgo_link_flags.append("-Wl,-whole-archive") 7 | + if p.goos == "darwin": 8 | + # Add -B gobuildid for compatibility with macOS 26. 9 | + # Only necessary before Go 1.24. 10 | + # See https://go.dev/issues/68678 11 | + link_flags.extend(["-B", "gobuildid"]) 12 | 13 | go_toolchain( 14 | name = "go_" + p.name + "-impl", 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of Skycfg authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history in source control. 6 | 7 | # Names of people must include a contact email, in the format: 8 | # Name 9 | 10 | GM Cruise LLC 11 | Mux, Inc. 12 | Stripe, Inc. 13 | 14 | Alex Gaynor 15 | Charles Xu 16 | Chun-Hung Hsiao 17 | Kevin Lin 18 | Peter N 19 | Taras Tsugrii 20 | -------------------------------------------------------------------------------- /go/yamlmodule/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "yamlmodule", 5 | srcs = [ 6 | "json_write.go", 7 | "yamlmodule.go", 8 | ], 9 | importpath = "github.com/stripe/skycfg/go/yamlmodule", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "@in_gopkg_yaml_v2//:yaml_v2", 13 | "@net_starlark_go//lib/json", 14 | "@net_starlark_go//starlark", 15 | "@net_starlark_go//starlarkstruct", 16 | ], 17 | ) 18 | 19 | go_test( 20 | name = "yamlmodule_test", 21 | srcs = ["yamlmodule_test.go"], 22 | embed = [":yamlmodule"], 23 | deps = ["@net_starlark_go//starlark"], 24 | ) 25 | -------------------------------------------------------------------------------- /_examples/k8s/nginx.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Skycfg Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | 17 | load("app.cfg", "deployment") 18 | 19 | def main(ctx): 20 | return [deployment("nginx")] 21 | 22 | -------------------------------------------------------------------------------- /go/assertmodule/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "assertmodule", 5 | srcs = [ 6 | "assert.go", 7 | "fail.go", 8 | ], 9 | importpath = "github.com/stripe/skycfg/go/assertmodule", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "@net_starlark_go//starlark", 13 | "@net_starlark_go//starlarkstruct", 14 | "@net_starlark_go//syntax", 15 | ], 16 | ) 17 | 18 | go_test( 19 | name = "assertmodule_test", 20 | size = "small", 21 | srcs = ["assert_test.go"], 22 | embed = [":assertmodule"], 23 | deps = [ 24 | "@net_starlark_go//starlark", 25 | "@net_starlark_go//starlarkstruct", 26 | "@net_starlark_go//syntax", 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /debugger/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "debugger", 5 | srcs = [ 6 | "debugger.go", 7 | "frame.go", 8 | "vars.go", 9 | ], 10 | importpath = "github.com/stripe/skycfg/debugger", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "@net_starlark_go//repl", 14 | "@net_starlark_go//starlark", 15 | "@net_starlark_go//syntax", 16 | ], 17 | ) 18 | 19 | go_test( 20 | name = "debugger_test", 21 | srcs = [ 22 | "debugger_test.go", 23 | "example_test.go", 24 | "export_test.go", 25 | ], 26 | embed = [":debugger"], 27 | deps = [ 28 | "@com_github_stretchr_testify//assert", 29 | "@net_starlark_go//starlark", 30 | "@net_starlark_go//syntax", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /go/parallelmodule/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "parallelmodule", 5 | srcs = [ 6 | "parallelmodule.go", 7 | "thread.go", 8 | ], 9 | importpath = "github.com/stripe/skycfg/go/parallelmodule", 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "@net_starlark_go//starlark", 13 | "@net_starlark_go//starlarkstruct", 14 | "@net_starlark_go//syntax", 15 | "@org_golang_x_sync//errgroup", 16 | ], 17 | ) 18 | 19 | go_test( 20 | name = "parallelmodule_test", 21 | srcs = ["parallelmodule_test.go"], 22 | embed = [":parallelmodule"], 23 | deps = [ 24 | "@com_github_stretchr_testify//assert", 25 | "@com_github_stretchr_testify//require", 26 | "@net_starlark_go//starlark", 27 | "@net_starlark_go//syntax", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /_examples/envoy/address.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Skycfg Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | 17 | corev2 = proto.package("envoy.api.v2.core") 18 | 19 | 20 | def address(addr, port): 21 | return corev2.Address( 22 | socket_address=corev2.SocketAddress( 23 | address=addr, 24 | port_value=port, 25 | ) 26 | ) 27 | -------------------------------------------------------------------------------- /internal/test_proto/BUILD: -------------------------------------------------------------------------------- 1 | load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") 2 | load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 3 | 4 | # gazelle:exclude package.go 5 | 6 | proto_library( 7 | name = "test_proto", 8 | srcs = [ 9 | "test_proto_v2.proto", 10 | "test_proto_v3.proto", 11 | ], 12 | import_prefix = "github.com/stripe/skycfg", # keep 13 | visibility = ["//:__subpackages__"], 14 | deps = [ 15 | "@com_google_protobuf//:any_proto", 16 | "@com_google_protobuf//:wrappers_proto", 17 | ], 18 | ) 19 | 20 | go_proto_library( 21 | name = "test_proto_go_proto", 22 | importpath = "github.com/stripe/skycfg/internal/test_proto", 23 | proto = ":test_proto", 24 | visibility = ["//:__subpackages__"], 25 | deps = [ 26 | "@io_bazel_rules_go//proto/wkt:any_go_proto", # keep 27 | "@io_bazel_rules_go//proto/wkt:wrappers_go_proto", # keep 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /go/protomodule/iter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | //go:build go1.23 18 | 19 | package protomodule 20 | 21 | import ( 22 | "iter" 23 | 24 | "go.starlark.net/starlark" 25 | ) 26 | 27 | func (m *protoMap) Entries() iter.Seq2[starlark.Value, starlark.Value] { return m.dict.Entries() } 28 | 29 | func (r *protoRepeated) Elements() iter.Seq[starlark.Value] { return r.list.Elements() } 30 | -------------------------------------------------------------------------------- /_examples/envoy/envoy.yaml: -------------------------------------------------------------------------------- 1 | node: 2 | id: "envoy" 3 | cluster: "cluster" 4 | 5 | admin: 6 | access_log_path: /tmp/admin_access.log 7 | address: 8 | socket_address: { address: 0.0.0.0, port_value: 9901 } 9 | 10 | static_resources: 11 | clusters: 12 | - name: ads 13 | connect_timeout: 0.25s 14 | type: LOGICAL_DNS 15 | # Comment out the following line to test on v6 networks 16 | dns_lookup_family: V4_ONLY 17 | http2_protocol_options: {} 18 | lb_policy: ROUND_ROBIN 19 | health_checks: 20 | grpc_health_check: {} 21 | timeout: 0.1s 22 | interval: 1s 23 | unhealthy_threshold: 1 24 | healthy_threshold: 1 25 | load_assignment: 26 | cluster_name: ads 27 | endpoints: 28 | - lb_endpoints: 29 | - endpoint: 30 | address: 31 | socket_address: 32 | address: host.docker.internal 33 | port_value: 8080 34 | 35 | dynamic_resources: 36 | cds_config: {ads: {}} 37 | lds_config: {ads: {}} 38 | 39 | ads_config: 40 | api_type: GRPC 41 | transport_api_version: V2 42 | grpc_services: 43 | envoy_grpc: 44 | cluster_name: ads 45 | -------------------------------------------------------------------------------- /debugger/example_test.go: -------------------------------------------------------------------------------- 1 | package debugger_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "go.starlark.net/starlark" 10 | "go.starlark.net/syntax" 11 | 12 | "github.com/stripe/skycfg/debugger" 13 | ) 14 | 15 | func Example() { 16 | // Run this example with 17 | // 18 | // go test -c github.com/stripe/skycfg/debugger && ./debugger.test -example 19 | 20 | prog := ` 21 | def g(): 22 | closure = [42] 23 | def f(): 24 | local_var = 1 25 | breakpoint() 26 | print("closure=" + str(closure)) 27 | return f 28 | 29 | g()() 30 | ` 31 | 32 | dbg := debugger.New() 33 | thread := starlark.Thread{ 34 | Print: printWithPos, 35 | } 36 | starlark.ExecFileOptions(&syntax.FileOptions{}, &thread, "", prog, starlark.StringDict{ 37 | "predeclared": starlark.String("value"), 38 | "breakpoint": dbg.Breakpoint(), 39 | }) 40 | } 41 | 42 | func printWithPos(thread *starlark.Thread, msg string) { 43 | var buf bytes.Buffer 44 | if thread.CallStackDepth() > 1 { 45 | pos := thread.CallFrame(1).Pos 46 | fmt.Fprintf(&buf, "[%v] ", pos) 47 | } 48 | fmt.Fprintf(&buf, msg) 49 | buf.WriteByte('\n') 50 | 51 | io.Copy(os.Stdout, &buf) 52 | } 53 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // This _test.go file exists in package skycfg to expose certain helpers to tests, 18 | // which live in package skycfg_test. 19 | // See https://github.com/golang/go/issues/56995 on how this works. 20 | 21 | package skycfg 22 | 23 | import "sort" 24 | 25 | // KeysForTestOnly is a test helper that returns the keys in the [LoadCache]. 26 | func (cache *LoadCache) KeysForTestOnly() []string { 27 | var keys []string 28 | cache.cache.Range(func(key, _ any) bool { 29 | keys = append(keys, key.(string)) 30 | return true 31 | }) 32 | sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) 33 | return keys 34 | } 35 | -------------------------------------------------------------------------------- /_examples/repl/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stripe/skycfg/_examples/repl 2 | 3 | require ( 4 | github.com/chzyer/logex v1.1.10 // indirect 5 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect 6 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect 7 | github.com/davecgh/go-spew v1.1.1 // indirect 8 | github.com/envoyproxy/go-control-plane v0.6.0 9 | github.com/ghodss/yaml v1.0.0 // indirect 10 | github.com/gogo/googleapis v1.1.0 // indirect 11 | github.com/gogo/protobuf v1.3.1 12 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect 13 | github.com/json-iterator/go v1.1.5 // indirect 14 | github.com/lyft/protoc-gen-validate v0.0.7 // indirect 15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 16 | github.com/modern-go/reflect2 v1.0.1 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | github.com/spf13/pflag v1.0.3 // indirect 19 | github.com/stretchr/testify v1.2.2 // indirect 20 | github.com/stripe/skycfg v0.0.0 21 | go.starlark.net v0.0.0-20201204201740-42d4f566359b 22 | google.golang.org/grpc v1.15.0 // indirect 23 | gopkg.in/inf.v0 v0.9.1 // indirect 24 | gopkg.in/yaml.v2 v2.2.1 25 | k8s.io/api v0.0.0-20181009084535-e3c5c37695fb 26 | k8s.io/apimachinery v0.0.0-20181009084401-76721d167b70 // indirect 27 | ) 28 | 29 | replace github.com/stripe/skycfg => ../../ 30 | -------------------------------------------------------------------------------- /go/parallelmodule/thread.go: -------------------------------------------------------------------------------- 1 | package parallelmodule 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "go.starlark.net/starlark" 8 | ) 9 | 10 | const ( 11 | contextKey = "context" 12 | threadCreatorKey = "parallel.thread_creator" // ThreadCreator 13 | 14 | parentThreadKey = "parallel.parent_thread" // *starlark.Thread 15 | ) 16 | 17 | type ThreadCreator func(parent *starlark.Thread, parallelFunc string, idx int) *starlark.Thread 18 | 19 | func GetThreadCreator(t *starlark.Thread) ThreadCreator { 20 | if tc, ok := t.Local(threadCreatorKey).(ThreadCreator); ok { 21 | return tc 22 | } 23 | return DefaultThreadCreator 24 | } 25 | 26 | func DefaultThreadCreator(parent *starlark.Thread, parallelFunc string, idx int) *starlark.Thread { 27 | nt := &starlark.Thread{ 28 | Name: fmt.Sprintf("%s > %s[%d]", parent.Name, parallelFunc, idx), 29 | Print: parent.Print, 30 | } 31 | if ctx, ok := parent.Local(contextKey).(context.Context); ok { 32 | nt.SetLocal(contextKey, ctx) 33 | } 34 | if tc, ok := parent.Local(threadCreatorKey).(ThreadCreator); ok { 35 | nt.SetLocal(threadCreatorKey, tc) 36 | } 37 | return nt 38 | } 39 | 40 | func newThread(parent *starlark.Thread, parallelFunc string, idx int) (*starlark.Thread, error) { 41 | tc := GetThreadCreator(parent) 42 | nt := tc(parent, parallelFunc, idx) 43 | nt.SetLocal(parentThreadKey, parent) 44 | return nt, nil 45 | } 46 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # The AUTHORS file lists the copyright holders; this file 2 | # lists people. For example, Stripe employees are listed here 3 | # but not in AUTHORS, because Stripe holds the copyright. 4 | # 5 | # Names should be added to this file as: 6 | # Name 7 | 8 | Aditya Anchuri 9 | Alex Gaynor 10 | Alexander Pakulov 11 | Andrew Bloom 12 | Benjamin Yolken 13 | Can Yucel # GM Cruise LLC 14 | Charles Xu 15 | Chun-Hung Hsiao 16 | Dmitry Ilyevsky 17 | Dmitry Ilyevsky # GM Cruise LLC 18 | Garvin Pang 19 | Henry Coelho 20 | Isaac Diamond 21 | James Woglom 22 | Josh Chorlton 23 | John Millikin 24 | Julian Brown 25 | Kevin Lin 26 | Matt Moriarity 27 | Peter N 28 | Ryan Brewster # Stripe, Inc. 29 | Seena Burns 30 | Sushain Cherivirala # Stripe, Inc. 31 | Taras Tsugrii 32 | Timothy Gu 33 | Yuhuan Qiu 34 | 35 | # Please alphabetize new entries. 36 | -------------------------------------------------------------------------------- /_examples/k8s/app.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Skycfg Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | 17 | appsv1 = proto.package("k8s.io.api.apps.v1") 18 | corev1 = proto.package("k8s.io.api.core.v1") 19 | metav1 = proto.package("k8s.io.apimachinery.pkg.apis.meta.v1") 20 | 21 | def container(name): 22 | return corev1.Container( 23 | name = name, 24 | image = name + ":latest", 25 | ports = [ 26 | corev1.ContainerPort(containerPort = 80) 27 | ], 28 | ) 29 | 30 | def deployment(name): 31 | d = appsv1.Deployment() 32 | d.metadata.name = name 33 | 34 | spec = d.spec 35 | spec.selector = metav1.LabelSelector( 36 | matchLabels = {"app": name}, 37 | ) 38 | spec.replicas = 1 39 | 40 | tmpl = spec.template 41 | tmpl.metadata.labels = {"app": name} 42 | tmpl.spec.containers = [ 43 | container(name), 44 | ] 45 | 46 | return d 47 | 48 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:def.bzl", "gazelle") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 3 | 4 | # gazelle:prefix github.com/stripe/skycfg 5 | # gazelle:build_file_name BUILD.bazel,BUILD 6 | # gazelle:go_naming_convention import 7 | 8 | # gazelle:exclude _examples 9 | 10 | go_library( 11 | name = "skycfg", 12 | srcs = [ 13 | "reader.go", 14 | "skycfg.go", 15 | ], 16 | importpath = "github.com/stripe/skycfg", 17 | visibility = ["//visibility:public"], 18 | deps = [ 19 | "//go/assertmodule", 20 | "//go/hashmodule", 21 | "//go/protomodule", 22 | "//go/urlmodule", 23 | "//go/yamlmodule", 24 | "@net_starlark_go//lib/json", 25 | "@net_starlark_go//starlark", 26 | "@net_starlark_go//starlarkstruct", 27 | "@net_starlark_go//syntax", 28 | "@org_golang_google_protobuf//proto", 29 | "@org_golang_google_protobuf//reflect/protoreflect", 30 | "@org_golang_google_protobuf//reflect/protoregistry", 31 | ], 32 | ) 33 | 34 | go_test( 35 | name = "skycfg_test", 36 | size = "small", 37 | srcs = [ 38 | "export_test.go", 39 | "skycfg_test.go", 40 | ], 41 | embed = [":skycfg"], 42 | deps = [ 43 | "//internal/test_proto:test_proto_go_proto", 44 | "@net_starlark_go//starlark", 45 | "@net_starlark_go//syntax", 46 | "@org_golang_google_protobuf//proto", 47 | "@org_golang_google_protobuf//types/known/wrapperspb", 48 | ], 49 | ) 50 | 51 | gazelle(name = "gazelle") 52 | -------------------------------------------------------------------------------- /_examples/envoy/envoy.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Skycfg Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | 17 | load("cluster.cfg", "proxy_cluster") 18 | load("address.cfg", "address") 19 | load("listener.cfg", "proxy_listener") 20 | 21 | 22 | def clusters(website, cluster_name): 23 | return [ 24 | proxy_cluster(website, cluster_name), 25 | ] 26 | 27 | 28 | def listeners(listen_addr, cluster_name): 29 | return [ 30 | proxy_listener(listen_addr, cluster_name), 31 | ] 32 | 33 | 34 | def proxy_resources(cluster_name, from_addr, to): 35 | resources = listeners(from_addr, cluster_name) + clusters(to, cluster_name) 36 | return resources 37 | 38 | 39 | def main(ctx): 40 | resources = proxy_resources( 41 | "first", 42 | from_addr=address("0.0.0.0", 10000), 43 | to="example.com", 44 | ) + proxy_resources( 45 | "second", 46 | from_addr=address("0.0.0.0", 10001), 47 | to="example.org", 48 | ) 49 | return resources 50 | -------------------------------------------------------------------------------- /debugger/debugger_test.go: -------------------------------------------------------------------------------- 1 | package debugger_test 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | 10 | . "github.com/stripe/skycfg/debugger" 11 | ) 12 | 13 | var runExample = flag.Bool("example", false, "run debugger on example program") 14 | 15 | func TestMain(m *testing.M) { 16 | flag.Parse() 17 | if *runExample { 18 | Example() 19 | os.Exit(0) 20 | } 21 | os.Exit(m.Run()) 22 | } 23 | 24 | type ( 25 | fakeMapValue map[string]string 26 | fakeFuncValue func() 27 | fakeStructValue struct { 28 | f func() 29 | a int 30 | } 31 | ) 32 | 33 | func TestSafeEq(t *testing.T) { 34 | t.Parallel() 35 | 36 | t.Run("Map", func(t *testing.T) { 37 | t.Parallel() 38 | 39 | a, b, nilMap := fakeMapValue{}, fakeMapValue{}, fakeMapValue(nil) 40 | assert.False(t, SafeEq(a, b)) 41 | assert.True(t, SafeEq(a, a)) 42 | assert.False(t, SafeEq(a, nilMap)) 43 | }) 44 | 45 | t.Run("Func", func(t *testing.T) { 46 | t.Parallel() 47 | 48 | f := func() {} 49 | a, b, nilFunc := fakeFuncValue(f), fakeFuncValue(f), fakeFuncValue(nil) 50 | assert.True(t, SafeEq(a, b)) 51 | assert.True(t, SafeEq(a, a)) 52 | assert.False(t, SafeEq(a, nilFunc)) 53 | // Remember SafeEq errs on the side of returning true, so don't test anything more complex than this. 54 | }) 55 | 56 | t.Run("Struct", func(t *testing.T) { 57 | t.Parallel() 58 | 59 | f := func() {} 60 | a, b, nilFunc := fakeStructValue{f: f, a: 1}, fakeStructValue{f: f, a: 2}, fakeStructValue{f: nil, a: 1} 61 | assert.False(t, SafeEq(a, b)) 62 | assert.True(t, SafeEq(a, a)) 63 | assert.False(t, SafeEq(a, nilFunc)) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: "Tests" 2 | on: ["push", "pull_request"] 3 | 4 | # This disables all permissions since we don't use GITHUB_TOKEN. 5 | permissions: {} 6 | 7 | jobs: 8 | tests: 9 | name: "Go ${{ matrix.go.series }}" 10 | runs-on: "ubuntu-24.04" 11 | container: 12 | # Keep this in sync with .bazelversion 13 | image: "docker://gcr.io/bazel-public/bazel:8.4.2" 14 | options: --user root 15 | 16 | strategy: 17 | matrix: 18 | go: 19 | # Keep minimum version up to date with go.mod 20 | # Keep version list in sync with MODULE.bazel 21 | - series: "1.22" 22 | version: "1.22.12" 23 | - series: "1.23" 24 | version: "1.23.12" 25 | - series: "1.24" 26 | version: "1.24.9" 27 | - series: "1.25" 28 | version: "1.25.3" 29 | 30 | steps: 31 | - uses: "actions/checkout@v4" 32 | with: 33 | path: "src" 34 | - uses: "actions/cache@v4" 35 | with: 36 | path: cache 37 | key: "go=${{ matrix.go.version }};v=1" 38 | - name: "bazel test //..." 39 | working-directory: src 40 | run: | 41 | exec bazel test \ 42 | --announce_rc \ 43 | --curses=no \ 44 | --color=yes \ 45 | --verbose_failures \ 46 | --test_output=errors \ 47 | --test_verbose_timeout_warnings \ 48 | --repository_cache="$GITHUB_WORKSPACE/cache/repository_cache" \ 49 | --disk_cache="$GITHUB_WORKSPACE/cache/disk_cache" \ 50 | --@io_bazel_rules_go//go/config:race \ 51 | --@io_bazel_rules_go//go/toolchain:sdk_version="$GO_VERSION" \ 52 | //... 53 | env: 54 | GO_VERSION: ${{ matrix.go.version }} 55 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module(name = "com_github_stripe_skycfg") 2 | 3 | bazel_dep( 4 | name = "rules_go", 5 | version = "0.57.0", 6 | repo_name = "io_bazel_rules_go", 7 | ) 8 | bazel_dep( 9 | name = "gazelle", 10 | version = "0.45.0", 11 | repo_name = "bazel_gazelle", 12 | ) 13 | 14 | # NB: this must come BEFORE bazel_dep(name = "protobuf") because they register the from-source toolchain, 15 | # and the first registration wins. 16 | bazel_dep( 17 | name = "toolchains_protoc", 18 | version = "0.5.0", 19 | ) 20 | 21 | single_version_override( 22 | module_name = "rules_go", 23 | patch_strip = 1, 24 | # Only necessary for Go <1.24. Remove when we bump the minimum version. 25 | patches = ["//tools:rules_go_macos.patch"], 26 | ) 27 | 28 | protobuf_version = "30.1" 29 | 30 | protoc = use_extension("@toolchains_protoc//protoc:extensions.bzl", "protoc") 31 | protoc.toolchain( 32 | version = "v" + protobuf_version, 33 | ) 34 | 35 | bazel_dep( 36 | name = "protobuf", 37 | version = protobuf_version, 38 | repo_name = "com_google_protobuf", 39 | ) 40 | 41 | go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk") 42 | 43 | # Always put the minimum version first, as that is the default Go version used by Bazel. 44 | # Keep version list in sync with .github/workflows/tests.yaml and go.mod. 45 | go_sdk.download(version = "1.22.12") 46 | go_sdk.download(version = "1.23.12") 47 | go_sdk.download(version = "1.24.9") 48 | go_sdk.download(version = "1.25.3") 49 | 50 | go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps") 51 | go_deps.from_file(go_mod = "//:go.mod") 52 | use_repo( 53 | go_deps, 54 | "com_github_spaolacci_murmur3", 55 | "com_github_stretchr_testify", 56 | "in_gopkg_yaml_v2", 57 | "net_starlark_go", 58 | "org_golang_google_protobuf", 59 | "org_golang_x_sync", 60 | ) 61 | -------------------------------------------------------------------------------- /_examples/k8s/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stripe/skycfg/_examples/k8s 2 | 3 | require ( 4 | cloud.google.com/go v0.26.0 // indirect 5 | github.com/davecgh/go-spew v1.1.1 // indirect 6 | github.com/ghodss/yaml v1.0.0 // indirect 7 | github.com/gogo/protobuf v1.3.1 8 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect 9 | github.com/golang/protobuf v1.4.1 10 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect 11 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect 12 | github.com/googleapis/gnostic v0.2.0 // indirect 13 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect 14 | github.com/imdario/mergo v0.3.6 // indirect 15 | github.com/json-iterator/go v1.1.5 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 17 | github.com/modern-go/reflect2 v1.0.1 // indirect 18 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/spf13/pflag v1.0.3 // indirect 21 | github.com/stretchr/testify v1.2.2 // indirect 22 | github.com/stripe/skycfg v0.0.0 23 | golang.org/x/crypto v0.0.0-20181029103014-dab2b1051b5d // indirect 24 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816 // indirect 25 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be // indirect 26 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect 27 | golang.org/x/text v0.3.0 // indirect 28 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect 29 | google.golang.org/appengine v1.1.0 // indirect 30 | gopkg.in/inf.v0 v0.9.1 // indirect 31 | gopkg.in/yaml.v2 v2.2.1 32 | k8s.io/api v0.0.0-20181027024800-9fcf73cc980b 33 | k8s.io/apimachinery v0.0.0-20181026144617-b7f9f1fa80ae 34 | k8s.io/client-go v9.0.0+incompatible 35 | ) 36 | 37 | replace github.com/stripe/skycfg => ../../ 38 | -------------------------------------------------------------------------------- /debugger/README.md: -------------------------------------------------------------------------------- 1 | # debugger 2 | 3 | Package debugger implements a simple interactive debugger for [starlark-go](https://github.com/google/starlark-go). 4 | It can be used in any starlark-go application, not necessarily one that uses skycfg. 5 | 6 | ## Example session 7 | 8 | ``` 9 | ❯ go test -c github.com/stripe/skycfg/debugger && ./debugger.test -example 10 | [:6:19] breakpoint: Stopped 11 | [:6:19] Available functions: c[ont[inue_]] (or Ctrl+D), q[uit], w[here]/b[ack]t[race], d[own], u[p], f[rame], vars, globals, locals 12 | >>> bt() 13 | Traceback (most recent call last): 14 | 1. :10:4: in 15 | > 0. :6:19: in f 16 | >>> vars() 17 | All predeclared symbols in frame 0 (f): 18 | predeclared: string = "value" 19 | breakpoint: builtin_function_or_method = 20 | 21 | All globals in frame 0 (f): 22 | g: function = 23 | 24 | All free vars in frame 0 (f): 25 | closure (:3:5): list = [42] 26 | 27 | All locals in frame 0 (f): 28 | local_var (:5:9): int = 1 29 | >>> closure 30 | [42] 31 | >>> closure.append(local_var) 32 | >>> closure 33 | [42, 1] 34 | ``` 35 | 36 | ### Switch to a different frame 37 | 38 | ``` 39 | >>> f(1) 40 | breakpoint: Stopped at :10:4: in 41 | >>> bt() 42 | Traceback (most recent call last): 43 | > 1. :10:4: in 44 | 0. :6:19: in f 45 | >>> vars() 46 | All predeclared symbols in frame 1 (): 47 | predeclared: string = "value" 48 | breakpoint: builtin_function_or_method = 49 | 50 | All globals in frame 1 (): 51 | g: function = 52 | 53 | All free vars in frame 1 (): 54 | 55 | All locals in frame 1 (): 56 | ``` 57 | 58 | ### Continue execution 59 | Notice that the change to variable `closure` is persisted. 60 | 61 | ``` 62 | >>> cont() 63 | closure=[42, 1] 64 | ``` 65 | -------------------------------------------------------------------------------- /go/protomodule/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "protomodule", 5 | srcs = [ 6 | "iter.go", 7 | "merge.go", 8 | "protomodule.go", 9 | "protomodule_enum.go", 10 | "protomodule_list.go", 11 | "protomodule_map.go", 12 | "protomodule_message.go", 13 | "protomodule_message_type.go", 14 | "protomodule_package.go", 15 | "type_conversions.go", 16 | ], 17 | importpath = "github.com/stripe/skycfg/go/protomodule", 18 | visibility = ["//visibility:public"], 19 | deps = [ 20 | "@net_starlark_go//starlark", 21 | "@net_starlark_go//starlarkstruct", 22 | "@net_starlark_go//syntax", 23 | "@org_golang_google_protobuf//encoding/protojson", 24 | "@org_golang_google_protobuf//encoding/prototext", 25 | "@org_golang_google_protobuf//proto", 26 | "@org_golang_google_protobuf//reflect/protoreflect", 27 | "@org_golang_google_protobuf//reflect/protoregistry", 28 | "@org_golang_google_protobuf//types/dynamicpb", 29 | "@org_golang_google_protobuf//types/known/anypb", 30 | "@org_golang_google_protobuf//types/known/wrapperspb", 31 | ], 32 | ) 33 | 34 | go_test( 35 | name = "protomodule_test", 36 | srcs = [ 37 | "bench_test.go", 38 | "iter_test.go", 39 | "protomodule_message_test.go", 40 | "protomodule_test.go", 41 | ], 42 | embed = [":protomodule"], 43 | deps = [ 44 | "//internal/test_proto:test_proto_go_proto", 45 | "@net_starlark_go//starlark", 46 | "@net_starlark_go//starlarkstruct", 47 | "@net_starlark_go//syntax", 48 | "@org_golang_google_protobuf//encoding/prototext", 49 | "@org_golang_google_protobuf//proto", 50 | "@org_golang_google_protobuf//reflect/protoreflect", 51 | "@org_golang_google_protobuf//reflect/protoregistry", 52 | "@org_golang_google_protobuf//types/known/anypb", 53 | "@org_golang_google_protobuf//types/known/wrapperspb", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /go/protomodule/iter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | //go:build go1.23 18 | 19 | package protomodule 20 | 21 | import ( 22 | "maps" 23 | "reflect" 24 | "slices" 25 | "testing" 26 | 27 | "go.starlark.net/starlark" 28 | 29 | pb "github.com/stripe/skycfg/internal/test_proto" 30 | ) 31 | 32 | func TestListType_Elements(t *testing.T) { 33 | msg := (&pb.MessageV3{}).ProtoReflect().Descriptor() 34 | listFieldDesc := msg.Fields().ByName("r_string") 35 | 36 | rep := newProtoRepeated(listFieldDesc) 37 | rep.Append(starlark.String("a")) 38 | rep.Append(starlark.String("b")) 39 | 40 | got := slices.Collect(rep.Elements()) 41 | want := []starlark.Value{starlark.String("a"), starlark.String("b")} 42 | if !reflect.DeepEqual(want, got) { 43 | t.Fatalf("wanted: %v\ngot : %v", want, got) 44 | } 45 | } 46 | 47 | func TestMapType_Entries(t *testing.T) { 48 | msg := (&pb.MessageV3{}).ProtoReflect().Descriptor() 49 | mapFieldDesc := msg.Fields().ByName("map_string") 50 | 51 | pmap := newProtoMap(mapFieldDesc.MapKey(), mapFieldDesc.MapValue()) 52 | pmap.SetKey(starlark.String("a"), starlark.String("1")) 53 | pmap.SetKey(starlark.String("b"), starlark.String("2")) 54 | 55 | got := maps.Collect(pmap.Entries()) 56 | want := map[starlark.Value]starlark.Value{ 57 | starlark.String("a"): starlark.String("1"), 58 | starlark.String("b"): starlark.String("2"), 59 | } 60 | if !reflect.DeepEqual(want, got) { 61 | t.Fatalf("wanted: %v\ngot : %v", want, got) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /_examples/wasm/go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 2 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 3 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 4 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 6 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 7 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 8 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 9 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 10 | go.starlark.net v0.0.0-20201204201740-42d4f566359b h1:yHUzJ1WfcdR1oOafytJ6K1/ntYwnEIXICNVzHb+FzbA= 11 | go.starlark.net v0.0.0-20201204201740-42d4f566359b/go.mod h1:5YFcFnRptTN+41758c2bMPiqpGg4zBfYji1IQz8wNFk= 12 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= 13 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 15 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 16 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 17 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 18 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 22 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 23 | -------------------------------------------------------------------------------- /go/urlmodule/urlmodule.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package urlmodule defines a Starlark module of URL-related functions. 18 | package urlmodule 19 | 20 | import ( 21 | "fmt" 22 | "net/url" 23 | 24 | "go.starlark.net/starlark" 25 | "go.starlark.net/starlarkstruct" 26 | ) 27 | 28 | // NewModule returns a Starlark module of URL-related functions. 29 | // 30 | // url = module( 31 | // encode_query, 32 | // ) 33 | // 34 | // See `docs/modules.asciidoc` for details on the API of each function. 35 | func NewModule() *starlarkstruct.Module { 36 | return &starlarkstruct.Module{ 37 | Name: "url", 38 | Members: starlark.StringDict{ 39 | "encode_query": starlark.NewBuiltin("url.encode_query", encodeQuery), 40 | }, 41 | } 42 | } 43 | 44 | func encodeQuery(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 45 | var d *starlark.Dict 46 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &d); err != nil { 47 | return nil, err 48 | } 49 | 50 | urlVals := url.Values{} 51 | 52 | for _, itemPair := range d.Items() { 53 | key := itemPair[0] 54 | value := itemPair[1] 55 | 56 | keyStr, keyIsStr := key.(starlark.String) 57 | if !keyIsStr { 58 | return nil, fmt.Errorf("Key is not string: %+v", key) 59 | } 60 | 61 | valStr, valIsStr := value.(starlark.String) 62 | if !valIsStr { 63 | return nil, fmt.Errorf("Value is not string: %+v", value) 64 | } 65 | 66 | urlVals.Add(string(keyStr), string(valStr)) 67 | } 68 | 69 | return starlark.String(urlVals.Encode()), nil 70 | } 71 | -------------------------------------------------------------------------------- /go/hashmodule/hashmodule_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package hashmodule 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | 23 | "go.starlark.net/starlark" 24 | ) 25 | 26 | type hashTestCase struct { 27 | input string 28 | hashFunc string 29 | expOutput string 30 | } 31 | 32 | func TestHashes(t *testing.T) { 33 | thread := new(starlark.Thread) 34 | env := starlark.StringDict{ 35 | "hash": NewModule(), 36 | } 37 | 38 | testCases := []hashTestCase{ 39 | hashTestCase{ 40 | input: "test md5 string", 41 | hashFunc: "md5", 42 | expOutput: "e3c37791f9070bc459d677d015016f90", 43 | }, 44 | hashTestCase{ 45 | input: "test sha1 string", 46 | hashFunc: "sha1", 47 | expOutput: "e4d245f6e79cc13f5a4c0261dfb991438b86fed9", 48 | }, 49 | hashTestCase{ 50 | input: "test sha256 string", 51 | hashFunc: "sha256", 52 | expOutput: "a9c78816353b119a0ba2a1281675b147fd47abee11a8d41d5abb739dce8273b7", 53 | }, 54 | hashTestCase{ 55 | input: "test murmur3 string", 56 | hashFunc: "murmur3", 57 | expOutput: "91dd87efc613eb2c", 58 | }, 59 | } 60 | 61 | for _, testCase := range testCases { 62 | v, err := starlark.Eval( 63 | thread, 64 | "", 65 | fmt.Sprintf(`hash.%s("%s")`, testCase.hashFunc, testCase.input), 66 | env, 67 | ) 68 | if err != nil { 69 | t.Error("Error from eval", "\nExpected nil", "\nGot", err) 70 | } else if v != starlark.String(testCase.expOutput) { 71 | t.Error( 72 | "Bad result from func", testCase.hashFunc, 73 | "\nExpected", starlark.String(testCase.expOutput), 74 | "\nGot", v, 75 | ) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /go/protomodule/merge.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package protomodule 18 | 19 | import ( 20 | "fmt" 21 | 22 | "go.starlark.net/starlark" 23 | ) 24 | 25 | // Implements proto.merge merging src into dst, returning merged value 26 | func mergeField(dst, src starlark.Value) (starlark.Value, error) { 27 | if dst == nil { 28 | return src, nil 29 | } 30 | if src == nil { 31 | return dst, nil 32 | } 33 | 34 | if dst.Type() != src.Type() { 35 | return nil, mergeError(dst, src) 36 | } 37 | 38 | switch dst := dst.(type) { 39 | case *protoRepeated: 40 | src, ok := src.(*protoRepeated) 41 | if !ok { 42 | return nil, mergeError(dst, src) 43 | } 44 | 45 | newList := newProtoRepeated(dst.fieldDesc) 46 | 47 | err := newList.Extend(dst) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | err = newList.Extend(src) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return newList, nil 58 | case *protoMap: 59 | src, ok := src.(*protoMap) 60 | if !ok { 61 | return nil, mergeError(dst, src) 62 | } 63 | 64 | newMap := newProtoMap(dst.mapKey, dst.mapValue) 65 | newMap.dict = dst.dict.Union(src.dict) 66 | return newMap, nil 67 | case *protoMessage: 68 | src, ok := src.(*protoMessage) 69 | if !ok { 70 | return nil, mergeError(dst, src) 71 | } 72 | 73 | newMessage, err := NewMessage(dst.emptyMsg.Interface()) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | newMessage.Merge(dst) 79 | newMessage.Merge(src) 80 | 81 | return newMessage, nil 82 | default: 83 | return src, nil 84 | } 85 | } 86 | 87 | func mergeError(dst, src starlark.Value) error { 88 | return fmt.Errorf("MergeError: Cannot merge protobufs of different types: Merge(%s, %s)", dst.Type(), src.Type()) 89 | } 90 | -------------------------------------------------------------------------------- /_examples/envoy/cluster.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Skycfg Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | 17 | corev2 = proto.package("envoy.api.v2.core") 18 | endpointv2 = proto.package("envoy.api.v2.endpoint") 19 | v2 = proto.package("envoy.api.v2") 20 | 21 | protobuf = proto.package("google.protobuf") 22 | 23 | load("address.cfg", "address") 24 | 25 | 26 | def health_checks(website): 27 | return corev2.HealthCheck( 28 | http_health_check=corev2.HealthCheck.HttpHealthCheck( 29 | path="/", 30 | host=website, 31 | ), 32 | unhealthy_threshold=1, 33 | healthy_threshold=1, 34 | timeout=protobuf.Duration(seconds=1), 35 | interval=protobuf.Duration(seconds=1), 36 | no_traffic_interval=protobuf.Duration(seconds=5), 37 | ) 38 | 39 | 40 | def proxy_cluster(website, cluster_name): 41 | cluster = v2.Cluster( 42 | name=cluster_name, 43 | dns_lookup_family=v2.Cluster.DnsLookupFamily.V4_ONLY, 44 | connect_timeout=protobuf.Duration( 45 | seconds=1, 46 | nanos=0, 47 | ), 48 | health_checks=[ 49 | health_checks(website), 50 | ], 51 | type=v2.Cluster.DiscoveryType.STRICT_DNS, 52 | load_assignment=v2.ClusterLoadAssignment( 53 | cluster_name=cluster_name, 54 | endpoints=[ 55 | endpointv2.LocalityLbEndpoints( 56 | lb_endpoints=[ 57 | endpointv2.LbEndpoint( 58 | endpoint=endpointv2.Endpoint( 59 | hostname=website, 60 | address=address(website, 80), 61 | ) 62 | ) 63 | ], 64 | ) 65 | ], 66 | ), 67 | ) 68 | 69 | return cluster 70 | -------------------------------------------------------------------------------- /go/urlmodule/urlmodule_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package urlmodule 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | 23 | "go.starlark.net/starlark" 24 | ) 25 | 26 | type UrlTestCase struct { 27 | name string 28 | skyExpr string 29 | expErr bool 30 | expOutput string 31 | } 32 | 33 | func TestEncodeQuery(t *testing.T) { 34 | thread := new(starlark.Thread) 35 | env := starlark.StringDict{ 36 | "url": NewModule(), 37 | } 38 | 39 | testCases := []UrlTestCase{ 40 | UrlTestCase{ 41 | name: "All good", 42 | skyExpr: `{"a": "value1 value2", "b": "/test/path"}`, 43 | expOutput: "a=value1+value2&b=%2Ftest%2Fpath", 44 | }, 45 | UrlTestCase{ 46 | name: "Called with a non-dict value", 47 | skyExpr: "abc", 48 | expErr: true, 49 | }, 50 | UrlTestCase{ 51 | name: "Key not a string", 52 | skyExpr: `{5: "a"}`, 53 | expErr: true, 54 | }, 55 | UrlTestCase{ 56 | name: "Value not a string", 57 | skyExpr: `{"a": 5}`, 58 | expErr: true, 59 | }, 60 | } 61 | 62 | for _, testCase := range testCases { 63 | v, err := starlark.Eval( 64 | thread, 65 | "", 66 | fmt.Sprintf("url.encode_query(%s)", testCase.skyExpr), 67 | env, 68 | ) 69 | if testCase.expErr { 70 | if err == nil { 71 | t.Error( 72 | "Bad eval err result for case", testCase.name, 73 | "\nExpected error", 74 | "\nGot", err) 75 | } 76 | } else { 77 | if err != nil { 78 | t.Error( 79 | "Bad eval err result for case", testCase.name, 80 | "\nExpected nil", 81 | "\nGot", err) 82 | } 83 | exp := starlark.String(testCase.expOutput) 84 | if v != exp { 85 | t.Error( 86 | "Bad return value for case", testCase.name, 87 | "\nExpected", exp, 88 | "\nGot", v, 89 | ) 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /internal/test_proto/test_proto_v2.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | syntax = "proto2"; 18 | 19 | option go_package = "github.com/stripe/skycfg/internal/test_proto"; 20 | package skycfg.test_proto; 21 | 22 | import "google/protobuf/wrappers.proto"; 23 | 24 | message MessageV2 { 25 | optional int32 f_int32 = 1; 26 | optional int64 f_int64 = 2; 27 | optional uint32 f_uint32 = 3; 28 | optional uint64 f_uint64 = 4; 29 | optional float f_float32 = 5; 30 | optional double f_float64 = 6; 31 | optional string f_string = 7 [default="default_str"];; 32 | optional bool f_bool = 8; 33 | 34 | optional MessageV2 f_submsg = 9; 35 | 36 | repeated string r_string = 10; 37 | repeated MessageV2 r_submsg = 11; 38 | 39 | map map_string = 12; 40 | map map_submsg = 13; 41 | 42 | message NestedMessage { 43 | optional string f_string = 1; 44 | 45 | message DoubleNestedMessage { 46 | optional string f_string = 1; 47 | } 48 | } 49 | optional NestedMessage f_nested_submsg = 16; 50 | 51 | enum NestedEnum { 52 | NESTED_ENUM_A = 0; 53 | NESTED_ENUM_B = 1; 54 | } 55 | optional ToplevelEnumV2 f_toplevel_enum = 14; 56 | optional NestedEnum f_nested_enum = 15; 57 | 58 | oneof f_oneof { 59 | string f_oneof_a = 17; 60 | string f_oneof_b = 18; 61 | } 62 | 63 | optional bytes f_bytes = 19; 64 | 65 | optional google.protobuf.BoolValue f_BoolValue = 20; 66 | optional google.protobuf.StringValue f_StringValue = 21; 67 | optional google.protobuf.DoubleValue f_DoubleValue = 22; 68 | optional google.protobuf.Int32Value f_Int32Value = 23; 69 | optional google.protobuf.Int64Value f_Int64Value = 24; 70 | optional google.protobuf.BytesValue f_BytesValue = 25; 71 | optional google.protobuf.UInt32Value f_Uint32Value = 26; 72 | optional google.protobuf.UInt64Value f_Uint64Value = 27; 73 | 74 | repeated google.protobuf.StringValue r_StringValue = 28; 75 | 76 | // NEXT: 29 77 | } 78 | 79 | enum ToplevelEnumV2 { 80 | TOPLEVEL_ENUM_V2_A = 0; 81 | TOPLEVEL_ENUM_V2_B = 1; 82 | } 83 | -------------------------------------------------------------------------------- /go/hashmodule/hashmodule.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package hashmodule defines a Starlark module of common hash functions. 18 | package hashmodule 19 | 20 | import ( 21 | "crypto/md5" 22 | "crypto/sha1" 23 | "crypto/sha256" 24 | "fmt" 25 | "hash" 26 | 27 | "github.com/spaolacci/murmur3" 28 | 29 | "go.starlark.net/starlark" 30 | "go.starlark.net/starlarkstruct" 31 | ) 32 | 33 | // NewModule returns a Starlark module of common hash functions. 34 | // 35 | // hash = module( 36 | // md5, 37 | // sha1, 38 | // sha256, 39 | // murmur3, 40 | // ) 41 | // 42 | // See `docs/modules.asciidoc` for details on the API of each function. 43 | func NewModule() *starlarkstruct.Module { 44 | return &starlarkstruct.Module{ 45 | Name: "hash", 46 | Members: starlark.StringDict{ 47 | "md5": starlark.NewBuiltin("hash.md5", fnHash(md5.New)), 48 | "sha1": starlark.NewBuiltin("hash.sha1", fnHash(sha1.New)), 49 | "sha256": starlark.NewBuiltin("hash.sha256", fnHash(sha256.New)), 50 | "murmur3": starlark.NewBuiltin("hash.murmur3", fnHash64(murmur3.New64)), 51 | }, 52 | } 53 | } 54 | 55 | func fnHash(hash func() hash.Hash) func(*starlark.Thread, *starlark.Builtin, starlark.Tuple, []starlark.Tuple) (starlark.Value, error) { 56 | return func(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 57 | var s starlark.String 58 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &s); err != nil { 59 | return nil, err 60 | } 61 | 62 | h := hash() 63 | h.Write([]byte(string(s))) 64 | return starlark.String(fmt.Sprintf("%x", h.Sum(nil))), nil 65 | } 66 | } 67 | 68 | func fnHash64(hash func() hash.Hash64) func(*starlark.Thread, *starlark.Builtin, starlark.Tuple, []starlark.Tuple) (starlark.Value, error) { 69 | return func(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 70 | var s starlark.String 71 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &s); err != nil { 72 | return nil, err 73 | } 74 | 75 | h := hash() 76 | h.Write([]byte(string(s))) 77 | return starlark.String(fmt.Sprintf("%x", h.Sum(nil))), nil 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /internal/test_proto/test_proto_v3.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | syntax = "proto3"; 18 | 19 | option go_package = "github.com/stripe/skycfg/internal/test_proto"; 20 | package skycfg.test_proto; 21 | 22 | import "google/protobuf/wrappers.proto"; 23 | import "google/protobuf/any.proto"; 24 | 25 | message MessageV3 { 26 | int32 f_int32 = 1; 27 | int64 f_int64 = 2; 28 | uint32 f_uint32 = 3; 29 | uint64 f_uint64 = 4; 30 | float f_float32 = 5; 31 | double f_float64 = 6; 32 | string f_string = 7; 33 | bool f_bool = 8; 34 | 35 | MessageV3 f_submsg = 9; 36 | 37 | repeated string r_string = 10; 38 | repeated MessageV3 r_submsg = 11; 39 | 40 | map map_string = 12; 41 | map map_submsg = 13; 42 | 43 | message NestedMessage { 44 | string f_string = 1; 45 | 46 | message DoubleNestedMessage { 47 | string f_string = 1; 48 | } 49 | } 50 | NestedMessage f_nested_submsg = 16; 51 | 52 | enum NestedEnum { 53 | NESTED_ENUM_A = 0; 54 | NESTED_ENUM_B = 1; 55 | } 56 | 57 | ToplevelEnumV3 f_toplevel_enum = 14; 58 | NestedEnum f_nested_enum = 15; 59 | 60 | oneof f_oneof { 61 | string f_oneof_a = 17; 62 | string f_oneof_b = 18; 63 | } 64 | 65 | bytes f_bytes = 19; 66 | 67 | google.protobuf.BoolValue f_BoolValue = 20; 68 | google.protobuf.StringValue f_StringValue = 21; 69 | google.protobuf.DoubleValue f_DoubleValue = 22; 70 | google.protobuf.Int32Value f_Int32Value = 23; 71 | google.protobuf.Int64Value f_Int64Value = 24; 72 | google.protobuf.BytesValue f_BytesValue = 25; 73 | google.protobuf.UInt32Value f_Uint32Value = 26; 74 | google.protobuf.UInt64Value f_Uint64Value = 27; 75 | 76 | repeated google.protobuf.StringValue r_StringValue = 28; 77 | 78 | google.protobuf.Any f_Any = 29; 79 | 80 | // Starlark reserved keywords 81 | // Reference: https://github.com/bazelbuild/starlark/blob/c8d88c388698b0ee49bc74737f56236af64da1b5/spec.md#lexical-elements 82 | bool pass = 30; 83 | bool return = 31; 84 | bool assert = 32; 85 | bool safe_ = 33; 86 | 87 | // NEXT: 34 88 | } 89 | 90 | enum ToplevelEnumV3 { 91 | TOPLEVEL_ENUM_V3_A = 0; 92 | TOPLEVEL_ENUM_V3_B = 1; 93 | } 94 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 2 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 3 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 6 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 10 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 14 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 15 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 16 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 17 | go.starlark.net v0.0.0-20250623223156-8bf495bf4e9a h1:4JpDHHQ9BoQWTX4F6nMBaZCz7OePNidT395Mr6ipbP8= 18 | go.starlark.net v0.0.0-20250623223156-8bf495bf4e9a/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= 19 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 20 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 21 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 22 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 24 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 25 | google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 26 | google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 29 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 30 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 31 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 32 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 33 | -------------------------------------------------------------------------------- /_examples/envoy/listener.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The Skycfg Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | 17 | v2 = proto.package("envoy.api.v2") 18 | listenerv2 = proto.package("envoy.api.v2.listener") 19 | routev3 = proto.package("envoy.config.route.v3") 20 | accesslogv3 = proto.package("envoy.config.accesslog.v3") 21 | 22 | hcmv3 = proto.package("envoy.extensions.filters.network.http_connection_manager.v3") 23 | fileAccessLoggers = proto.package("envoy.extensions.access_loggers.file.v3") 24 | 25 | 26 | def virtual_host(name): 27 | return routev3.VirtualHost( 28 | name=name, 29 | domains=["*"], 30 | routes=[ 31 | routev3.Route( 32 | match=routev3.RouteMatch(prefix="/"), 33 | route=routev3.RouteAction( 34 | cluster=name, 35 | auto_host_rewrite=True, 36 | ), 37 | ), 38 | ], 39 | ) 40 | 41 | 42 | def http_connection_manager(cluster_name): 43 | access_log_config = fileAccessLoggers.FileAccessLog( 44 | path="/dev/stdout", 45 | ) 46 | 47 | return hcmv3.HttpConnectionManager( 48 | stat_prefix="ingress_http", 49 | codec_type=hcmv3.HttpConnectionManager.CodecType.AUTO, 50 | access_log=[ 51 | accesslogv3.AccessLog( 52 | typed_config=proto.to_any(access_log_config), 53 | ), 54 | ], 55 | route_config=routev3.RouteConfiguration( 56 | name="route_" + cluster_name, 57 | virtual_hosts=[ 58 | virtual_host(cluster_name), 59 | ], 60 | ), 61 | http_filters=[ 62 | hcmv3.HttpFilter( 63 | name="envoy.filters.http.router", 64 | ) 65 | ], 66 | ) 67 | 68 | 69 | def proxy_listener(address, cluster_name): 70 | hcm = http_connection_manager(cluster_name) 71 | listener = v2.Listener( 72 | name="listener_" + cluster_name, 73 | address=address, 74 | filter_chains=[ 75 | listenerv2.FilterChain( 76 | filters=[ 77 | listenerv2.Filter( 78 | name="envoy.filters.network.http_connection_manager", 79 | typed_config=proto.to_any(hcm), 80 | ), 81 | ], 82 | ) 83 | ], 84 | ) 85 | return listener 86 | -------------------------------------------------------------------------------- /_examples/wasm/addressbook.proto: -------------------------------------------------------------------------------- 1 | // https://github.com/protocolbuffers/protobuf/blob/v3.6.1/examples/addressbook.proto 2 | 3 | // Copyright 2008 Google Inc. All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // See README.txt for information and build instructions. 32 | // 33 | // Note: START and END tags are used in comments to define sections used in 34 | // tutorials. They are not part of the syntax for Protocol Buffers. 35 | // 36 | // To get an in-depth walkthrough of this file and the related examples, see: 37 | // https://developers.google.com/protocol-buffers/docs/tutorials 38 | 39 | // [START declaration] 40 | syntax = "proto3"; 41 | package tutorial; 42 | 43 | import "google/protobuf/timestamp.proto"; 44 | // [END declaration] 45 | 46 | // [START java_declaration] 47 | option java_package = "com.example.tutorial"; 48 | option java_outer_classname = "AddressBookProtos"; 49 | // [END java_declaration] 50 | 51 | // [START csharp_declaration] 52 | option csharp_namespace = "Google.Protobuf.Examples.AddressBook"; 53 | // [END csharp_declaration] 54 | 55 | // [START messages] 56 | message Person { 57 | string name = 1; 58 | int32 id = 2; // Unique ID number for this person. 59 | string email = 3; 60 | 61 | enum PhoneType { 62 | MOBILE = 0; 63 | HOME = 1; 64 | WORK = 2; 65 | } 66 | 67 | message PhoneNumber { 68 | string number = 1; 69 | PhoneType type = 2; 70 | } 71 | 72 | repeated PhoneNumber phones = 4; 73 | 74 | google.protobuf.Timestamp last_updated = 5; 75 | } 76 | 77 | // Our address book file is just one of these. 78 | message AddressBook { 79 | repeated Person people = 1; 80 | } 81 | // [END messages] 82 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package skycfg 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io/fs" 23 | "os" 24 | "path" 25 | "path/filepath" 26 | "strings" 27 | ) 28 | 29 | // A FileReader controls how load() calls resolve and read other modules. 30 | type FileReader interface { 31 | // Resolve parses the "name" part of load("name", "symbol") to a path. This 32 | // is not required to correspond to a true path on the filesystem, but should 33 | // be "absolute" within the semantics of this FileReader. 34 | // 35 | // fromPath will be empty when loading the root module passed to Load(). 36 | Resolve(ctx context.Context, name, fromPath string) (path string, err error) 37 | 38 | // ReadFile reads the content of the file at the given path, which was 39 | // returned from Resolve(). 40 | ReadFile(ctx context.Context, path string) ([]byte, error) 41 | } 42 | 43 | type localFileReader struct { 44 | root string 45 | } 46 | 47 | // LocalFileReader returns a [FileReader] that resolves and loads files from 48 | // within a given filesystem directory. 49 | // LocalFileReader expects paths in load() to always use '/' as the separator, 50 | // regardless of the operating system's native path separator. 51 | func LocalFileReader(root string) FileReader { 52 | if root == "" { 53 | panic("LocalFileReader: empty root path") 54 | } 55 | return &localFileReader{root} 56 | } 57 | 58 | func (r *localFileReader) Resolve(ctx context.Context, name, fromPath string) (string, error) { 59 | if fromPath == "" { 60 | return name, nil 61 | } 62 | if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { 63 | return "", fmt.Errorf("load(%q): invalid character in module name", name) 64 | } 65 | resolved := filepath.Join(r.root, filepath.FromSlash(path.Clean("/"+name))) 66 | return resolved, nil 67 | } 68 | 69 | func (r *localFileReader) ReadFile(ctx context.Context, path string) ([]byte, error) { 70 | return os.ReadFile(path) 71 | } 72 | 73 | type fsFileReader struct { 74 | fsys fs.FS 75 | } 76 | 77 | // FSFileReader returns a [FileReader] that loads files from the given [fs.FS]. 78 | // Path resolution on a FSFileReader is just [path.Clean]. 79 | func FSFileReader(fsys fs.FS) FileReader { 80 | if fsys == nil { 81 | panic("FSFileReader: nil fsys") 82 | } 83 | return &fsFileReader{fsys: fsys} 84 | } 85 | 86 | func (r *fsFileReader) Resolve(ctx context.Context, name, fromPath string) (string, error) { 87 | return path.Clean(name), nil 88 | } 89 | 90 | func (r *fsFileReader) ReadFile(ctx context.Context, path string) ([]byte, error) { 91 | return fs.ReadFile(r.fsys, path) 92 | } 93 | -------------------------------------------------------------------------------- /_examples/wasm/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "syscall/js" 23 | 24 | "github.com/golang/protobuf/proto" 25 | "google.golang.org/protobuf/encoding/protojson" 26 | "google.golang.org/protobuf/encoding/prototext" 27 | yaml "gopkg.in/yaml.v2" 28 | 29 | "github.com/stripe/skycfg" 30 | _ "github.com/stripe/skycfg/_examples/wasm/addressbook" 31 | ) 32 | 33 | type stubFileReader struct { 34 | content string 35 | } 36 | 37 | func (*stubFileReader) Resolve(ctx context.Context, name, fromPath string) (string, error) { 38 | if fromPath == "" { 39 | return name, nil 40 | } 41 | return "", fmt.Errorf("load(%q): not available in webasm demo", name) 42 | } 43 | 44 | func (r *stubFileReader) ReadFile(ctx context.Context, path string) ([]byte, error) { 45 | return []byte(r.content), nil 46 | } 47 | 48 | func runDemo(content string) ([]js.Value, error) { 49 | config, err := skycfg.Load( 50 | context.Background(), 51 | "", 52 | skycfg.WithFileReader(&stubFileReader{content}), 53 | ) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | messages, err := config.Main(context.Background()) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | var out []js.Value 64 | for _, msg := range messages { 65 | msg := proto.MessageV2(msg) 66 | jsonData, err := (protojson.MarshalOptions{ 67 | UseProtoNames: true, 68 | Indent: " ", 69 | EmitUnpopulated: true, 70 | }).Marshal(msg) 71 | if err != nil { 72 | return nil, fmt.Errorf("json.Marshal: %v", err) 73 | } 74 | var yamlMap yaml.MapSlice 75 | if err := yaml.Unmarshal(jsonData, &yamlMap); err != nil { 76 | return nil, fmt.Errorf("yaml.Unmarshal: %v", err) 77 | } 78 | yamlData, err := yaml.Marshal(yamlMap) 79 | if err != nil { 80 | return nil, fmt.Errorf("yaml.Marshal: %v", err) 81 | } 82 | textData, err := (prototext.MarshalOptions{ 83 | Indent: " ", 84 | }).Marshal(msg) 85 | if err != nil { 86 | return nil, fmt.Errorf("prototext.Marshal: %v", err) 87 | } 88 | out = append(out, js.ValueOf(map[string]interface{}{ 89 | "yaml": string(yamlData), 90 | "json": string(jsonData), 91 | "proto": string(textData), 92 | })) 93 | } 94 | return out, nil 95 | } 96 | 97 | func jsMain(this js.Value, args []js.Value) interface{} { 98 | content := args[0].String() 99 | result, err := runDemo(content) 100 | if err != nil { 101 | args[1].Call("err", err.Error()) 102 | return nil 103 | } 104 | var out []interface{} 105 | for _, item := range result { 106 | out = append(out, js.ValueOf(item)) 107 | } 108 | args[1].Call("ok", out) 109 | return nil 110 | } 111 | 112 | func main() { 113 | js.Global().Set("skycfg_main", js.FuncOf(jsMain).Value) 114 | c := make(chan struct{}, 0) 115 | <-c 116 | } 117 | -------------------------------------------------------------------------------- /debugger/frame.go: -------------------------------------------------------------------------------- 1 | package debugger 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go.starlark.net/starlark" 7 | ) 8 | 9 | func (s *debugSession) switchFrame(fn string, newDepth int) error { 10 | endFrame := s.ParentThread.CallStackDepth() - 1 11 | 12 | frameIdx := s.startFrame + newDepth 13 | if frameIdx < s.startFrame || frameIdx > endFrame { 14 | return fmt.Errorf("provided frame index out of range: %v", newDepth) 15 | } 16 | 17 | // Check for modifications 18 | s.checkForModifications(fn, "switching frames") 19 | 20 | // Set frame 21 | s.currentDepth = newDepth 22 | s.refreshGlobals() 23 | 24 | // Print message 25 | frame := s.currentFrame() 26 | fmt.Printf("breakpoint: Stopped at %v: in %s\n", frame.Position(), frame.Callable().Name()) 27 | return nil 28 | } 29 | 30 | func (s *debugSession) makeFrame() *starlark.Builtin { 31 | return starlark.NewBuiltin("frame", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 32 | idxArg := starlark.MakeInt(1) 33 | err := starlark.UnpackArgs(fn.Name(), args, kwargs, "idx", &idxArg) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | // Check range 39 | idx, ok := idxArg.Uint64() 40 | if !ok || int(idx) < 0 || uint64(int(idx)) != idx { 41 | return nil, fmt.Errorf("frame: provided index out of range: %v", idxArg) 42 | } 43 | 44 | err = s.switchFrame(fn.Name(), int(idx)) 45 | if err != nil { 46 | return nil, fmt.Errorf("frame: %w", err) 47 | } 48 | 49 | return starlark.None, nil 50 | }) 51 | } 52 | 53 | func (s *debugSession) makeUp() *starlark.Builtin { 54 | return starlark.NewBuiltin("up", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 55 | offsetArg := starlark.MakeInt(1) 56 | err := starlark.UnpackArgs(fn.Name(), args, kwargs, "idx?", &offsetArg) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // Check range 62 | offset, ok := offsetArg.Uint64() 63 | if !ok || int(offset) < 0 || uint64(int(offset)) != offset { 64 | return nil, fmt.Errorf("up: provided offset out of range: %v", offsetArg) 65 | } 66 | newDepth := s.currentDepth + int(offset) 67 | if newDepth < 0 { 68 | return nil, fmt.Errorf("down: provided frame index out of range: %d", newDepth) 69 | } 70 | 71 | err = s.switchFrame(fn.Name(), newDepth) 72 | if err != nil { 73 | return nil, fmt.Errorf("up: %w", err) 74 | } 75 | 76 | return starlark.None, nil 77 | }) 78 | } 79 | 80 | func (s *debugSession) makeDown() *starlark.Builtin { 81 | return starlark.NewBuiltin("down", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 82 | offsetArg := starlark.MakeInt(1) 83 | err := starlark.UnpackArgs(fn.Name(), args, kwargs, "idx?", &offsetArg) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | // Check range 89 | offset, ok := offsetArg.Uint64() 90 | if !ok || int(offset) < 0 || uint64(int(offset)) != offset { 91 | return nil, fmt.Errorf("down: provided offset out of range: %v", offsetArg) 92 | } 93 | newDepth := s.currentDepth - int(offset) 94 | if newDepth < 0 { 95 | return nil, fmt.Errorf("down: provided frame index out of range: %d", newDepth) 96 | } 97 | 98 | err = s.switchFrame(fn.Name(), newDepth) 99 | if err != nil { 100 | return nil, fmt.Errorf("down: %w", err) 101 | } 102 | 103 | return starlark.None, nil 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /go/protomodule/protomodule_package.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package protomodule 18 | 19 | import ( 20 | "fmt" 21 | "sort" 22 | 23 | "go.starlark.net/starlark" 24 | "google.golang.org/protobuf/reflect/protoreflect" 25 | "google.golang.org/protobuf/reflect/protoregistry" 26 | ) 27 | 28 | func starlarkPackageFn(registry *protoregistry.Types) starlark.Callable { 29 | return starlark.NewBuiltin("proto.package", func( 30 | t *starlark.Thread, 31 | fn *starlark.Builtin, 32 | args starlark.Tuple, 33 | kwargs []starlark.Tuple, 34 | ) (starlark.Value, error) { 35 | var rawPackageName string 36 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &rawPackageName); err != nil { 37 | return nil, err 38 | } 39 | packageName := protoreflect.FullName(rawPackageName) 40 | if !packageName.IsValid() { 41 | return nil, fmt.Errorf("invalid Protobuf package name %q", packageName) 42 | } 43 | return NewProtoPackage(registry, packageName), nil 44 | }) 45 | } 46 | 47 | type protoPackage struct { 48 | name protoreflect.FullName 49 | registry *protoregistry.Types 50 | attrs starlark.StringDict 51 | } 52 | 53 | func NewProtoPackage( 54 | registry *protoregistry.Types, 55 | packageName protoreflect.FullName, 56 | ) *protoPackage { 57 | attrs := make(starlark.StringDict) 58 | 59 | registry.RangeEnums(func(t protoreflect.EnumType) bool { 60 | desc := t.Descriptor() 61 | name := desc.Name() 62 | if packageName.Append(name) == desc.FullName() { 63 | attrs[string(name)] = newEnumType(desc) 64 | } 65 | return true 66 | }) 67 | 68 | registry.RangeMessages(func(t protoreflect.MessageType) bool { 69 | desc := t.Descriptor() 70 | name := desc.Name() 71 | if packageName.Append(name) == desc.FullName() { 72 | msgType := newMessageType(registry, t.New().Interface()) 73 | attrs[string(name)] = msgType 74 | } 75 | return true 76 | }) 77 | 78 | return &protoPackage{ 79 | name: packageName, 80 | registry: registry, 81 | attrs: attrs, 82 | } 83 | } 84 | 85 | func (pkg *protoPackage) String() string { return fmt.Sprintf("", pkg.name) } 86 | func (pkg *protoPackage) Type() string { return "proto.package" } 87 | func (pkg *protoPackage) Freeze() {} 88 | func (pkg *protoPackage) Truth() starlark.Bool { return starlark.True } 89 | func (pkg *protoPackage) Hash() (uint32, error) { 90 | return 0, fmt.Errorf("unhashable type: %s", pkg.Type()) 91 | } 92 | 93 | func (pkg *protoPackage) AttrNames() []string { 94 | names := make([]string, 0, len(pkg.attrs)) 95 | for name := range pkg.attrs { 96 | names = append(names, name) 97 | } 98 | sort.Strings(names) 99 | return names 100 | } 101 | 102 | func (pkg *protoPackage) Attr(attrName string) (starlark.Value, error) { 103 | if attr, ok := pkg.attrs[attrName]; ok { 104 | return attr, nil 105 | } 106 | fullName := pkg.name.Append(protoreflect.Name(attrName)) 107 | return nil, fmt.Errorf("Protobuf type %q not found", fullName) 108 | } 109 | -------------------------------------------------------------------------------- /go/yamlmodule/json_write.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The Bazel Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 10 | // 2. Redistributions in binary form must reproduce the above copyright 11 | // notice, this list of conditions and the following disclaimer in the 12 | // documentation and/or other materials provided with the 13 | // distribution. 14 | // 15 | // 3. Neither the name of the copyright holder nor the names of its 16 | // contributors may be used to endorse or promote products derived 17 | // from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | // 31 | // SPDX-License-Identifier: BSD-3-Clause 32 | 33 | package yamlmodule 34 | 35 | import ( 36 | "bytes" 37 | "encoding/json" 38 | "fmt" 39 | 40 | "go.starlark.net/starlark" 41 | ) 42 | 43 | // Adapted from struct-specific JSON function: 44 | // https://github.com/google/starlark-go/blob/67717b5898061eb621519a94a4b89cedede9bca0/skylarkstruct/struct.go#L321 45 | func writeJSON(out *bytes.Buffer, v starlark.Value) error { 46 | if marshaler, ok := v.(json.Marshaler); ok { 47 | jsonData, err := marshaler.MarshalJSON() 48 | if err != nil { 49 | return err 50 | } 51 | out.Write(jsonData) 52 | return nil 53 | } 54 | 55 | switch v := v.(type) { 56 | case starlark.NoneType: 57 | out.WriteString("null") 58 | case starlark.Bool: 59 | fmt.Fprintf(out, "%t", v) 60 | case starlark.Int: 61 | out.WriteString(v.String()) 62 | case starlark.Float: 63 | fmt.Fprintf(out, "%g", v) 64 | case starlark.String: 65 | s := string(v) 66 | if goQuoteIsSafe(s) { 67 | fmt.Fprintf(out, "%q", s) 68 | } else { 69 | // vanishingly rare for text strings 70 | data, _ := json.Marshal(s) 71 | out.Write(data) 72 | } 73 | case starlark.Indexable: // Tuple, List 74 | out.WriteByte('[') 75 | for i, n := 0, starlark.Len(v); i < n; i++ { 76 | if i > 0 { 77 | out.WriteString(", ") 78 | } 79 | if err := writeJSON(out, v.Index(i)); err != nil { 80 | return err 81 | } 82 | } 83 | out.WriteByte(']') 84 | case *starlark.Dict: 85 | out.WriteByte('{') 86 | for i, itemPair := range v.Items() { 87 | key := itemPair[0] 88 | value := itemPair[1] 89 | if i > 0 { 90 | out.WriteString(", ") 91 | } 92 | if err := writeJSON(out, key); err != nil { 93 | return err 94 | } 95 | out.WriteString(": ") 96 | if err := writeJSON(out, value); err != nil { 97 | return err 98 | } 99 | } 100 | out.WriteByte('}') 101 | default: 102 | return fmt.Errorf("TypeError: value %s (type `%s') can't be converted to JSON.", v.String(), v.Type()) 103 | } 104 | return nil 105 | } 106 | 107 | func goQuoteIsSafe(s string) bool { 108 | for _, r := range s { 109 | // JSON doesn't like Go's \xHH escapes for ASCII control codes, 110 | // nor its \UHHHHHHHH escapes for runes >16 bits. 111 | if r < 0x20 || r >= 0x10000 { 112 | return false 113 | } 114 | } 115 | return true 116 | } 117 | -------------------------------------------------------------------------------- /docs/protobuf.asciidoc: -------------------------------------------------------------------------------- 1 | = Protobuf 2 | 3 | Skycfg supports the construction, modification, and encoding of 4 | https://en.wikipedia.org/wiki/Protocol_Buffers[Protobuf] messages. The 5 | user-facing API for interacting with Protobuf consists of Starlark types and 6 | values that mirror a set of Protobuf schemas. 7 | 8 | See link:modules.asciidoc[/docs/modules] for more details on the Skycfg API 9 | in general. 10 | 11 | == `Package` 12 | 13 | A `Package` provides access to registered message descriptors with a matching 14 | Protobuf package name. 15 | 16 | >>> pb = proto.package("google.protobuf") 17 | >>> pb 18 | 19 | >>> 20 | 21 | Top-level messages and enums are available as attributes of the `Package`. 22 | 23 | >>> dir(pb)[:5] 24 | ["Any", "BoolValue", "BytesValue", "DescriptorProto", "DoubleValue"] 25 | >>> pb.Any 26 | 27 | >>> 28 | 29 | == `MessageType` 30 | 31 | A `MessageType` can be used to construct instances of `<>`, and can 32 | also be passed to functions such as `proto.decode_json`. 33 | 34 | >>> pb = proto.package("google.protobuf") 35 | >>> pb.FileOptions 36 | 37 | >>> 38 | 39 | Nested messages and enums are available as attributes of the `MessageType` 40 | 41 | >>> pb.FileOptions.OptimizeMode 42 | 43 | >>> 44 | 45 | == `Message` 46 | 47 | A `Message` is a single instance of a Protobuf message type. It provides access 48 | to and mediates mutation of the Protobuf data structures. 49 | 50 | >>> pb = proto.package("google.protobuf") 51 | >>> msg = pb.FileOptions() 52 | >>> msg 53 | 54 | >>> dir(msg)[:5] 55 | ["cc_enable_arenas", "cc_generic_services", "csharp_namespace", "deprecated", "go_package"] 56 | 57 | Fields are available as attributes of the `Message`, with unset fields being 58 | a zero value or `None` depending on the field type and Protobuf dialect. 59 | 60 | >>> pb.FileOptions().java_package 61 | >>> type(pb.FileOptions().java_package) 62 | "NoneType" 63 | >>> pb.StringValue().value 64 | "" 65 | >>> 66 | 67 | Fields are strongly typed, and will reject assignments from values of a 68 | different type. 69 | 70 | If a repeated field is assigned to, it will make a shallow copy rather than a 71 | reference. Further modification of the value that was assigned has no effect. 72 | 73 | >>> msg = pb.FileDescriptorProto() 74 | >>> deps = ["a", "b"] 75 | >>> msg.dependency = deps 76 | >>> deps.append("c") 77 | >>> msg 78 | 79 | 80 | It is possible to modify repeated fields in-place via methods on the field 81 | itself. 82 | 83 | >>> msg.dependency.append("d") 84 | >>> msg 85 | 86 | 87 | == `EnumType` 88 | 89 | A Protobuf enum type provides access to its values. 90 | 91 | >>> pb = proto.package("google.protobuf") 92 | >>> pb.FileOptions.OptimizeMode 93 | 94 | >>> dir(pb.FileOptions.OptimizeMode) 95 | ["CODE_SIZE", "LITE_RUNTIME", "SPEED"] 96 | >>> 97 | 98 | == `EnumValue` 99 | 100 | A Protobuf enum value can be inspected to determine its name and number. 101 | 102 | >>> pb = proto.package("google.protobuf") 103 | >>> pb.FileOptions.OptimizeMode.SPEED 104 | 105 | >>> 106 | 107 | Enum fields can be assigned to by symbol or name. 108 | 109 | >>> msg = pb.FileOptions() 110 | >>> msg.optimize_for 111 | >>> msg.optimize_for = pb.FileOptions.OptimizeMode.SPEED 112 | >>> msg 113 | 114 | >>> msg.optimize_for = 'CODE_SIZE' 115 | >>> msg 116 | 117 | >>> 118 | 119 | WARNING: Protobuf enums are allowed to have multiple names assigned to the same 120 | number. In this case, it is unspecified which name Skycfg will report for enum 121 | fields set to such a number. 122 | -------------------------------------------------------------------------------- /_examples/wasm/index.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 70 | 71 | 80 | 81 | 82 |
83 | 98 |

 99 | 		
100 |
101 |
102 | 103 | 108 |
109 |

110 | 		
111 | 112 | 163 | 164 | -------------------------------------------------------------------------------- /go/protomodule/protomodule_enum.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package protomodule 18 | 19 | import ( 20 | "fmt" 21 | "sort" 22 | 23 | "go.starlark.net/starlark" 24 | "go.starlark.net/syntax" 25 | "google.golang.org/protobuf/reflect/protoreflect" 26 | ) 27 | 28 | func newEnumType(descriptor protoreflect.EnumDescriptor) *protoEnumType { 29 | values := descriptor.Values() 30 | attrs := make(map[string]*protoEnumValue, values.Len()) 31 | for ii := 0; ii < values.Len(); ii++ { 32 | value := values.Get(ii) 33 | attrs[string(value.Name())] = &protoEnumValue{ 34 | typeName: descriptor.FullName(), 35 | value: value, 36 | } 37 | } 38 | 39 | return &protoEnumType{ 40 | name: descriptor.FullName(), 41 | attrs: attrs, 42 | } 43 | } 44 | 45 | type protoEnumType struct { 46 | name protoreflect.FullName 47 | attrs map[string]*protoEnumValue 48 | } 49 | 50 | var _ starlark.HasAttrs = (*protoEnumType)(nil) 51 | var _ starlark.Value = (*protoEnumType)(nil) 52 | 53 | func (t *protoEnumType) String() string { 54 | return fmt.Sprintf("", t.name) 55 | } 56 | func (t *protoEnumType) Type() string { return "proto.EnumType" } 57 | func (t *protoEnumType) Freeze() {} 58 | func (t *protoEnumType) Truth() starlark.Bool { return starlark.True } 59 | func (t *protoEnumType) Hash() (uint32, error) { 60 | return 0, fmt.Errorf("unhashable type: %s", t.Type()) 61 | } 62 | 63 | func (t *protoEnumType) Attr(attrName string) (starlark.Value, error) { 64 | if attr, ok := t.attrs[attrName]; ok { 65 | return attr, nil 66 | } 67 | // TODO: better error message 68 | return nil, nil 69 | } 70 | 71 | func (t *protoEnumType) AttrNames() []string { 72 | names := make([]string, 0, len(t.attrs)) 73 | for name := range t.attrs { 74 | names = append(names, name) 75 | } 76 | sort.Strings(names) 77 | return names 78 | } 79 | 80 | func (t *protoEnumType) ByNum(enumNumber protoreflect.EnumNumber) (starlark.Value, error) { 81 | for _, enum := range t.attrs { 82 | if enum.enumNumber() == enumNumber { 83 | return enum, nil 84 | } 85 | } 86 | return nil, fmt.Errorf("ValueError: enum %d out of bounds for %s", enumNumber, string(t.name)) 87 | } 88 | 89 | type protoEnumValue struct { 90 | typeName protoreflect.FullName 91 | value protoreflect.EnumValueDescriptor 92 | } 93 | 94 | var _ starlark.Comparable = (*protoEnumValue)(nil) 95 | var _ starlark.HasAttrs = (*protoEnumValue)(nil) 96 | var _ starlark.Value = (*protoEnumValue)(nil) 97 | 98 | func (v *protoEnumValue) String() string { 99 | return fmt.Sprintf("<%s %s=%d>", v.typeName, v.value.Name(), v.value.Number()) 100 | } 101 | func (v *protoEnumValue) Type() string { return string(v.typeName) } 102 | func (v *protoEnumValue) Freeze() {} 103 | func (v *protoEnumValue) Truth() starlark.Bool { return starlark.True } 104 | func (v *protoEnumValue) Hash() (uint32, error) { 105 | return starlark.MakeInt64(int64(v.value.Number())).Hash() 106 | } 107 | 108 | func (v *protoEnumValue) Attr(attrName string) (starlark.Value, error) { 109 | switch attrName { 110 | case "name": 111 | return starlark.String(v.value.Name()), nil 112 | case "value": 113 | return starlark.MakeInt64(int64(v.value.Number())), nil 114 | default: 115 | return nil, fmt.Errorf("unknown attribute %s on %s", attrName, v.String()) 116 | } 117 | } 118 | 119 | func (v *protoEnumValue) AttrNames() []string { 120 | return []string{"name", "value"} 121 | } 122 | 123 | func (v *protoEnumValue) enumNumber() protoreflect.EnumNumber { 124 | return v.value.Number() 125 | } 126 | 127 | func (v *protoEnumValue) CompareSameType(op syntax.Token, y starlark.Value, depth int) (bool, error) { 128 | other := y.(*protoEnumValue) 129 | switch op { 130 | case syntax.EQL: 131 | return v.value == other.value, nil 132 | case syntax.NEQ: 133 | return v.value != other.value, nil 134 | default: 135 | return false, fmt.Errorf("enums support only `==' and `!=' comparisons, got: %#v", op) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /debugger/vars.go: -------------------------------------------------------------------------------- 1 | package debugger 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go.starlark.net/starlark" 7 | ) 8 | 9 | func (s *debugSession) makeGlobals() *starlark.Builtin { 10 | return starlark.NewBuiltin("globals", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 11 | // We have no args to unpack. Make sure no one tried to set any. 12 | err := starlark.UnpackArgs(fn.Name(), args, kwargs) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | frame := s.currentFrame() 18 | starFunc, ok := frame.Callable().(*starlark.Function) 19 | if !ok { 20 | fmt.Printf("globals: Warning: No globals visible in non-Starlark function (%s) %v\n", frame.Callable().Type(), frame.Callable()) 21 | return new(starlark.Dict), nil 22 | } 23 | 24 | globals := starFunc.Globals() 25 | globalsDict := starlark.NewDict(len(starFunc.Globals())) 26 | for name, val := range globals { 27 | if val != nil { 28 | globalsDict.SetKey(starlark.String(name), val) 29 | } 30 | } 31 | return globalsDict, nil 32 | }) 33 | } 34 | 35 | func (s *debugSession) makeLocals() *starlark.Builtin { 36 | return starlark.NewBuiltin("locals", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 37 | // We have no args to unpack. Make sure no one tried to set any. 38 | err := starlark.UnpackArgs(fn.Name(), args, kwargs) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | frame := s.currentFrame() 44 | if _, ok := frame.Callable().(*starlark.Function); !ok { 45 | fmt.Printf("locals: Warning: No locals visible in non-Starlark function (%s) %v\n", frame.Callable().Type(), frame.Callable()) 46 | return new(starlark.Dict), nil 47 | } 48 | 49 | localsDict := starlark.NewDict(frame.NumLocals()) 50 | for i := 0; i < frame.NumLocals(); i++ { 51 | binding, val := frame.Local(i) 52 | if val != nil { 53 | localsDict.SetKey(starlark.String(binding.Name), val) 54 | } 55 | } 56 | return localsDict, nil 57 | }) 58 | } 59 | 60 | func (s *debugSession) makeVars() *starlark.Builtin { 61 | return starlark.NewBuiltin("vars", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 62 | // We have no args to unpack. Make sure no one tried to set any. 63 | err := starlark.UnpackArgs(fn.Name(), args, kwargs) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | frame := s.currentFrame() 69 | starFunc, ok := frame.Callable().(*starlark.Function) 70 | if !ok { 71 | fmt.Printf("No variables visible in non-Starlark function (%s) %v\n", frame.Callable().Type(), frame.Callable()) 72 | return starlark.None, nil 73 | } 74 | 75 | fmt.Printf("All predeclared symbols in frame %d (%s):\n", s.currentDepth, starFunc.Name()) 76 | for name, val := range starFunc.Module().Predeclared() { 77 | if val == nil { 78 | fmt.Printf(" %s = \n", name) 79 | } else { 80 | fmt.Printf(" %s: %s = %s\n", name, val.Type(), shortString(val)) 81 | } 82 | } 83 | fmt.Println() 84 | 85 | fmt.Printf("All globals in frame %d (%s):\n", s.currentDepth, starFunc.Name()) 86 | for name, val := range starFunc.Globals() { 87 | if val == nil { 88 | fmt.Printf(" %s = \n", name) 89 | } else { 90 | fmt.Printf(" %s: %s = %s\n", name, val.Type(), shortString(val)) 91 | } 92 | } 93 | fmt.Println() 94 | 95 | fmt.Printf("All free vars in frame %d (%s):\n", s.currentDepth, starFunc.Name()) 96 | for i := 0; i < starFunc.NumFreeVars(); i++ { 97 | binding, val := starFunc.FreeVar(i) 98 | if val == nil { 99 | fmt.Printf(" %s (%v) = \n", binding.Name, binding.Pos) 100 | } else { 101 | fmt.Printf(" %s (%v): %s = %s\n", binding.Name, binding.Pos, val.Type(), shortString(val)) 102 | } 103 | } 104 | fmt.Println() 105 | 106 | fmt.Printf("All locals in frame %d (%s):\n", s.currentDepth, starFunc.Name()) 107 | for i := 0; i < frame.NumLocals(); i++ { 108 | binding, val := frame.Local(i) 109 | if val == nil { 110 | fmt.Printf(" %s (%v) = \n", binding.Name, binding.Pos) 111 | } else { 112 | fmt.Printf(" %s (%v): %s = %s\n", binding.Name, binding.Pos, val.Type(), shortString(val)) 113 | } 114 | } 115 | 116 | return starlark.None, nil 117 | }) 118 | } 119 | 120 | func shortString(val starlark.Value) string { 121 | if val == nil { 122 | return "" 123 | } 124 | s := val.String() 125 | if len(s) < 70 { 126 | return s 127 | } 128 | return s[:38] + "…" + s[len(s)-29:] 129 | } 130 | -------------------------------------------------------------------------------- /go/yamlmodule/yamlmodule.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package yamlmodule defines a Starlark module of YAML-related functions. 18 | package yamlmodule 19 | 20 | import ( 21 | "bytes" 22 | "fmt" 23 | "reflect" 24 | 25 | starlarkjson "go.starlark.net/lib/json" 26 | "go.starlark.net/starlark" 27 | "go.starlark.net/starlarkstruct" 28 | yaml "gopkg.in/yaml.v2" 29 | ) 30 | 31 | // NewModule returns a Starlark module of YAML-related functions. 32 | // 33 | // yaml = module( 34 | // decode, 35 | // encode, 36 | // ) 37 | // 38 | // See `docs/modules.asciidoc` for details on the API of each function. 39 | func NewModule() *starlarkstruct.Module { 40 | return &starlarkstruct.Module{ 41 | Name: "yaml", 42 | Members: starlark.StringDict{ 43 | "decode": starlark.NewBuiltin("yaml.decode", yamlDecode), 44 | "encode": starlark.NewBuiltin("yaml.encode", yamlEncode), 45 | }, 46 | } 47 | } 48 | 49 | func yamlDecode(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 50 | var blob string 51 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &blob); err != nil { 52 | return nil, err 53 | } 54 | var inflated interface{} 55 | if err := yaml.Unmarshal([]byte(blob), &inflated); err != nil { 56 | return nil, err 57 | } 58 | return toStarlarkValue(inflated) 59 | } 60 | 61 | var jsonEncode = starlarkjson.Module.Members["encode"] 62 | 63 | func yamlEncode(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 64 | var v starlark.Value 65 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &v); err != nil { 66 | return nil, err 67 | } 68 | 69 | var buf bytes.Buffer 70 | if err := writeJSON(&buf, v); err != nil { 71 | return nil, err 72 | } 73 | var jsonObj interface{} 74 | if err := yaml.Unmarshal(buf.Bytes(), &jsonObj); err != nil { 75 | return nil, err 76 | } 77 | yamlBytes, err := yaml.Marshal(jsonObj) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return starlark.String(yamlBytes), nil 82 | } 83 | 84 | // toStarlarkScalarValue converts a scalar [obj] value to its starlark Value 85 | func toStarlarkScalarValue(obj interface{}) (starlark.Value, bool) { 86 | if obj == nil { 87 | return starlark.None, true 88 | } 89 | rt := reflect.TypeOf(obj) 90 | v := reflect.ValueOf(obj) 91 | switch rt.Kind() { 92 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 93 | return starlark.MakeInt64(v.Int()), true 94 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 95 | return starlark.MakeUint64(v.Uint()), true 96 | case reflect.Bool: 97 | return starlark.Bool(v.Bool()), true 98 | case reflect.Float32, reflect.Float64: 99 | return starlark.Float(v.Float()), true 100 | case reflect.String: 101 | return starlark.String(v.String()), true 102 | default: 103 | return nil, false 104 | } 105 | } 106 | 107 | // toStarlarkValue is a DFS walk to translate the DAG from go to starlark 108 | func toStarlarkValue(obj interface{}) (starlark.Value, error) { 109 | if objval, ok := toStarlarkScalarValue(obj); ok { 110 | return objval, nil 111 | } 112 | rt := reflect.TypeOf(obj) 113 | switch rt.Kind() { 114 | case reflect.Map: 115 | ret := &starlark.Dict{} 116 | for k, v := range obj.(map[interface{}]interface{}) { 117 | keyval, ok := toStarlarkScalarValue(k) 118 | if !ok { 119 | return nil, fmt.Errorf("%s (%v) is not a supported key type", rt.Kind(), obj) 120 | } 121 | starval, err := toStarlarkValue(v) 122 | if err != nil { 123 | return nil, err 124 | } 125 | if err = ret.SetKey(keyval, starval); err != nil { 126 | return nil, err 127 | } 128 | } 129 | return ret, nil 130 | case reflect.Slice: 131 | slice := obj.([]interface{}) 132 | starvals := make([]starlark.Value, len(slice)) 133 | for i, element := range slice { 134 | v, err := toStarlarkValue(element) 135 | if err != nil { 136 | return nil, err 137 | } 138 | starvals[i] = v 139 | } 140 | return starlark.NewList(starvals), nil 141 | default: 142 | return nil, fmt.Errorf("%s (%v) is not a supported type", rt.Kind(), obj) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /_examples/repl/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package main 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "flag" 23 | "fmt" 24 | "os" 25 | 26 | "github.com/gogo/protobuf/jsonpb" 27 | "go.starlark.net/repl" 28 | "go.starlark.net/starlark" 29 | yaml "gopkg.in/yaml.v2" 30 | 31 | _ "github.com/envoyproxy/go-control-plane/envoy/api/v2" 32 | _ "k8s.io/api/apps/v1" 33 | _ "k8s.io/api/batch/v1" 34 | _ "k8s.io/api/core/v1" 35 | _ "k8s.io/api/storage/v1" 36 | 37 | "github.com/stripe/skycfg" 38 | "github.com/stripe/skycfg/gogocompat" 39 | ) 40 | 41 | func main() { 42 | flag.Parse() 43 | argv := flag.Args() 44 | var mode, filename string 45 | switch len(argv) { 46 | case 1: 47 | mode = "repl" 48 | filename = argv[0] 49 | case 2: 50 | mode = argv[0] 51 | filename = argv[1] 52 | default: 53 | fmt.Fprintf(os.Stderr, `Demo REPL for Skycfg, a library for building complex typed configs. 54 | 55 | usage: %s FILENAME 56 | %s [ repl | json | yaml ] FILENAME 57 | `, os.Args[0], os.Args[0]) 58 | os.Exit(1) 59 | } 60 | 61 | switch mode { 62 | case "repl", "json", "yaml": 63 | default: 64 | fmt.Fprintf(os.Stderr, "Unknown subcommand %q: expected `repl', `json', or `yaml'\n") 65 | os.Exit(1) 66 | } 67 | 68 | config, err := skycfg.Load(context.Background(), filename, skycfg.WithProtoRegistry(gogocompat.ProtoRegistry())) 69 | if err != nil { 70 | fmt.Fprintf(os.Stderr, "error loading %q: %v\n", filename, err) 71 | os.Exit(1) 72 | } 73 | 74 | if mode == "repl" { 75 | fmt.Printf("Entering Skycfg REPL for %s\n", config.Filename()) 76 | thread := &starlark.Thread{} 77 | globals := make(starlark.StringDict) 78 | globals["help"] = &replHelp{config} 79 | globals["exit"] = &replExit{} 80 | for key, value := range config.Globals() { 81 | globals[key] = value 82 | } 83 | for key, value := range config.Locals() { 84 | globals[key] = value 85 | } 86 | repl.REPL(thread, globals) 87 | return 88 | } 89 | 90 | var jsonMarshaler = &jsonpb.Marshaler{OrigName: true} 91 | protos, err := config.Main(context.Background()) 92 | if err != nil { 93 | fmt.Fprintf(os.Stderr, "error evaluating %q: %v\n", config.Filename(), err) 94 | os.Exit(1) 95 | } 96 | for _, msg := range protos { 97 | marshaled, err := jsonMarshaler.MarshalToString(msg) 98 | sep := "" 99 | if err != nil { 100 | fmt.Fprintf(os.Stderr, "json.Marshal: %v\n", err) 101 | continue 102 | } 103 | if mode == "yaml" { 104 | var yamlMap yaml.MapSlice 105 | if err := yaml.Unmarshal([]byte(marshaled), &yamlMap); err != nil { 106 | panic(fmt.Sprintf("yaml.Unmarshal: %v", err)) 107 | } 108 | yamlMarshaled, err := yaml.Marshal(yamlMap) 109 | if err != nil { 110 | panic(fmt.Sprintf("yaml.Marshal: %v", err)) 111 | } 112 | marshaled = string(yamlMarshaled) 113 | sep = "---\n" 114 | } 115 | fmt.Printf("%s%s\n", sep, marshaled) 116 | } 117 | } 118 | 119 | type replHelp struct { 120 | config *skycfg.Config 121 | } 122 | 123 | func (*replHelp) Type() string { return "skycfg_repl_help" } 124 | func (*replHelp) Freeze() {} 125 | func (*replHelp) Truth() starlark.Bool { return starlark.True } 126 | 127 | func (help *replHelp) Hash() (uint32, error) { 128 | return 0, fmt.Errorf("unhashable type: %s", help.Type()) 129 | } 130 | 131 | func (help *replHelp) String() string { 132 | var buf bytes.Buffer 133 | buf.WriteString(`Skycfg, a library for building complex typed configs. 134 | 135 | Homepage: https://github.com/stripe/skycfg 136 | API docs: https://godoc.org/github.com/stripe/skycfg 137 | 138 | Pre-defined values: 139 | `) 140 | for name := range help.config.Globals() { 141 | fmt.Fprintf(&buf, "* %s\n", name) 142 | } 143 | 144 | fmt.Fprintf(&buf, "\nConfig values (from %s):\n", help.config.Filename()) 145 | for name := range help.config.Locals() { 146 | fmt.Fprintf(&buf, "* %s\n", name) 147 | } 148 | return buf.String() 149 | } 150 | 151 | type replExit struct{} 152 | 153 | func (*replExit) Type() string { return "skycfg_repl_exit" } 154 | func (*replExit) Freeze() {} 155 | func (*replExit) Truth() starlark.Bool { return starlark.True } 156 | func (*replExit) String() string { return "Use exit() or Ctrl-D (i.e. EOF) to exit" } 157 | func (*replExit) Name() string { return "exit" } 158 | 159 | func (exit *replExit) Hash() (uint32, error) { 160 | return 0, fmt.Errorf("unhashable type: %s", exit.Type()) 161 | } 162 | 163 | func (*replExit) Call(_ *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 164 | if err := starlark.UnpackPositionalArgs("exit", args, kwargs, 0); err != nil { 165 | return nil, err 166 | } 167 | fmt.Printf("\n") 168 | os.Exit(0) 169 | return starlark.None, nil 170 | } 171 | -------------------------------------------------------------------------------- /go/protomodule/protomodule_message_type.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package protomodule 18 | 19 | import ( 20 | "fmt" 21 | "sort" 22 | 23 | "go.starlark.net/starlark" 24 | "google.golang.org/protobuf/proto" 25 | "google.golang.org/protobuf/reflect/protoreflect" 26 | "google.golang.org/protobuf/reflect/protoregistry" 27 | "google.golang.org/protobuf/types/dynamicpb" 28 | ) 29 | 30 | // newMessageType creates a Starlark value representing a named Protobuf message 31 | // type that can be used for constructing new concrete protobuf objects. 32 | func newMessageType(registry *protoregistry.Types, msg protoreflect.ProtoMessage) starlark.Callable { 33 | attrs := make(starlark.StringDict) 34 | 35 | descriptor := msg.ProtoReflect().Type().Descriptor() 36 | 37 | // Register child messages, enums as attrs 38 | for ii := 0; ii < descriptor.Enums().Len(); ii++ { 39 | child := descriptor.Enums().Get(ii) 40 | attrs[string(child.Name())] = newEnumType(child) 41 | } 42 | for ii := 0; ii < descriptor.Messages().Len(); ii++ { 43 | child := descriptor.Messages().Get(ii) 44 | if !child.IsMapEntry() { 45 | childMsg, err := registry.FindMessageByName(child.FullName()) 46 | if err != nil { 47 | // Fallback to dynamicpb if nested message is 48 | // unavailable in registry. This points to the 49 | // registry having been incompletely 50 | // constructed, missing nested types 51 | childMsg = dynamicpb.NewMessageType(child) 52 | } 53 | 54 | attrs[string(child.Name())] = newMessageType(registry, childMsg.New().Interface()) 55 | } 56 | } 57 | 58 | emptyMsg := proto.Clone(msg) 59 | proto.Reset(emptyMsg) 60 | 61 | return &protoMessageType{ 62 | descriptor: descriptor, 63 | attrs: attrs, 64 | emptyMsg: emptyMsg, 65 | } 66 | } 67 | 68 | type protoMessageType struct { 69 | descriptor protoreflect.MessageDescriptor 70 | attrs starlark.StringDict 71 | 72 | // An empty protobuf message of the appropriate type. 73 | emptyMsg protoreflect.ProtoMessage 74 | } 75 | 76 | var _ starlark.HasAttrs = (*protoMessageType)(nil) 77 | var _ starlark.Callable = (*protoMessageType)(nil) 78 | var _ skyProtoMessageType = (*protoMessageType)(nil) 79 | 80 | func (t *protoMessageType) String() string { 81 | return fmt.Sprintf("", t.Name()) 82 | } 83 | func (t *protoMessageType) Type() string { return "proto.MessageType" } 84 | func (t *protoMessageType) Freeze() {} 85 | func (t *protoMessageType) Truth() starlark.Bool { return starlark.True } 86 | func (t *protoMessageType) Hash() (uint32, error) { 87 | return 0, fmt.Errorf("unhashable type: %s", t.Type()) 88 | } 89 | 90 | func (t *protoMessageType) Name() string { 91 | return string(t.descriptor.FullName()) 92 | } 93 | 94 | func (t *protoMessageType) AttrNames() []string { 95 | names := make([]string, 0, len(t.attrs)) 96 | for name := range t.attrs { 97 | names = append(names, name) 98 | } 99 | sort.Strings(names) 100 | return names 101 | } 102 | 103 | func (t *protoMessageType) Attr(attrName string) (starlark.Value, error) { 104 | if attr, ok := t.attrs[attrName]; ok { 105 | return attr, nil 106 | } 107 | fullName := t.descriptor.FullName().Append(protoreflect.Name(attrName)) 108 | return nil, fmt.Errorf("Protobuf type %q not found", fullName) 109 | } 110 | 111 | func (t *protoMessageType) CallInternal( 112 | thread *starlark.Thread, 113 | args starlark.Tuple, 114 | kwargs []starlark.Tuple, 115 | ) (starlark.Value, error) { 116 | // This is semantically the constructor of a protobuf message, and we 117 | // want it to accept only kwargs (where keys are protobuf field names). 118 | // Inject a useful error message if a user tries to pass positional args. 119 | if err := starlark.UnpackPositionalArgs(t.Name(), args, nil, 0); err != nil { 120 | return nil, err 121 | } 122 | 123 | // Parse the kwarg set into a map[string]starlark.Value, containing one 124 | // entry for each provided kwarg. Keys are the original protobuf field names. 125 | // This lets the starlark kwarg parser handle most of the error reporting, 126 | // except type errors which are deferred until later. 127 | var parserPairs []interface{} 128 | parsedKwargs := make(map[string]*starlark.Value, len(kwargs)) 129 | 130 | fields := t.descriptor.Fields() 131 | for ii := 0; ii < fields.Len(); ii++ { 132 | fieldName := string(fields.Get(ii).Name()) 133 | attrName := fieldNameToAttrName(fieldName) 134 | v := new(starlark.Value) 135 | parsedKwargs[attrName] = v 136 | parserPairs = append(parserPairs, attrName+"?", v) 137 | } 138 | 139 | if err := starlark.UnpackArgs(t.Name(), nil, kwargs, parserPairs...); err != nil { 140 | return nil, err 141 | } 142 | 143 | // Instantiate a new message and populate the fields 144 | out, err := NewMessage(t.emptyMsg) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | // Make sure to iterate over the user-provided kwargs in order. 150 | // Ordering matters for oneof branches, since setting one resets all others. 151 | // This order is what Python protobuf library uses. 152 | // See https://github.com/stripe/skycfg/issues/103 153 | for _, kwarg := range kwargs { 154 | attrName := kwarg[0].(starlark.String).GoString() 155 | starlarkValue := parsedKwargs[attrName] 156 | if *starlarkValue == nil { 157 | continue 158 | } 159 | 160 | fieldName := attrNameToFieldName(attrName) 161 | 162 | if err := out.SetField(fieldName, *starlarkValue); err != nil { 163 | return nil, err 164 | } 165 | } 166 | 167 | return out, nil 168 | } 169 | 170 | func (t *protoMessageType) NewMessage() protoreflect.ProtoMessage { 171 | return proto.Clone(t.emptyMsg) 172 | } 173 | -------------------------------------------------------------------------------- /go/yamlmodule/yamlmodule_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package yamlmodule 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | 23 | "go.starlark.net/starlark" 24 | ) 25 | 26 | type YamlTestCase struct { 27 | skyExpr string 28 | expOutput string 29 | } 30 | 31 | func TestSkyToYaml(t *testing.T) { 32 | thread := new(starlark.Thread) 33 | env := starlark.StringDict{ 34 | "yaml": NewModule(), 35 | } 36 | 37 | testCases := []YamlTestCase{ 38 | YamlTestCase{ 39 | skyExpr: "123", 40 | expOutput: `123 41 | `, 42 | }, 43 | YamlTestCase{ 44 | skyExpr: `{"a": 5, 13: 2, "k": {"k2": "v"}}`, 45 | expOutput: `13: 2 46 | a: 5 47 | k: 48 | k2: v 49 | `, 50 | }, 51 | YamlTestCase{ 52 | skyExpr: `[1, 2, 3, "abc", None, 15, True, False, {"k": "v"}]`, 53 | expOutput: `- 1 54 | - 2 55 | - 3 56 | - abc 57 | - null 58 | - 15 59 | - true 60 | - false 61 | - k: v 62 | `, 63 | }, 64 | } 65 | 66 | for _, testCase := range testCases { 67 | v, err := starlark.Eval( 68 | thread, 69 | "", 70 | fmt.Sprintf("yaml.encode(%s)", testCase.skyExpr), 71 | env, 72 | ) 73 | if err != nil { 74 | t.Error("Error from eval", "\nExpected nil", "\nGot", err) 75 | } 76 | exp := starlark.String(testCase.expOutput) 77 | if v != exp { 78 | t.Error( 79 | "Bad return value from yaml.encode", 80 | "\nExpected", 81 | exp, 82 | "\nGot", 83 | v, 84 | ) 85 | } 86 | } 87 | } 88 | 89 | func TestYamlToSky(t *testing.T) { 90 | thread := new(starlark.Thread) 91 | env := starlark.StringDict{ 92 | "yaml": NewModule(), 93 | } 94 | 95 | skyExpr := `{ 96 | "strKey": "val", 97 | "arrKey": ["a", "b"], 98 | "mapKey": {"subkey": "val"}, 99 | "intKey": 2147483647, 100 | "int64Key": 2147483648, 101 | "nIntKey": -2147483648, 102 | "nInt64Key": -2147483649, 103 | "uintKey": 9223372036854775808, 104 | "overflowUintKey": 18446744073709551616, 105 | "floatKey": 1.234, 106 | "boolKey": False, 107 | "nullKey": None, 108 | 2147483647: "intKey", 109 | 2147483648: "int64Key", 110 | -2147483648: "nIntKey", 111 | -2147483649: "nInt64Key", 112 | 9223372036854775808: "uintKey", 113 | 1.234: "floatKey", 114 | False: "boolKey", 115 | None: "nullKey", 116 | }` 117 | 118 | v, err := starlark.Eval( 119 | thread, 120 | "", 121 | fmt.Sprintf(`yaml.decode(yaml.encode(%s))`, skyExpr), 122 | env, 123 | ) 124 | if err != nil { 125 | t.Error("Error from eval", "\nExpected nil", "\nGot", err) 126 | } 127 | staryaml := v.(starlark.Mapping) 128 | for _, testCase := range []struct { 129 | name string 130 | key starlark.Value 131 | want string 132 | expectedErr error 133 | }{ 134 | { 135 | name: "key mapped to String", 136 | key: starlark.String("strKey"), 137 | want: `"val"`, 138 | }, 139 | { 140 | name: "key mapped to Array", 141 | key: starlark.String("arrKey"), 142 | want: `["a", "b"]`, 143 | }, 144 | { 145 | name: "key mapped to Map", 146 | key: starlark.String("mapKey"), 147 | want: `{"subkey": "val"}`, 148 | }, 149 | { 150 | name: "key mapped to Uint", 151 | key: starlark.String("uintKey"), 152 | want: `9223372036854775808`, 153 | }, 154 | { 155 | name: "key mapped to negative Int64", 156 | key: starlark.String("nInt64Key"), 157 | want: `-2147483649`, 158 | }, 159 | { 160 | name: "key mapped to Int", 161 | key: starlark.String("intKey"), 162 | want: `2147483647`, 163 | }, 164 | { 165 | name: "key mapped to Int64", 166 | key: starlark.String("int64Key"), 167 | want: `2147483648`, 168 | }, 169 | { 170 | name: "key mapped to Float", 171 | key: starlark.String("floatKey"), 172 | want: `1.234`, 173 | }, 174 | { 175 | name: "key mapped to Overflow Uint64", 176 | key: starlark.String("overflowUintKey"), 177 | want: `1.8446744073709552e+19`, 178 | }, 179 | { 180 | name: "key mapped to Bool", 181 | key: starlark.String("boolKey"), 182 | want: `False`, 183 | }, 184 | { 185 | name: "key mapped to Null", 186 | key: starlark.String("nullKey"), 187 | want: `None`, 188 | }, 189 | { 190 | name: "int key mapped to String", 191 | key: starlark.MakeInt(2147483647), 192 | want: `"intKey"`, 193 | }, 194 | { 195 | name: "Int64 key mapped to String", 196 | key: starlark.MakeInt64(2147483648), 197 | want: `"int64Key"`, 198 | }, 199 | { 200 | name: "negative Int64 key mapped to String", 201 | key: starlark.MakeInt64(-2147483649), 202 | want: `"nInt64Key"`, 203 | }, 204 | { 205 | name: "Uint key mapped to String", 206 | key: starlark.MakeUint(9223372036854775808), 207 | want: `"uintKey"`, 208 | }, 209 | { 210 | name: "Float key mapped to String", 211 | key: starlark.Float(1.234), 212 | want: `"floatKey"`, 213 | }, 214 | { 215 | name: "Bool key mapped to String", 216 | key: starlark.Bool(false), 217 | want: `"boolKey"`, 218 | }, 219 | { 220 | name: "Null key mapped to String", 221 | key: starlark.None, 222 | want: `"nullKey"`, 223 | }, 224 | } { 225 | t.Run(testCase.name, func(t *testing.T) { 226 | got, _, err := staryaml.Get(testCase.key) 227 | if err != nil { 228 | t.Errorf("error accessing key [%v] in staryaml: %v", testCase.key, err) 229 | } 230 | if testCase.want != got.String() { 231 | t.Error( 232 | "Bad return value from yaml.decode", 233 | "\nExpected:", 234 | testCase.want, 235 | "\nGot:", 236 | got, 237 | ) 238 | } 239 | }) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /go/protomodule/protomodule_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package protomodule 18 | 19 | import ( 20 | "fmt" 21 | 22 | "go.starlark.net/starlark" 23 | "go.starlark.net/syntax" 24 | "google.golang.org/protobuf/reflect/protoreflect" 25 | ) 26 | 27 | var allowedListMethods = map[string]func(*protoRepeated) starlark.Value{ 28 | "clear": nil, 29 | "append": (*protoRepeated).wrapAppend, 30 | "extend": (*protoRepeated).wrapExtend, 31 | } 32 | 33 | // protoRepeated wraps an underlying starlark.List to provide typechecking on 34 | // writes 35 | // 36 | // starlark.List is heterogeneous, where protoRepeated enforces all values 37 | // conform to the given fieldDesc 38 | type protoRepeated struct { 39 | fieldDesc protoreflect.FieldDescriptor 40 | list *starlark.List 41 | } 42 | 43 | var _ starlark.Value = (*protoRepeated)(nil) 44 | var _ starlark.Iterable = (*protoRepeated)(nil) 45 | var _ starlark.Sequence = (*protoRepeated)(nil) 46 | var _ starlark.Indexable = (*protoRepeated)(nil) 47 | var _ starlark.HasAttrs = (*protoRepeated)(nil) 48 | var _ starlark.HasSetIndex = (*protoRepeated)(nil) 49 | var _ starlark.HasBinary = (*protoRepeated)(nil) 50 | var _ starlark.Comparable = (*protoRepeated)(nil) 51 | 52 | func newProtoRepeated(fieldDesc protoreflect.FieldDescriptor) *protoRepeated { 53 | return &protoRepeated{fieldDesc, starlark.NewList(nil)} 54 | } 55 | 56 | func newProtoRepeatedFromList(fieldDesc protoreflect.FieldDescriptor, l *starlark.List) (*protoRepeated, error) { 57 | out := &protoRepeated{fieldDesc, l} 58 | for i := 0; i < l.Len(); i++ { 59 | err := scalarTypeCheck(fieldDesc, l.Index(i)) 60 | if err != nil { 61 | return nil, err 62 | } 63 | } 64 | return out, nil 65 | } 66 | 67 | func (r *protoRepeated) Attr(name string) (starlark.Value, error) { 68 | wrapper, ok := allowedListMethods[name] 69 | if !ok { 70 | return nil, nil 71 | } 72 | if wrapper != nil { 73 | return wrapper(r), nil 74 | } 75 | return r.list.Attr(name) 76 | } 77 | 78 | func (r *protoRepeated) AttrNames() []string { return r.list.AttrNames() } 79 | func (r *protoRepeated) Freeze() { r.list.Freeze() } 80 | func (r *protoRepeated) Hash() (uint32, error) { return r.list.Hash() } 81 | func (r *protoRepeated) Index(i int) starlark.Value { return r.list.Index(i) } 82 | func (r *protoRepeated) Iterate() starlark.Iterator { return r.list.Iterate() } 83 | func (r *protoRepeated) Len() int { return r.list.Len() } 84 | func (r *protoRepeated) Slice(x, y, step int) starlark.Value { return r.list.Slice(x, y, step) } 85 | func (r *protoRepeated) String() string { return r.list.String() } 86 | func (r *protoRepeated) Truth() starlark.Bool { return r.list.Truth() } 87 | 88 | func (r *protoRepeated) Type() string { 89 | return fmt.Sprintf("list<%s>", typeName(r.fieldDesc)) 90 | } 91 | 92 | func (r *protoRepeated) CompareSameType(op syntax.Token, y starlark.Value, depth int) (bool, error) { 93 | other, ok := y.(*protoRepeated) 94 | if !ok { 95 | return false, nil 96 | } 97 | 98 | return starlark.CompareDepth(op, r.list, other.list, depth) 99 | } 100 | 101 | func (r *protoRepeated) Append(v starlark.Value) error { 102 | err := scalarTypeCheck(r.fieldDesc, v) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | return r.list.Append(v) 108 | } 109 | 110 | func (r *protoRepeated) appendUnchecked(v starlark.Value) error { 111 | return r.list.Append(v) 112 | } 113 | 114 | func (r *protoRepeated) SetIndex(i int, v starlark.Value) error { 115 | err := scalarTypeCheck(r.fieldDesc, v) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | return r.list.SetIndex(i, v) 121 | } 122 | 123 | func (r *protoRepeated) Extend(iterable starlark.Iterable) error { 124 | iter := iterable.Iterate() 125 | defer iter.Done() 126 | 127 | var val starlark.Value 128 | for iter.Next(&val) { 129 | err := r.Append(val) 130 | if err != nil { 131 | return err 132 | } 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func (r *protoRepeated) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) { 139 | if op == syntax.PLUS { 140 | if side == starlark.Left { 141 | switch y := y.(type) { 142 | case *starlark.List: 143 | return starlark.Binary(op, r.list, y) 144 | case *protoRepeated: 145 | return starlark.Binary(op, r.list, y.list) 146 | } 147 | return nil, nil 148 | } 149 | if side == starlark.Right { 150 | if _, ok := y.(*starlark.List); ok { 151 | return starlark.Binary(op, y, r.list) 152 | } 153 | return nil, nil 154 | } 155 | } 156 | return nil, nil 157 | } 158 | 159 | func (r *protoRepeated) wrapAppend() starlark.Value { 160 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 161 | var val starlark.Value 162 | if err := starlark.UnpackPositionalArgs("append", args, kwargs, 1, &val); err != nil { 163 | return nil, err 164 | } 165 | if err := r.Append(val); err != nil { 166 | return nil, err 167 | } 168 | return starlark.None, nil 169 | } 170 | return starlark.NewBuiltin("append", impl).BindReceiver(r) 171 | } 172 | 173 | func (r *protoRepeated) wrapExtend() starlark.Value { 174 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 175 | var val starlark.Iterable 176 | if err := starlark.UnpackPositionalArgs("extend", args, kwargs, 1, &val); err != nil { 177 | return nil, err 178 | } 179 | if err := r.Extend(val); err != nil { 180 | return nil, err 181 | } 182 | return starlark.None, nil 183 | } 184 | return starlark.NewBuiltin("extend", impl).BindReceiver(r) 185 | } 186 | -------------------------------------------------------------------------------- /go/parallelmodule/parallelmodule.go: -------------------------------------------------------------------------------- 1 | // Package parallelmodule contains Starlark functions for running code in parallel. 2 | // Unlike other Starlark modules in skycfg, this module is not loaded by skycfg by default. 3 | package parallelmodule 4 | 5 | import ( 6 | "fmt" 7 | 8 | "go.starlark.net/starlark" 9 | "go.starlark.net/starlarkstruct" 10 | "go.starlark.net/syntax" 11 | "golang.org/x/sync/errgroup" 12 | ) 13 | 14 | // NewModule returns a Starlark module of parallel functions. 15 | // 16 | // parallel = module( 17 | // map, 18 | // ) 19 | // 20 | // def map(function, iterable): 21 | // 22 | // Runs function on each element of iterable in parallel. 23 | // Returns a list containing the result of function, in the same order as the 24 | // original iterable. 25 | // If any call to function fails, parallel.map fails as well. 26 | // If multiple invocations fail, the error is chosen arbitrarily. 27 | // 28 | // To guarantee safety, the following measures are taken: 29 | // - The function and each element of iterable are frozen. 30 | // - map cannot be called during module initialization. 31 | // See https://github.com/google/starlark-go/issues/623. 32 | // - map detects and forbids recursion, even if Starlark is configured to 33 | // allow recursion. 34 | var Module = &starlarkstruct.Module{ 35 | Name: "parallel", 36 | Members: starlark.StringDict{ 37 | "map": starlark.NewBuiltin("parallel.map", parMap), 38 | }, 39 | } 40 | 41 | // makeTrampoline creates a function appropriate for [starlark.NewBuiltin] that 42 | // just calls the provided callable with args and wargs. 43 | func makeTrampoline(callable starlark.Callable) func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 44 | return func(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 45 | return starlark.Call(t, callable, args, kwargs) 46 | } 47 | } 48 | 49 | // callableWithPos is a callable that always has the given position in a stack trace. 50 | type callableWithPos struct { 51 | starlark.Callable 52 | pos syntax.Position 53 | } 54 | 55 | func (f callableWithPos) Position() syntax.Position { 56 | return f.pos 57 | } 58 | 59 | // replicateStack creates a Starlark callable that, when called, adds the call 60 | // frames of t to the call stack, and then calls fn. 61 | func replicateStack(t *starlark.Thread, fn starlark.Callable) starlark.Callable { 62 | for depth := range t.CallStackDepth() { 63 | fr := t.CallFrame(depth) 64 | builtin := starlark.NewBuiltin(fr.Name, makeTrampoline(fn)) 65 | fn = callableWithPos{builtin, fr.Pos} 66 | } 67 | return fn 68 | } 69 | 70 | func checkRecursionOrInitialization(t *starlark.Thread, fn *starlark.Builtin) error { 71 | seenPos := map[syntax.Position]struct{}{} 72 | for depth := range t.CallStackDepth() { 73 | fr := t.CallFrame(depth) 74 | 75 | // We cannot guarantee data race freedom during module initialization, 76 | // because freezing a function does not prevent a global from being 77 | // modified or reassigned (if -globalreassign). 78 | // See https://github.com/google/starlark-go/issues/623. 79 | // 80 | // Merely freezing the mapper's globals is **not** sufficient: 81 | // the iterable can also contain functions with access to globals. 82 | // See TestMap_mutate_global_during_toplevel2. 83 | // 84 | // On the other hand, if a module is fully initialized, race freedom is 85 | // guaranteed: 86 | // 1. Globals can never be reassigned by non-top-level Starlark functions. 87 | // 2. Global values must have been frozen already at the end of module 88 | // initialization. 89 | if fr.Name == "" { 90 | return fmt.Errorf("function %s cannot be called during module top-level initialization", fn.Name()) 91 | } 92 | 93 | // This is not entirely foolproof, since: 94 | // 1. we only check for cross-thread recursion if a parallel function 95 | // is called; 96 | // 2. checkRecursion checks frame positions, not function code identity 97 | // like Starlark itself. 98 | // 99 | // But in practice this is good enough to guarantee no infinite loop. 100 | // See TestMap_recurse_benign for a code sample that is allowed under this 101 | // check, but not by Starlark in single-threaded execution. 102 | if fr.Pos.Filename() == "" { 103 | // built-in function; ignore 104 | continue 105 | } 106 | if _, ok := seenPos[fr.Pos]; ok { 107 | return fmt.Errorf("function %s called recursively", fr.Name) 108 | } 109 | seenPos[fr.Pos] = struct{}{} 110 | } 111 | return nil 112 | } 113 | 114 | // par implements part of the function: 115 | // 116 | // def par(callable, iterable): 117 | // 118 | // It calls callable on each element of iterable, and returns a slice of result 119 | // values. 120 | func par(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) ([]starlark.Value, error) { 121 | fnName := fn.Name() 122 | 123 | var mapper starlark.Callable 124 | var iterable starlark.Iterable 125 | if err := starlark.UnpackPositionalArgs(fnName, args, kwargs, 2, &mapper, &iterable); err != nil { 126 | return nil, err 127 | } 128 | 129 | if err := checkRecursionOrInitialization(t, fn); err != nil { 130 | return nil, err 131 | } 132 | 133 | // Important: freeze inputs to prevent data races. 134 | mapper.Freeze() 135 | inputs := toFrozenSlice(iterable) 136 | 137 | out := make([]starlark.Value, len(inputs)) 138 | 139 | var eg errgroup.Group 140 | for i, val := range inputs { 141 | curThread, err := newThread(t, fn.Name(), i) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | var mapCaller starlark.Callable 147 | 148 | // Create a trampoline function with a descriptive name for nice stack traces. 149 | mapCaller = starlark.NewBuiltin(fmt.Sprintf("%s.fn[%d]", fnName, i), makeTrampoline(mapper)) 150 | 151 | // Replicate the stack of the parent thread. 152 | // This is important for two reasons: 153 | // 1. It makes checkRecursion work across threads. 154 | // 2. In case of evaluation error, the captured stack trace will 155 | // include the stack from the parent thread. 156 | mapCaller = replicateStack(t, mapCaller) 157 | 158 | eg.Go(func() error { 159 | mappedVal, err := starlark.Call(curThread, mapCaller, starlark.Tuple{val}, nil) 160 | if err != nil { 161 | return err 162 | } 163 | out[i] = mappedVal 164 | return nil 165 | }) 166 | } 167 | 168 | err := eg.Wait() 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | return out, nil 174 | } 175 | 176 | func parMap(t *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 177 | vals, err := par(t, fn, args, kwargs) 178 | if err != nil { 179 | return nil, err 180 | } 181 | return starlark.NewList(vals), nil 182 | } 183 | 184 | // toFrozenSlice converts an iterable to a slice of frozen values. 185 | func toFrozenSlice(seq starlark.Iterable) (out []starlark.Value) { 186 | iter := seq.Iterate() 187 | defer iter.Done() 188 | var elem starlark.Value 189 | for iter.Next(&elem) { 190 | elem.Freeze() 191 | out = append(out, elem) 192 | } 193 | return out 194 | } 195 | -------------------------------------------------------------------------------- /go/assertmodule/assert.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package assertmodule 18 | 19 | import ( 20 | "fmt" 21 | "sort" 22 | 23 | "go.starlark.net/starlark" 24 | "go.starlark.net/starlarkstruct" 25 | "go.starlark.net/syntax" 26 | ) 27 | 28 | // AssertModule contains assertion functions. 29 | // The *TestContext returned can be used to track assertion failures. 30 | // assert.* functions from this module will mutate the *TestContext. 31 | // After execution is complete, TestContext.Failures will be non-empty 32 | // if any of the assertions failed, and also contain details about the failures. 33 | func AssertModule() *TestContext { 34 | ctx := &TestContext{} 35 | ctx.Attrs = starlark.StringDict{} 36 | 37 | // this loop populates the assert module with binary comparator functions 38 | // e.g. assert.equal, assert.lesser, etc. 39 | // soo tokenToString for all supported operations 40 | for op, str := range tokenToString { 41 | ctx.Attrs[str] = starlark.NewBuiltin(fmt.Sprintf("assert.%s", str), ctx.AssertBinaryImpl(op)) 42 | } 43 | 44 | ctx.Attrs["fails"] = starlark.NewBuiltin("assert.fails", ctx.AssertFails) 45 | 46 | return ctx 47 | } 48 | 49 | // assertionError represents a failed assertion 50 | type assertionError struct { 51 | op *syntax.Token 52 | val1 starlark.Value 53 | val2 starlark.Value 54 | msg string 55 | callStack starlark.CallStack 56 | } 57 | 58 | func (err assertionError) Error() string { 59 | callStack := err.callStack[:len(err.callStack)-1] 60 | position := callStack.At(0).Pos.String() 61 | backtrace := callStack.String() 62 | 63 | // use custom message if provided 64 | if err.msg != "" { 65 | return fmt.Sprintf("[%s] assertion failed: %s\n%s", position, err.msg, backtrace) 66 | } 67 | 68 | // straight boolean assertions like assert.true(false) 69 | if err.op == nil { 70 | return fmt.Sprintf("[%s] assertion failed\n%s", position, backtrace) 71 | } 72 | 73 | // binary assertions, like assert.eql(1, 2) 74 | return fmt.Sprintf( 75 | "[%s] assertion failed: %s (type: %s) %s %s (type: %s)\n%s", 76 | position, 77 | err.val1.String(), 78 | err.val1.Type(), 79 | err.op.String(), 80 | err.val2.String(), 81 | err.val2.Type(), 82 | backtrace, 83 | ) 84 | } 85 | 86 | // TestContext is keeps track of whether there is a failure during a test execution 87 | type TestContext struct { 88 | Attrs starlark.StringDict 89 | Failures []error 90 | } 91 | 92 | var _ starlark.HasAttrs = (*TestContext)(nil) 93 | var _ starlark.Value = (*TestContext)(nil) 94 | var _ starlark.Callable = (*TestContext)(nil) 95 | 96 | func (t *TestContext) Name() string { return "assert" } 97 | func (t *TestContext) String() string { return "" } 98 | func (t *TestContext) Type() string { return "test_context" } 99 | func (t *TestContext) Freeze() { t.Attrs.Freeze() } 100 | func (t *TestContext) Truth() starlark.Bool { return starlark.True } 101 | func (t *TestContext) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: %s", t.Type()) } 102 | 103 | func (t *TestContext) Attr(name string) (starlark.Value, error) { 104 | if val, ok := t.Attrs[name]; ok { 105 | return val, nil 106 | } 107 | return nil, nil 108 | } 109 | 110 | func (t *TestContext) AttrNames() []string { 111 | var names []string 112 | for name := range t.Attrs { 113 | names = append(names, name) 114 | } 115 | sort.Strings(names) 116 | return names 117 | } 118 | 119 | // CallInternal is the implementation for assert(...) 120 | func (t *TestContext) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 121 | var val bool 122 | var msg starlark.String 123 | if err := starlark.UnpackArgs("assert", args, kwargs, "val", &val, "msg?", &msg); err != nil { 124 | return nil, err 125 | } 126 | 127 | if !val { 128 | err := assertionError{ 129 | msg: string(msg), 130 | callStack: thread.CallStack(), 131 | } 132 | t.Failures = append(t.Failures, err) 133 | return nil, err 134 | } 135 | 136 | return starlark.None, nil 137 | } 138 | 139 | // AssertBinaryImpl returns a function that implements comparing binary values in an assertion (i.e. assert_eq(1, 2)) 140 | func (t *TestContext) AssertBinaryImpl(op syntax.Token) func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 141 | return func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 142 | var val1 starlark.Value 143 | var val2 starlark.Value 144 | var msg starlark.String 145 | if err := starlark.UnpackArgs(fn.Name(), args, kwargs, "val1", &val1, "val2", &val2, "msg?", &msg); err != nil { 146 | return nil, err 147 | } 148 | 149 | passes, err := starlark.Compare(op, val1, val2) 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | if !passes { 155 | err := assertionError{ 156 | op: &op, 157 | val1: val1, 158 | val2: val2, 159 | msg: string(msg), 160 | callStack: thread.CallStack(), 161 | } 162 | t.Failures = append(t.Failures, err) 163 | return nil, err 164 | } 165 | 166 | return starlark.None, nil 167 | } 168 | } 169 | 170 | func (t *TestContext) AssertFails(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 171 | if len(args) < 1 { 172 | return nil, fmt.Errorf("assert.fails: missing argument for fn") 173 | } 174 | 175 | failFn := args[0] 176 | failArgs := args[1:] 177 | 178 | if _, err := starlark.Call(thread, failFn, failArgs, kwargs); err != nil { 179 | if _, ok := err.(*starlark.EvalError); ok { 180 | // an eval error means the function failed and the assertion passes 181 | // return a struct with `message` as the string from the error 182 | s := starlark.NewBuiltin("struct", starlarkstruct.Make) 183 | result := starlarkstruct.FromStringDict(s, starlark.StringDict{ 184 | "message": starlark.String(err.Error()), 185 | }) 186 | return result, nil 187 | } 188 | 189 | return nil, err 190 | } 191 | 192 | // if no error was returned, the assertion fails 193 | err := assertionError{ 194 | msg: fmt.Sprintf("function %s should have failed", failFn.(starlark.Callable).Name()), 195 | callStack: thread.CallStack(), 196 | } 197 | t.Failures = append(t.Failures, err) 198 | return nil, err 199 | } 200 | 201 | var tokenToString = map[syntax.Token]string{ 202 | syntax.LT: "lesser", 203 | syntax.GT: "greater", 204 | syntax.GE: "greater_or_equal", 205 | syntax.LE: "lesser_or_equal", 206 | syntax.EQL: "equal", 207 | syntax.NEQ: "not_equal", 208 | } 209 | -------------------------------------------------------------------------------- /_examples/k8s/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= 2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 5 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 6 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 8 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 9 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 10 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 11 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 12 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 13 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 14 | github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= 15 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 16 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= 17 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 18 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 19 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 20 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= 22 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 23 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 24 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= 25 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 26 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= 27 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 28 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 29 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 30 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 31 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 32 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 33 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 34 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 35 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 36 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 37 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 38 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 39 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 40 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 41 | go.starlark.net v0.0.0-20190604130855-6ddc71c0ba77/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= 42 | golang.org/x/crypto v0.0.0-20181029103014-dab2b1051b5d h1:5JyY8HlzxzYI+qHOOciM8s2lJbIEaefMUdtYt7dRDrg= 43 | golang.org/x/crypto v0.0.0-20181029103014-dab2b1051b5d/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 44 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o= 45 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 46 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 47 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 48 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 49 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 50 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 51 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 52 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= 53 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 54 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 55 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 56 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 57 | google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= 58 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 59 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 60 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 61 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 62 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 63 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 64 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 65 | google.golang.org/protobuf v1.25.1-0.20201016220047-aa45c4675289 h1:ieV9+8kKXheLmRJkIxAmpEA6oY01gnhqlpxEvc1e/tg= 66 | google.golang.org/protobuf v1.25.1-0.20201016220047-aa45c4675289/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= 67 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 68 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 69 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 70 | k8s.io/api v0.0.0-20181027024800-9fcf73cc980b h1:ojgIGmjzUSwsug8H2yVCoueRAGy0IshvrtowuLycEQo= 71 | k8s.io/api v0.0.0-20181027024800-9fcf73cc980b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 72 | k8s.io/apimachinery v0.0.0-20181026144617-b7f9f1fa80ae h1:QV0MJn7lF87qcyC3Y+On2zKM62Erf99uoORoQbu7lag= 73 | k8s.io/apimachinery v0.0.0-20181026144617-b7f9f1fa80ae/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 74 | k8s.io/client-go v9.0.0+incompatible h1:2kqW3X2xQ9SbFvWZjGEHBLlWc1LG9JIJNXWkuqwdZ3A= 75 | k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 76 | -------------------------------------------------------------------------------- /go/protomodule/protomodule_map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package protomodule 18 | 19 | import ( 20 | "fmt" 21 | 22 | "go.starlark.net/starlark" 23 | "go.starlark.net/syntax" 24 | "google.golang.org/protobuf/reflect/protoreflect" 25 | ) 26 | 27 | var allowedDictMethods = map[string]func(*protoMap) starlark.Value{ 28 | "clear": nil, 29 | "get": nil, 30 | "items": nil, 31 | "keys": nil, 32 | "setdefault": (*protoMap).wrapSetDefault, 33 | "update": (*protoMap).wrapUpdate, 34 | "values": nil, 35 | } 36 | 37 | // protoMap wraps an underlying starlark.Dict to enforce typechecking 38 | type protoMap struct { 39 | mapKey protoreflect.FieldDescriptor 40 | mapValue protoreflect.FieldDescriptor 41 | dict *starlark.Dict 42 | } 43 | 44 | var _ starlark.Value = (*protoMap)(nil) 45 | var _ starlark.Iterable = (*protoMap)(nil) 46 | var _ starlark.IterableMapping = (*protoMap)(nil) 47 | var _ starlark.Sequence = (*protoMap)(nil) 48 | var _ starlark.HasAttrs = (*protoMap)(nil) 49 | var _ starlark.HasSetKey = (*protoMap)(nil) 50 | var _ starlark.Comparable = (*protoMap)(nil) 51 | 52 | func newProtoMap(mapKey protoreflect.FieldDescriptor, mapValue protoreflect.FieldDescriptor) *protoMap { 53 | return newProtoMapSize(mapKey, mapValue, 0) 54 | } 55 | 56 | func newProtoMapSize(mapKey protoreflect.FieldDescriptor, mapValue protoreflect.FieldDescriptor, size int) *protoMap { 57 | return &protoMap{ 58 | mapKey: mapKey, 59 | mapValue: mapValue, 60 | dict: starlark.NewDict(size), 61 | } 62 | } 63 | 64 | func newProtoMapFromDict(mapKey protoreflect.FieldDescriptor, mapValue protoreflect.FieldDescriptor, d *starlark.Dict) (*protoMap, error) { 65 | out := &protoMap{ 66 | mapKey: mapKey, 67 | mapValue: mapValue, 68 | dict: d, 69 | } 70 | 71 | // SetKey is used to typecheck fields appropriately but done on a temporary object 72 | // so that the underlying out.dict still has a reference to the given 73 | // dict rather than copying 74 | tmpMap := newProtoMap(mapKey, mapValue) 75 | for _, item := range d.Items() { 76 | err := tmpMap.SetKey(item[0], item[1]) 77 | if err != nil { 78 | return nil, err 79 | } 80 | } 81 | 82 | // Remove any None values from map, see SetKey for compatibility behavior 83 | for _, item := range d.Items() { 84 | if item[1] == starlark.None { 85 | _, _, err := d.Delete(item[0]) 86 | if err != nil { 87 | return nil, err 88 | } 89 | } 90 | } 91 | 92 | return out, nil 93 | } 94 | 95 | func (m *protoMap) Attr(name string) (starlark.Value, error) { 96 | wrapper, ok := allowedDictMethods[name] 97 | if !ok { 98 | return nil, nil 99 | } 100 | if wrapper != nil { 101 | return wrapper(m), nil 102 | } 103 | return m.dict.Attr(name) 104 | } 105 | 106 | func (m *protoMap) AttrNames() []string { return m.dict.AttrNames() } 107 | func (m *protoMap) Freeze() { m.dict.Freeze() } 108 | func (m *protoMap) Hash() (uint32, error) { return m.dict.Hash() } 109 | func (m *protoMap) Get(k starlark.Value) (starlark.Value, bool, error) { return m.dict.Get(k) } 110 | func (m *protoMap) Iterate() starlark.Iterator { return m.dict.Iterate() } 111 | func (m *protoMap) Len() int { return m.dict.Len() } 112 | func (m *protoMap) String() string { return m.dict.String() } 113 | func (m *protoMap) Truth() starlark.Bool { return m.dict.Truth() } 114 | func (m *protoMap) Items() []starlark.Tuple { return m.dict.Items() } 115 | 116 | func (m *protoMap) Type() string { 117 | return fmt.Sprintf("map<%s, %s>", typeName(m.mapKey), typeName(m.mapValue)) 118 | } 119 | 120 | func (m *protoMap) CompareSameType(op syntax.Token, y starlark.Value, depth int) (bool, error) { 121 | other, ok := y.(*protoMap) 122 | if !ok { 123 | return false, nil 124 | } 125 | 126 | return starlark.CompareDepth(op, m.dict, other.dict, depth) 127 | } 128 | 129 | func (m *protoMap) wrapSetDefault() starlark.Value { 130 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 131 | var key, defaultValue starlark.Value = nil, starlark.None 132 | if err := starlark.UnpackPositionalArgs("setdefault", args, kwargs, 1, &key, &defaultValue); err != nil { 133 | return nil, err 134 | } 135 | if val, ok, err := m.dict.Get(key); err != nil { 136 | return nil, err 137 | } else if ok { 138 | return val, nil 139 | } 140 | return defaultValue, m.SetKey(key, defaultValue) 141 | } 142 | return starlark.NewBuiltin("setdefault", impl).BindReceiver(m) 143 | } 144 | 145 | func (m *protoMap) wrapUpdate() starlark.Value { 146 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 147 | // Use the underlying starlark `dict.update()` to get a Dict containing 148 | // all the new values, so we don't have to recreate the API here. After 149 | // the temp dict is constructed, type check. 150 | tempDict := &starlark.Dict{} 151 | tempUpdate, _ := tempDict.Attr("update") 152 | if _, err := starlark.Call(thread, tempUpdate, args, kwargs); err != nil { 153 | return nil, err 154 | } 155 | for _, item := range tempDict.Items() { 156 | if err := m.SetKey(item[0], item[1]); err != nil { 157 | return nil, err 158 | } 159 | } 160 | 161 | return starlark.None, nil 162 | } 163 | return starlark.NewBuiltin("update", impl).BindReceiver(m) 164 | } 165 | 166 | func (m *protoMap) SetKey(k, v starlark.Value) error { 167 | // Typecheck key 168 | err := scalarTypeCheck(m.mapKey, k) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | // Pre 1.0 compatibility allowed maps to be constructed with None in proto2 174 | // with the value treated as nil. protoreflect does not allow setting 175 | // with a value of nil, so instead treat it as an unset 176 | if fieldAllowsNone(m.mapValue) && v == starlark.None { 177 | _, _, err = m.dict.Delete(k) 178 | return err 179 | } 180 | 181 | // Typecheck value 182 | err = scalarTypeCheck(m.mapValue, v) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | return m.dict.SetKey(k, v) 188 | } 189 | 190 | // setKeyUnchecked is the same as [protoMap.SetKey], but assumes that k and v 191 | // have already been type-checked against the map's key and value types. 192 | func (m *protoMap) setKeyUnchecked(k, v starlark.Value) error { 193 | // Pre 1.0 compatibility allowed maps to be constructed with None in proto2 194 | // with the value treated as nil. protoreflect does not allow setting 195 | // with a value of nil, so instead treat it as an unset 196 | if fieldAllowsNone(m.mapValue) && v == starlark.None { 197 | _, _, err := m.dict.Delete(k) 198 | return err 199 | } 200 | 201 | return m.dict.SetKey(k, v) 202 | } 203 | -------------------------------------------------------------------------------- /go/protomodule/bench_test.go: -------------------------------------------------------------------------------- 1 | package protomodule 2 | 3 | import ( 4 | "testing" 5 | 6 | pb "github.com/stripe/skycfg/internal/test_proto" 7 | "go.starlark.net/starlark" 8 | "google.golang.org/protobuf/proto" 9 | "google.golang.org/protobuf/types/known/wrapperspb" 10 | ) 11 | 12 | var ( 13 | benchmarkNewMessage_empty_sink *protoMessage 14 | benchmarkNewMessage_empty_err error 15 | ) 16 | 17 | func BenchmarkNewMessage_empty(b *testing.B) { 18 | wantMsg := &pb.MessageV2{} 19 | benchmarkNewMessage_empty_sink, benchmarkNewMessage_empty_err = NewMessage(wantMsg) 20 | b.ResetTimer() 21 | for range b.N { 22 | benchmarkNewMessage_empty_sink, benchmarkNewMessage_empty_err = NewMessage(wantMsg) 23 | } 24 | } 25 | 26 | var ( 27 | benchmarkNewMessage_full_sink *protoMessage 28 | benchmarkNewMessage_full_err error 29 | ) 30 | 31 | func BenchmarkNewMessage_full(b *testing.B) { 32 | wantMsg := &pb.MessageV2{ 33 | FInt32: proto.Int32(1010), 34 | FInt64: proto.Int64(1020), 35 | FUint32: proto.Uint32(1030), 36 | FUint64: proto.Uint64(1040), 37 | FFloat32: proto.Float32(10.50), 38 | FFloat64: proto.Float64(10.60), 39 | FString: proto.String("some string"), 40 | FBool: proto.Bool(true), 41 | FSubmsg: &pb.MessageV2{ 42 | FString: proto.String("string in submsg"), 43 | }, 44 | RString: []string{"r_string1", "r_string2"}, 45 | RSubmsg: []*pb.MessageV2{{ 46 | FString: proto.String("string in r_submsg"), 47 | }}, 48 | MapString: map[string]string{ 49 | "map_string key": "map_string val", 50 | }, 51 | MapSubmsg: map[string]*pb.MessageV2{ 52 | "map_submsg key": { 53 | FString: proto.String("map_submsg val"), 54 | }, 55 | }, 56 | FNestedSubmsg: &pb.MessageV2_NestedMessage{ 57 | FString: proto.String("nested_submsg val"), 58 | }, 59 | FToplevelEnum: pb.ToplevelEnumV2_TOPLEVEL_ENUM_V2_B.Enum(), 60 | FNestedEnum: pb.MessageV2_NESTED_ENUM_B.Enum(), 61 | FOneof: &pb.MessageV2_FOneofB{FOneofB: "string in oneof"}, 62 | FBytes: []byte("also some string"), 63 | F_BoolValue: &wrapperspb.BoolValue{Value: true}, 64 | F_StringValue: &wrapperspb.StringValue{Value: "something"}, 65 | F_DoubleValue: &wrapperspb.DoubleValue{Value: 3110.4120}, 66 | F_Int32Value: &wrapperspb.Int32Value{Value: 110}, 67 | F_Int64Value: &wrapperspb.Int64Value{Value: 2148483647}, 68 | F_BytesValue: &wrapperspb.BytesValue{Value: []byte("foo/bar/baz")}, 69 | F_Uint32Value: &wrapperspb.UInt32Value{Value: 4294967295}, 70 | F_Uint64Value: &wrapperspb.UInt64Value{Value: 8294967295}, 71 | R_StringValue: []*wrapperspb.StringValue{ 72 | {Value: "s1"}, 73 | {Value: "s2"}, 74 | {Value: "s3"}, 75 | }, 76 | } 77 | benchmarkNewMessage_full_sink, benchmarkNewMessage_full_err = NewMessage(wantMsg) 78 | 79 | b.ResetTimer() 80 | for range b.N { 81 | benchmarkNewMessage_full_sink, benchmarkNewMessage_full_err = NewMessage(wantMsg) 82 | } 83 | } 84 | 85 | var ( 86 | benchmarkMessage_Attr_unpopulatedScalar_sink starlark.Value 87 | benchmarkMessage_Attr_unpopulatedScalar_err error 88 | ) 89 | 90 | func BenchmarkMessage_Attr_unpopulatedScalar(b *testing.B) { 91 | msg, err := NewMessage(&pb.MessageV3{}) 92 | if err != nil { 93 | b.Fatal(err) 94 | } 95 | b.ResetTimer() 96 | for range b.N { 97 | benchmarkMessage_Attr_unpopulatedScalar_sink, benchmarkMessage_Attr_unpopulatedScalar_err = msg.Attr("f_int32") 98 | } 99 | } 100 | 101 | var ( 102 | benchmarkMessage_Attr_populatedScalar_sink starlark.Value 103 | benchmarkMessage_Attr_populatedScalar_err error 104 | ) 105 | 106 | func BenchmarkMessage_Attr_populatedScalar(b *testing.B) { 107 | msg, err := NewMessage(&pb.MessageV3{ 108 | FInt32: 42, 109 | }) 110 | if err != nil { 111 | b.Fatal(err) 112 | } 113 | b.ResetTimer() 114 | for range b.N { 115 | benchmarkMessage_Attr_populatedScalar_sink, benchmarkMessage_Attr_populatedScalar_err = msg.Attr("f_int32") 116 | } 117 | } 118 | 119 | var ( 120 | benchmarkMessage_Attr_unpopulatedList_sink starlark.Value 121 | benchmarkMessage_Attr_unpopulatedList_err error 122 | ) 123 | 124 | func BenchmarkMessage_Attr_unpopulatedList(b *testing.B) { 125 | msg, err := NewMessage(&pb.MessageV3{}) 126 | if err != nil { 127 | b.Fatal(err) 128 | } 129 | msg.Freeze() 130 | b.ResetTimer() 131 | for range b.N { 132 | benchmarkMessage_Attr_unpopulatedList_sink, benchmarkMessage_Attr_unpopulatedList_err = msg.Attr("r_string") 133 | } 134 | } 135 | 136 | var ( 137 | benchmarkMessage_Attr_populatedList_sink starlark.Value 138 | benchmarkMessage_Attr_populatedList_err error 139 | ) 140 | 141 | func BenchmarkMessage_Attr_populatedList(b *testing.B) { 142 | msg, err := NewMessage(&pb.MessageV3{ 143 | RString: []string{"a", "b", "c"}, 144 | }) 145 | if err != nil { 146 | b.Fatal(err) 147 | } 148 | msg.Freeze() 149 | b.ResetTimer() 150 | for range b.N { 151 | benchmarkMessage_Attr_populatedList_sink, benchmarkMessage_Attr_populatedList_err = msg.Attr("r_string") 152 | } 153 | } 154 | 155 | var ( 156 | benchmarkMessage_Attr_unpopulatedLastItem_sink starlark.Value 157 | benchmarkMessage_Attr_unpopulatedLastItem_err error 158 | ) 159 | 160 | func BenchmarkMessage_Attr_unpopulatedLastItem(b *testing.B) { 161 | msg, err := NewMessage(&pb.MessageV3{}) 162 | if err != nil { 163 | b.Fatal(err) 164 | } 165 | b.ResetTimer() 166 | 167 | for range b.N { 168 | benchmarkMessage_Attr_unpopulatedLastItem_sink, benchmarkMessage_Attr_unpopulatedLastItem_err = msg.Attr("safe_") 169 | } 170 | } 171 | 172 | var ( 173 | benchmarkMessage_Attr_populatedLastItem_sink starlark.Value 174 | benchmarkMessage_Attr_populatedLastItem_err error 175 | ) 176 | 177 | func BenchmarkMessage_Attr_populatedLastItem(b *testing.B) { 178 | msg, err := NewMessage(&pb.MessageV3{Safe_: true}) 179 | if err != nil { 180 | b.Fatal(err) 181 | } 182 | b.ResetTimer() 183 | 184 | for range b.N { 185 | benchmarkMessage_Attr_populatedLastItem_sink, benchmarkMessage_Attr_populatedLastItem_err = msg.Attr("safe_") 186 | } 187 | } 188 | 189 | var ( 190 | benchmarkMessage_SetField_unpopulatedInt32_err error 191 | ) 192 | 193 | func BenchmarkMessage_SetField_unpopulatedInt32(b *testing.B) { 194 | msg, err := NewMessage(&pb.MessageV3{}) 195 | if err != nil { 196 | b.Fatal(err) 197 | } 198 | b.ResetTimer() 199 | 200 | si := starlark.MakeInt(100) 201 | for range b.N { 202 | benchmarkMessage_SetField_unpopulatedInt32_err = msg.SetField("f_int32", si) 203 | } 204 | } 205 | 206 | var ( 207 | benchmarkMessage_SetField_populatedInt32_err error 208 | ) 209 | 210 | func BenchmarkMessage_SetField_populatedInt32(b *testing.B) { 211 | msg, err := NewMessage(&pb.MessageV3{ 212 | FInt32: 42, 213 | }) 214 | if err != nil { 215 | b.Fatal(err) 216 | } 217 | b.ResetTimer() 218 | 219 | si := starlark.MakeInt(100) 220 | for range b.N { 221 | benchmarkMessage_SetField_populatedInt32_err = msg.SetField("f_int32", si) 222 | } 223 | } 224 | 225 | var ( 226 | benchmarkMessage_SetField_unpopulatedLastItem_err error 227 | ) 228 | 229 | func BenchmarkMessage_SetField_unpopulatedLastItem(b *testing.B) { 230 | msg, err := NewMessage(&pb.MessageV3{}) 231 | if err != nil { 232 | b.Fatal(err) 233 | } 234 | b.ResetTimer() 235 | 236 | for range b.N { 237 | benchmarkMessage_SetField_unpopulatedLastItem_err = msg.SetField("safe_", starlark.True) 238 | } 239 | } 240 | 241 | var ( 242 | benchmarkMessage_SetField_populatedLastItem_err error 243 | ) 244 | 245 | func BenchmarkMessage_SetField_populatedLastItem(b *testing.B) { 246 | msg, err := NewMessage(&pb.MessageV3{Safe_: true}) 247 | if err != nil { 248 | b.Fatal(err) 249 | } 250 | b.ResetTimer() 251 | 252 | for range b.N { 253 | benchmarkMessage_SetField_populatedLastItem_err = msg.SetField("safe_", starlark.True) 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Skycfg 2 | 3 | Skycfg is an extension library for the [Starlark language](https://github.com/bazelbuild/starlark) that adds support for constructing [Protocol Buffer](https://developers.google.com/protocol-buffers/) messages. It was developed by Stripe to simplify configuration of Kubernetes services, Envoy routes, Terraform resources, and other complex configuration data. 4 | 5 | At present, only the Go implementation of Starlark is supported. 6 | 7 | [![API Documentation](https://godoc.org/github.com/stripe/skycfg?status.svg)](https://godoc.org/github.com/stripe/skycfg) 8 | [![Test Status](https://github.com/stripe/skycfg/workflows/Tests/badge.svg)](https://github.com/stripe/skycfg/actions) 9 | [![Test Coverage](https://coveralls.io/repos/github/stripe/skycfg/badge.svg?branch=master)](https://coveralls.io/github/stripe/skycfg?branch=master) 10 | 11 | ## Getting Started 12 | 13 | The entry point to Skycfg is the [`skycfg.Load()`](https://godoc.org/pkg/github.com/stripe/skycfg/#Load) function, which reads a configuration file from local disk. As the implementation stabilizes we expect to expand the public API surface so that Skycfg can be combined with other Starlark extensions. 14 | 15 | Lets start with a simple `main` function that prints out every Protobuf message created by the config file `hello.sky`: 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "github.com/stripe/skycfg" 24 | _ "github.com/golang/protobuf/ptypes/wrappers" 25 | ) 26 | 27 | func main() { 28 | ctx := context.Background() 29 | config, err := skycfg.Load(ctx, "hello.sky") 30 | if err != nil { panic(err) } 31 | messages, err := config.Main(ctx) 32 | if err != nil { panic(err) } 33 | for _, msg := range messages { 34 | fmt.Printf("%s\n", msg.String()) 35 | } 36 | } 37 | ``` 38 | 39 | ```python 40 | # hello.sky 41 | pb = proto.package("google.protobuf") 42 | 43 | def main(ctx): 44 | return [pb.StringValue(value = "Hello, world!")] 45 | ``` 46 | 47 | Now we can build a small test driver and see what values are returned: 48 | 49 | ``` 50 | $ go get github.com/stripe/skycfg 51 | $ go build -o test-skycfg 52 | $ ./test-skycfg 53 | value:"Hello, world!" 54 | $ 55 | ``` 56 | 57 | Success! 58 | 59 | For more in-depth examples covering specific topics, see the `_examples/` directory: 60 | * [`_examples/repl`](https://github.com/stripe/skycfg/tree/master/_examples/repl): Interactive evaluation of a Skycfg file 61 | * [`_examples/k8s`](https://github.com/stripe/skycfg/tree/master/_examples/k8s): Basic Kubernetes integration 62 | * [`_examples/wasm`](https://github.com/stripe/skycfg/tree/master/_examples/wasm): Skycfg in the browser with WebAssembly 63 | 64 | ## Why use Skycfg? 65 | 66 | Compared to bare YAML or TOML, the Python-like syntax of Skycfg might not seem like a win. Why would we want configuration files with all those quotes and colons and braces? 67 | 68 | There are four important benefits to using Skycfg over YAML: 69 | 70 | ### Type Safety 71 | 72 | Protobuf has a statically-typed data model, which means the type of every field is known to Skycfg when it's building your configuration. There is no risk of accidentally assigning a string to a number, a struct to a different struct, or forgetting to quote a YAML value. 73 | 74 | ```python 75 | pb = proto.package("google.protobuf") 76 | 77 | def main(ctx): 78 | return [pb.StringValue(value = 123)] 79 | ``` 80 | 81 | ``` 82 | $ ./test-skycfg 83 | panic: TypeError: value 123 (type `int') can't be assigned to type `string'. 84 | ``` 85 | 86 | ### Functions 87 | 88 | As in standard Python, you can define helper functions to reduce duplicated typing and share logic. 89 | 90 | ```python 91 | pb = proto.package("google.protobuf") 92 | 93 | def greet(lang): 94 | greeting = { 95 | "en": "Hello, world!", 96 | "fr": "Bonjour, monde!", 97 | }[lang] 98 | return pb.StringValue(value = greeting) 99 | 100 | def main(ctx): 101 | return [greet("en"), greet("fr")] 102 | ``` 103 | ``` 104 | $ ./test-skycfg 105 | value:"Hello world!" 106 | value:"Bonjour, monde!" 107 | ``` 108 | 109 | ### Modules 110 | 111 | Starlark supports importing modules from other files, which you can use to share common code between configurations. By default the paths to load are resolved on the local filesystem, but you can also override the `load()` handler to support syntaxes such as [Bazel-style target labels](https://docs.bazel.build/versions/master/build-ref.html). 112 | 113 | Modules can protect service owners from complex Kubernetes logic: 114 | 115 | ```python 116 | load("//config/common/kubernetes.sky", "kubernetes") 117 | 118 | def my_service(ctx): 119 | return kubernetes.pod( 120 | name = "my-namespace/my-service", 121 | containers = [ 122 | kubernetes.container(name = "main", image = "index.docker.io/hello-world"), 123 | ] 124 | ) 125 | ``` 126 | 127 | When combined with VCS hooks like [GitHub CODEOWNERS](https://help.github.com/articles/about-codeowners/), you can use modules to provide an API surface for third-party tools deployed in your infrastructure: 128 | 129 | ```python 130 | load("//config/common/constants.sky", "CLUSTERS") 131 | load("//config/production/k8s_dashboard/v1.10.0/main.sky", 132 | "kubernetes_dashboard") 133 | 134 | def main(ctx): 135 | return [ 136 | kubernetes_dashboard(ctx, cluster = CLUSTERS['ord1']), 137 | kubernetes_dashboard(ctx, cluster = CLUSTERS['ord1-canary']), 138 | ] 139 | ``` 140 | 141 | ### Context Variables 142 | 143 | Skycfg supports limited dynamic behavior through the use of _context variables_, which let the Go caller pass arbitrary key:value pairs in the `ctx` parameter. 144 | 145 | ```go 146 | func main() { 147 | // ... 148 | messages, err := config.Main(ctx, skycfg.WithVars(starlark.StringDict{ 149 | "revision": starlark.String("master/12345"), 150 | })) 151 | ``` 152 | 153 | ```python 154 | pb = proto.package("google.protobuf") 155 | 156 | def main(ctx): 157 | print("ctx.vars:", ctx.vars) 158 | return [] 159 | ``` 160 | 161 | ``` 162 | $ ./test-skycfg 163 | [hello.sky:4] ctx.vars: {"revision": "master/12345"} 164 | ``` 165 | 166 | ## Contributing 167 | 168 | We welcome contributions from the community. For small simple changes, go ahead and [open a pull request](https://github.com/stripe/skycfg/compare). Larger changes should start out in the issue tracker, so we can make sure they fit into the roadmap. Changes to the Starlark language itself (such as new primitive types or syntax) should be applied to https://github.com/google/starlark-go. 169 | 170 | Bazel is the officially supported build system for skycfg. To regenerate BUILD files, run: 171 | 172 | ```bash 173 | $ bazel run //:gazelle 174 | ``` 175 | 176 | However, the `go` toolchain is unofficially supported as well. To get started: 177 | 178 | ```bash 179 | $ go install google.golang.org/protobuf/cmd/protoc-gen-go 180 | $ go generate ./... 181 | $ go test ./... 182 | ``` 183 | 184 | ## Stability 185 | 186 | Skycfg depends on internal details of the go-protobuf generated code, and as such may need to be updated to work with future versions of go-protobuf. We will release Skycfg v1.0 after all dependencies on go-protobuf implementation details have been fixed, which will be after the "api-v2" branch lands in a stable release of go-protobuf. 187 | 188 | Our existing public APIs are expected to be stable even before the v1.0 release. Symbols that will change before v1.0 are hidden from the public docs and named `Unstable*`. 189 | 190 | ## Known issues 191 | 192 | ### gogo/protobuf support 193 | 194 | Skycfg has dropped support for 195 | [gogo/protobuf](https://github.com/gogo/protobuf), an alternative protobuf go 196 | code generator, when upgrading to using the go-protobuf v2 api. For using gogo 197 | support, see `Skycfg@v0.1.0` (Skycfg prior to switching to v2). 198 | -------------------------------------------------------------------------------- /_examples/repl/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 3 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 5 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 6 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 7 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 8 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/envoyproxy/go-control-plane v0.6.0 h1:Oww9IYSQ6w7v0L2yNxmsI2lOBTtWYvZuA4bgteKe9pg= 12 | github.com/envoyproxy/go-control-plane v0.6.0/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 13 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 14 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 15 | github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI= 16 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= 17 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 18 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 19 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 20 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 21 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 22 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 23 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 24 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 25 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 26 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 27 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 28 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 29 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 30 | github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= 31 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 32 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 33 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 34 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= 36 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 37 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 38 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 39 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= 40 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 41 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 42 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 43 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= 44 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 45 | github.com/lyft/protoc-gen-validate v0.0.7 h1:WdKhTdks4N9eamVA+rrPY3hc/q25G8r7/9EvNVWgy1A= 46 | github.com/lyft/protoc-gen-validate v0.0.7/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= 47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 49 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 50 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 51 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 52 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 53 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 54 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 55 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 56 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 57 | go.starlark.net v0.0.0-20201204201740-42d4f566359b h1:yHUzJ1WfcdR1oOafytJ6K1/ntYwnEIXICNVzHb+FzbA= 58 | go.starlark.net v0.0.0-20201204201740-42d4f566359b/go.mod h1:5YFcFnRptTN+41758c2bMPiqpGg4zBfYji1IQz8wNFk= 59 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 60 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= 61 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 62 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 63 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 64 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 65 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= 66 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 67 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 68 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 69 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= 70 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 71 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 72 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 73 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 74 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 75 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= 76 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 77 | google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw= 78 | google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 79 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 80 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 81 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 82 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 83 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 84 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 85 | google.golang.org/protobuf v1.25.1-0.20201016220047-aa45c4675289 h1:ieV9+8kKXheLmRJkIxAmpEA6oY01gnhqlpxEvc1e/tg= 86 | google.golang.org/protobuf v1.25.1-0.20201016220047-aa45c4675289/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= 87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 88 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 89 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 90 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 91 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 92 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 93 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 94 | k8s.io/api v0.0.0-20181009084535-e3c5c37695fb h1:LK8d5VfIkd0/RGexcf01IU6R7PYtf41NOzN5NZqb1Jk= 95 | k8s.io/api v0.0.0-20181009084535-e3c5c37695fb/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 96 | k8s.io/apimachinery v0.0.0-20181009084401-76721d167b70 h1:4aurmAVLVQICkGCyOmj9OXcV2RZq3KTjj1ssQ0rQ6iM= 97 | k8s.io/apimachinery v0.0.0-20181009084401-76721d167b70/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 98 | -------------------------------------------------------------------------------- /_examples/wasm/addressbook/addressbook.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: addressbook.proto 3 | 4 | package tutorial 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 10 | math "math" 11 | ) 12 | 13 | // Reference imports to suppress errors if they are not otherwise used. 14 | var _ = proto.Marshal 15 | var _ = fmt.Errorf 16 | var _ = math.Inf 17 | 18 | // This is a compile-time assertion to ensure that this generated file 19 | // is compatible with the proto package it is being compiled against. 20 | // A compilation error at this line likely means your copy of the 21 | // proto package needs to be updated. 22 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 23 | 24 | type Person_PhoneType int32 25 | 26 | const ( 27 | Person_MOBILE Person_PhoneType = 0 28 | Person_HOME Person_PhoneType = 1 29 | Person_WORK Person_PhoneType = 2 30 | ) 31 | 32 | var Person_PhoneType_name = map[int32]string{ 33 | 0: "MOBILE", 34 | 1: "HOME", 35 | 2: "WORK", 36 | } 37 | 38 | var Person_PhoneType_value = map[string]int32{ 39 | "MOBILE": 0, 40 | "HOME": 1, 41 | "WORK": 2, 42 | } 43 | 44 | func (x Person_PhoneType) String() string { 45 | return proto.EnumName(Person_PhoneType_name, int32(x)) 46 | } 47 | 48 | func (Person_PhoneType) EnumDescriptor() ([]byte, []int) { 49 | return fileDescriptor_1eb1a68c9dd6d429, []int{0, 0} 50 | } 51 | 52 | // [START messages] 53 | type Person struct { 54 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 55 | Id int32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` 56 | Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` 57 | Phones []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"` 58 | LastUpdated *timestamp.Timestamp `protobuf:"bytes,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` 59 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 60 | XXX_unrecognized []byte `json:"-"` 61 | XXX_sizecache int32 `json:"-"` 62 | } 63 | 64 | func (m *Person) Reset() { *m = Person{} } 65 | func (m *Person) String() string { return proto.CompactTextString(m) } 66 | func (*Person) ProtoMessage() {} 67 | func (*Person) Descriptor() ([]byte, []int) { 68 | return fileDescriptor_1eb1a68c9dd6d429, []int{0} 69 | } 70 | 71 | func (m *Person) XXX_Unmarshal(b []byte) error { 72 | return xxx_messageInfo_Person.Unmarshal(m, b) 73 | } 74 | func (m *Person) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 75 | return xxx_messageInfo_Person.Marshal(b, m, deterministic) 76 | } 77 | func (m *Person) XXX_Merge(src proto.Message) { 78 | xxx_messageInfo_Person.Merge(m, src) 79 | } 80 | func (m *Person) XXX_Size() int { 81 | return xxx_messageInfo_Person.Size(m) 82 | } 83 | func (m *Person) XXX_DiscardUnknown() { 84 | xxx_messageInfo_Person.DiscardUnknown(m) 85 | } 86 | 87 | var xxx_messageInfo_Person proto.InternalMessageInfo 88 | 89 | func (m *Person) GetName() string { 90 | if m != nil { 91 | return m.Name 92 | } 93 | return "" 94 | } 95 | 96 | func (m *Person) GetId() int32 { 97 | if m != nil { 98 | return m.Id 99 | } 100 | return 0 101 | } 102 | 103 | func (m *Person) GetEmail() string { 104 | if m != nil { 105 | return m.Email 106 | } 107 | return "" 108 | } 109 | 110 | func (m *Person) GetPhones() []*Person_PhoneNumber { 111 | if m != nil { 112 | return m.Phones 113 | } 114 | return nil 115 | } 116 | 117 | func (m *Person) GetLastUpdated() *timestamp.Timestamp { 118 | if m != nil { 119 | return m.LastUpdated 120 | } 121 | return nil 122 | } 123 | 124 | type Person_PhoneNumber struct { 125 | Number string `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"` 126 | Type Person_PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=tutorial.Person_PhoneType" json:"type,omitempty"` 127 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 128 | XXX_unrecognized []byte `json:"-"` 129 | XXX_sizecache int32 `json:"-"` 130 | } 131 | 132 | func (m *Person_PhoneNumber) Reset() { *m = Person_PhoneNumber{} } 133 | func (m *Person_PhoneNumber) String() string { return proto.CompactTextString(m) } 134 | func (*Person_PhoneNumber) ProtoMessage() {} 135 | func (*Person_PhoneNumber) Descriptor() ([]byte, []int) { 136 | return fileDescriptor_1eb1a68c9dd6d429, []int{0, 0} 137 | } 138 | 139 | func (m *Person_PhoneNumber) XXX_Unmarshal(b []byte) error { 140 | return xxx_messageInfo_Person_PhoneNumber.Unmarshal(m, b) 141 | } 142 | func (m *Person_PhoneNumber) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 143 | return xxx_messageInfo_Person_PhoneNumber.Marshal(b, m, deterministic) 144 | } 145 | func (m *Person_PhoneNumber) XXX_Merge(src proto.Message) { 146 | xxx_messageInfo_Person_PhoneNumber.Merge(m, src) 147 | } 148 | func (m *Person_PhoneNumber) XXX_Size() int { 149 | return xxx_messageInfo_Person_PhoneNumber.Size(m) 150 | } 151 | func (m *Person_PhoneNumber) XXX_DiscardUnknown() { 152 | xxx_messageInfo_Person_PhoneNumber.DiscardUnknown(m) 153 | } 154 | 155 | var xxx_messageInfo_Person_PhoneNumber proto.InternalMessageInfo 156 | 157 | func (m *Person_PhoneNumber) GetNumber() string { 158 | if m != nil { 159 | return m.Number 160 | } 161 | return "" 162 | } 163 | 164 | func (m *Person_PhoneNumber) GetType() Person_PhoneType { 165 | if m != nil { 166 | return m.Type 167 | } 168 | return Person_MOBILE 169 | } 170 | 171 | // Our address book file is just one of these. 172 | type AddressBook struct { 173 | People []*Person `protobuf:"bytes,1,rep,name=people,proto3" json:"people,omitempty"` 174 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 175 | XXX_unrecognized []byte `json:"-"` 176 | XXX_sizecache int32 `json:"-"` 177 | } 178 | 179 | func (m *AddressBook) Reset() { *m = AddressBook{} } 180 | func (m *AddressBook) String() string { return proto.CompactTextString(m) } 181 | func (*AddressBook) ProtoMessage() {} 182 | func (*AddressBook) Descriptor() ([]byte, []int) { 183 | return fileDescriptor_1eb1a68c9dd6d429, []int{1} 184 | } 185 | 186 | func (m *AddressBook) XXX_Unmarshal(b []byte) error { 187 | return xxx_messageInfo_AddressBook.Unmarshal(m, b) 188 | } 189 | func (m *AddressBook) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 190 | return xxx_messageInfo_AddressBook.Marshal(b, m, deterministic) 191 | } 192 | func (m *AddressBook) XXX_Merge(src proto.Message) { 193 | xxx_messageInfo_AddressBook.Merge(m, src) 194 | } 195 | func (m *AddressBook) XXX_Size() int { 196 | return xxx_messageInfo_AddressBook.Size(m) 197 | } 198 | func (m *AddressBook) XXX_DiscardUnknown() { 199 | xxx_messageInfo_AddressBook.DiscardUnknown(m) 200 | } 201 | 202 | var xxx_messageInfo_AddressBook proto.InternalMessageInfo 203 | 204 | func (m *AddressBook) GetPeople() []*Person { 205 | if m != nil { 206 | return m.People 207 | } 208 | return nil 209 | } 210 | 211 | func init() { 212 | proto.RegisterEnum("tutorial.Person_PhoneType", Person_PhoneType_name, Person_PhoneType_value) 213 | proto.RegisterType((*Person)(nil), "tutorial.Person") 214 | proto.RegisterType((*Person_PhoneNumber)(nil), "tutorial.Person.PhoneNumber") 215 | proto.RegisterType((*AddressBook)(nil), "tutorial.AddressBook") 216 | } 217 | 218 | func init() { proto.RegisterFile("addressbook.proto", fileDescriptor_1eb1a68c9dd6d429) } 219 | 220 | var fileDescriptor_1eb1a68c9dd6d429 = []byte{ 221 | // 350 bytes of a gzipped FileDescriptorProto 222 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x5f, 0x4b, 0xbb, 0x50, 223 | 0x18, 0xc7, 0x7f, 0x3a, 0x27, 0xdb, 0xe3, 0x8f, 0xe1, 0x0e, 0x23, 0x44, 0x82, 0x64, 0x74, 0x21, 224 | 0x04, 0x67, 0xb0, 0x82, 0xae, 0xba, 0x48, 0x18, 0x15, 0xb5, 0x26, 0xb2, 0xd1, 0x65, 0x1c, 0xf3, 225 | 0xb4, 0x64, 0xea, 0x39, 0x78, 0x8e, 0xd0, 0xde, 0x52, 0x6f, 0xa1, 0x37, 0x17, 0x7a, 0x74, 0x8c, 226 | 0xe8, 0xee, 0xf9, 0xf3, 0xf1, 0xf1, 0x7b, 0x3e, 0x30, 0x26, 0x49, 0x52, 0x52, 0x21, 0x62, 0xc6, 227 | 0x76, 0x98, 0x97, 0x4c, 0x32, 0x34, 0x90, 0x95, 0x64, 0x65, 0x4a, 0x32, 0xf7, 0x6c, 0xcb, 0xd8, 228 | 0x36, 0xa3, 0xb3, 0x66, 0x1e, 0x57, 0xef, 0x33, 0x99, 0xe6, 0x54, 0x48, 0x92, 0x73, 0x85, 0x4e, 229 | 0xbf, 0x75, 0x30, 0x43, 0x5a, 0x0a, 0x56, 0x20, 0x04, 0x46, 0x41, 0x72, 0xea, 0x68, 0x9e, 0xe6, 230 | 0x0f, 0xa3, 0xa6, 0x46, 0x23, 0xd0, 0xd3, 0xc4, 0xd1, 0x3d, 0xcd, 0xef, 0x47, 0x7a, 0x9a, 0xa0, 231 | 0x09, 0xf4, 0x69, 0x4e, 0xd2, 0xcc, 0xe9, 0x35, 0x90, 0x6a, 0xd0, 0x15, 0x98, 0xfc, 0x83, 0x15, 232 | 0x54, 0x38, 0x86, 0xd7, 0xf3, 0xad, 0xf9, 0x29, 0xee, 0x02, 0x60, 0x75, 0x1b, 0x87, 0xf5, 0xfa, 233 | 0xb9, 0xca, 0x63, 0x5a, 0x46, 0x2d, 0x8b, 0x6e, 0xe0, 0x7f, 0x46, 0x84, 0x7c, 0xad, 0x78, 0x42, 234 | 0x24, 0x4d, 0x9c, 0xbe, 0xa7, 0xf9, 0xd6, 0xdc, 0xc5, 0x2a, 0x32, 0xee, 0x22, 0xe3, 0x75, 0x17, 235 | 0x39, 0xb2, 0x6a, 0x7e, 0xa3, 0x70, 0x77, 0x03, 0xd6, 0xd1, 0x55, 0x74, 0x02, 0x66, 0xd1, 0x54, 236 | 0x6d, 0xfe, 0xb6, 0x43, 0x18, 0x0c, 0xb9, 0xe7, 0xb4, 0x79, 0xc3, 0x68, 0xee, 0xfe, 0x9d, 0x6c, 237 | 0xbd, 0xe7, 0x34, 0x6a, 0xb8, 0xe9, 0x05, 0x0c, 0x0f, 0x23, 0x04, 0x60, 0x2e, 0x57, 0xc1, 0xc3, 238 | 0xd3, 0xc2, 0xfe, 0x87, 0x06, 0x60, 0xdc, 0xaf, 0x96, 0x0b, 0x5b, 0xab, 0xab, 0x97, 0x55, 0xf4, 239 | 0x68, 0xeb, 0xd3, 0x6b, 0xb0, 0x6e, 0x95, 0xfd, 0x80, 0xb1, 0x1d, 0xf2, 0xc1, 0xe4, 0x94, 0xf1, 240 | 0xac, 0x76, 0x58, 0x7b, 0xb0, 0x7f, 0xff, 0x2d, 0x6a, 0xf7, 0x41, 0x08, 0x93, 0x37, 0x96, 0x63, 241 | 0xfa, 0x49, 0x72, 0x9e, 0xd1, 0x03, 0x16, 0x8c, 0x8f, 0xce, 0x85, 0xb5, 0x00, 0xf1, 0xa5, 0x9f, 242 | 0xdf, 0x29, 0x21, 0x61, 0x27, 0x64, 0xa1, 0xbe, 0x12, 0xf8, 0x08, 0x8e, 0xcd, 0xc6, 0xd7, 0xe5, 243 | 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x33, 0xde, 0xce, 0x42, 0x0f, 0x02, 0x00, 0x00, 244 | } 245 | -------------------------------------------------------------------------------- /_examples/envoy/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "math/rand" 24 | "net" 25 | "os" 26 | "os/signal" 27 | "sync" 28 | "syscall" 29 | "time" 30 | 31 | api "github.com/envoyproxy/go-control-plane/envoy/api/v2" 32 | _ "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth" 33 | _ "github.com/envoyproxy/go-control-plane/envoy/api/v2/cluster" 34 | _ "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint" 35 | _ "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener" 36 | _ "github.com/envoyproxy/go-control-plane/envoy/api/v2/ratelimit" 37 | _ "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" 38 | _ "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2" 39 | _ "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 40 | _ "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3" 41 | _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 42 | 43 | core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 44 | discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2" 45 | envoy_types "github.com/envoyproxy/go-control-plane/pkg/cache/types" 46 | cache "github.com/envoyproxy/go-control-plane/pkg/cache/v2" 47 | server "github.com/envoyproxy/go-control-plane/pkg/server/v2" 48 | "github.com/golang/protobuf/proto" 49 | log "github.com/sirupsen/logrus" 50 | "github.com/stripe/skycfg" 51 | "golang.org/x/time/rate" 52 | "google.golang.org/grpc" 53 | "google.golang.org/grpc/health" 54 | health_pb "google.golang.org/grpc/health/grpc_health_v1" 55 | ) 56 | 57 | const ( 58 | node = "" 59 | ) 60 | 61 | var ( 62 | addr = flag.String("addr", ":8080", "Address to start the discovery service on") 63 | level = flag.String("log-level", "info", "Log level to write application logs") 64 | 65 | logger = log.New() 66 | limiter = rate.NewLimiter(1, 1) 67 | 68 | cbFuncs = server.CallbackFuncs{ 69 | StreamOpenFunc: func(ctx context.Context, id int64, s string) error { 70 | logger.Printf("[xds][%d] accepted connection from peer: %+v", id, s) 71 | return nil 72 | }, 73 | StreamClosedFunc: func(id int64) { 74 | logger.Printf("[xds][%d] connection closed for peer", id) 75 | }, 76 | StreamRequestFunc: func(id int64, req *api.DiscoveryRequest) error { 77 | if err := limiter.Wait(context.Background()); err != nil { 78 | logger.Errorf("[xds][%d, %s] could not enforce rate limit: %+v", id, req.GetTypeUrl(), err) 79 | } 80 | 81 | logger.Printf( 82 | "[xds][%d, %s] recieved discovery request (version %q) from peer: %+v", 83 | id, req.GetTypeUrl(), req.GetVersionInfo(), req.GetNode().GetId(), 84 | ) 85 | logger.Debugf( 86 | "[%d, %s] discovery request contents: %#v", 87 | id, req.GetTypeUrl(), req, 88 | ) 89 | return nil 90 | }, 91 | StreamResponseFunc: func(id int64, req *api.DiscoveryRequest, res *api.DiscoveryResponse) { 92 | logger.Printf( 93 | "[xds][%d, %s] sending discovery response (version %q) to peer: %+v", 94 | id, req.GetTypeUrl(), res.GetVersionInfo(), req.GetNode().GetId(), 95 | ) 96 | logger.Debugf( 97 | "[xds][%d, %s] discovery response contents: %#v", 98 | id, req.GetTypeUrl(), res, 99 | ) 100 | }, 101 | } 102 | ) 103 | 104 | type nodeHashFunc func(node *core.Node) string 105 | 106 | func (h nodeHashFunc) ID(node *core.Node) string { 107 | return h(node) 108 | } 109 | 110 | func resourcesByType(version string, protos []proto.Message) [envoy_types.UnknownType]cache.Resources { 111 | m := map[envoy_types.ResponseType][]envoy_types.Resource{} 112 | for _, proto := range protos { 113 | switch proto.(type) { 114 | case *api.ClusterLoadAssignment: 115 | m[envoy_types.Endpoint] = append(m[envoy_types.Endpoint], envoy_types.Resource(proto)) 116 | case *api.Listener: 117 | m[envoy_types.Listener] = append(m[envoy_types.Listener], envoy_types.Resource(proto)) 118 | case *api.Cluster: 119 | m[envoy_types.Cluster] = append(m[envoy_types.Cluster], envoy_types.Resource(proto)) 120 | default: 121 | m[envoy_types.UnknownType] = append(m[envoy_types.UnknownType], envoy_types.Resource(proto)) 122 | } 123 | } 124 | 125 | ret := [envoy_types.UnknownType]cache.Resources{} 126 | for k, v := range m { 127 | ret[k] = cache.NewResources(version, v) 128 | } 129 | 130 | return ret 131 | } 132 | 133 | func resourcesEqual(a, b cache.Resources) bool { 134 | for aKey, aValue := range a.Items { 135 | bValue := b.Items[aKey] 136 | if !proto.Equal(aValue, bValue) { 137 | return false 138 | } 139 | } 140 | 141 | for bKey, bValue := range b.Items { 142 | aValue := a.Items[bKey] 143 | if !proto.Equal(aValue, bValue) { 144 | return false 145 | } 146 | } 147 | return true 148 | } 149 | 150 | func snapshotsEqual(a, b cache.Snapshot) bool { 151 | for i := 0; i < len(a.Resources) && i < len(b.Resources); i++ { 152 | aResources := a.Resources[i] 153 | bResources := b.Resources[i] 154 | 155 | if !resourcesEqual(aResources, bResources) { 156 | return false 157 | } 158 | } 159 | return true 160 | } 161 | 162 | type ConfigLoader struct { 163 | Cache cache.SnapshotCache 164 | version uint 165 | 166 | // Protects cache and version 167 | sync.Mutex 168 | } 169 | 170 | type validatableProto interface { 171 | proto.Message 172 | Validate() error 173 | } 174 | 175 | func (c *ConfigLoader) validateProtos(protos []proto.Message) error { 176 | for _, proto := range protos { 177 | validatable, ok := proto.(validatableProto) 178 | logger.Debugf("validating: %+v", proto) 179 | if !ok { 180 | logger.Debugf("cannot validate: %+v", proto) 181 | continue 182 | } 183 | 184 | if err := validatable.Validate(); err != nil { 185 | return err 186 | } 187 | } 188 | return nil 189 | } 190 | 191 | func (c *ConfigLoader) evalSkycfg(filename string) ([]proto.Message, error) { 192 | config, err := skycfg.Load( 193 | context.Background(), filename, 194 | skycfg.WithGlobals(skycfg.UnstablePredeclaredModules(nil)), 195 | ) 196 | 197 | if err != nil { 198 | return nil, fmt.Errorf("error loading %q: %v", filename, err) 199 | } 200 | 201 | protos, err := config.Main(context.Background()) 202 | if err != nil { 203 | return nil, fmt.Errorf("error evaluating %q: %v", config.Filename(), err) 204 | } 205 | 206 | if err := c.validateProtos(protos); err != nil { 207 | return nil, fmt.Errorf("validation error: %+v", err) 208 | } 209 | 210 | return protos, nil 211 | } 212 | 213 | func (c *ConfigLoader) Load(filename string) error { 214 | protos, err := c.evalSkycfg(filename) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | resourcesByType := resourcesByType(fmt.Sprintf("%d-%d-%d", rand.Int(), os.Getpid(), c.version), protos) 220 | 221 | c.Lock() 222 | defer c.Unlock() 223 | oldSnapshot, _ := c.Cache.GetSnapshot(node) 224 | snapshot := oldSnapshot 225 | 226 | // Update all changed resources in the new snapshot 227 | for resourceType, oldResources := range oldSnapshot.Resources { 228 | newResources := resourcesByType[resourceType] 229 | if !resourcesEqual(oldResources, newResources) { 230 | snapshot.Resources[resourceType] = newResources 231 | } 232 | } 233 | 234 | if err := snapshot.Consistent(); err != nil { 235 | return fmt.Errorf("snapshot not consistent: %+v", err) 236 | } 237 | 238 | // Only send a config update if we've actually changed anything 239 | if snapshotsEqual(snapshot, oldSnapshot) { 240 | logger.Printf("[skycfg] skipping update as all resources are equal") 241 | return nil 242 | } 243 | 244 | c.Cache.SetSnapshot(node, snapshot) 245 | c.version++ 246 | return nil 247 | } 248 | 249 | func main() { 250 | flag.Parse() 251 | 252 | argv := flag.Args() 253 | 254 | logrusLevel, err := log.ParseLevel(*level) 255 | if err != nil { 256 | logger.Fatalf("could not set log level to %q: %+v", *level, err) 257 | } 258 | logger.SetLevel(logrusLevel) 259 | 260 | if len(argv) != 1 { 261 | fmt.Fprintf(os.Stderr, `Demo Envoy CLI for Skycfg, a library for building complex typed configs. 262 | 263 | usage: %s FILENAME 264 | `, os.Args[0]) 265 | os.Exit(1) 266 | } 267 | 268 | rand.Seed(time.Now().UnixNano()) 269 | 270 | filename := argv[0] 271 | 272 | h := nodeHashFunc(func(_ *core.Node) string { 273 | return node 274 | }) 275 | c := cache.NewSnapshotCache(true, h, logger) 276 | 277 | loader := &ConfigLoader{ 278 | Cache: c, 279 | } 280 | 281 | if err := loader.Load(filename); err != nil { 282 | logger.Fatalf("%+v", err) 283 | } 284 | 285 | server := server.NewServer(context.Background(), c, cbFuncs) 286 | 287 | lis, err := net.Listen("tcp", *addr) 288 | if err != nil { 289 | logger.Fatalf("failed to listen: %v", err) 290 | } 291 | 292 | logger.Infof("Starting server on %s", *addr) 293 | grpcServer := grpc.NewServer() 294 | 295 | discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, server) 296 | health_pb.RegisterHealthServer(grpcServer, health.NewServer()) 297 | 298 | sigCh := make(chan os.Signal, 1) 299 | signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGSTOP) 300 | 301 | go func(sigCh chan os.Signal) { 302 | for sig := range sigCh { 303 | switch sig { 304 | case syscall.SIGHUP: 305 | if err := loader.Load(filename); err != nil { 306 | logger.Errorf("%+v", err) 307 | } 308 | case syscall.SIGINT, syscall.SIGTERM, syscall.SIGSTOP: 309 | logger.Warnf("caught signal %v, exiting", sig) 310 | grpcServer.Stop() 311 | default: 312 | } 313 | } 314 | }(sigCh) 315 | 316 | if err := grpcServer.Serve(lis); err != nil { 317 | logger.Fatalf("%+v", err) 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /go/protomodule/protomodule.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Skycfg Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package protomodule defines a Starlark module of Protobuf-related functions. 18 | package protomodule 19 | 20 | import ( 21 | "fmt" 22 | 23 | "go.starlark.net/starlark" 24 | "go.starlark.net/starlarkstruct" 25 | "google.golang.org/protobuf/encoding/protojson" 26 | "google.golang.org/protobuf/encoding/prototext" 27 | "google.golang.org/protobuf/proto" 28 | "google.golang.org/protobuf/reflect/protoreflect" 29 | "google.golang.org/protobuf/reflect/protoregistry" 30 | any_pb "google.golang.org/protobuf/types/known/anypb" 31 | ) 32 | 33 | // NewModule returns a Starlark module of Protobuf-related functions. 34 | // 35 | // proto = module( 36 | // clear, 37 | // clone, 38 | // decode_any, 39 | // decode_json, 40 | // decode_text, 41 | // encode_any, 42 | // encode_json, 43 | // encode_text, 44 | // merge, 45 | // set_defaults, 46 | // ) 47 | // 48 | // See `docs/modules.asciidoc` for details on the API of each function. 49 | func NewModule(registry *protoregistry.Types) *starlarkstruct.Module { 50 | return &starlarkstruct.Module{ 51 | Name: "proto", 52 | Members: starlark.StringDict{ 53 | "clear": starlarkClear, 54 | "clone": starlarkClone, 55 | "decode_any": decodeAny(registry), 56 | "decode_json": decodeJSON(registry), 57 | "decode_text": decodeText(registry), 58 | "encode_any": starlarkEncodeAny, 59 | "encode_json": encodeJSON(registry), 60 | "encode_text": encodeText(registry), 61 | "merge": starlarkMerge, 62 | "package": starlarkPackageFn(registry), 63 | "set_defaults": starlarkSetDefaults, 64 | }, 65 | } 66 | } 67 | 68 | var starlarkClear = starlark.NewBuiltin("proto.clear", func( 69 | t *starlark.Thread, 70 | fn *starlark.Builtin, 71 | args starlark.Tuple, 72 | kwargs []starlark.Tuple, 73 | ) (starlark.Value, error) { 74 | _, skyProtoMsg, err := wantSingleProtoMessage(fn, args, kwargs) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | err = skyProtoMsg.Clear() 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | return skyProtoMsg, nil 85 | }) 86 | 87 | var starlarkClone = starlark.NewBuiltin("proto.clone", func( 88 | t *starlark.Thread, 89 | fn *starlark.Builtin, 90 | args starlark.Tuple, 91 | kwargs []starlark.Tuple, 92 | ) (starlark.Value, error) { 93 | msg, _, err := wantSingleProtoMessage(fn, args, kwargs) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return NewMessage(proto.Clone(msg)) 98 | }) 99 | 100 | func decodeAny(registry *protoregistry.Types) starlark.Callable { 101 | return starlark.NewBuiltin("proto.decode_any", func( 102 | t *starlark.Thread, 103 | fn *starlark.Builtin, 104 | args starlark.Tuple, 105 | kwargs []starlark.Tuple, 106 | ) (starlark.Value, error) { 107 | protoMsg, skyProtoMsg, err := wantSingleProtoMessage(fn, args, kwargs) 108 | if err != nil { 109 | return nil, err 110 | } 111 | anyMsg, ok := protoMsg.(*any_pb.Any) 112 | if !ok { 113 | return nil, fmt.Errorf("%s: for parameter 1: got %s, want google.protobuf.Any", fn.Name(), skyProtoMsg.Type()) 114 | } 115 | 116 | decoded, err := any_pb.UnmarshalNew(anyMsg, proto.UnmarshalOptions{ 117 | Resolver: registry, 118 | }) 119 | if err != nil { 120 | return nil, err 121 | } 122 | return NewMessage(decoded) 123 | }) 124 | } 125 | 126 | func decodeJSON(registry *protoregistry.Types) starlark.Callable { 127 | return starlark.NewBuiltin("proto.decode_json", func( 128 | t *starlark.Thread, 129 | fn *starlark.Builtin, 130 | args starlark.Tuple, 131 | kwargs []starlark.Tuple, 132 | ) (starlark.Value, error) { 133 | var msgType starlark.Value 134 | var value starlark.String 135 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &msgType, &value); err != nil { 136 | return nil, err 137 | } 138 | protoMsgType, ok := msgType.(skyProtoMessageType) 139 | if !ok { 140 | return nil, fmt.Errorf("%s: for parameter 1: got %s, want proto.MessageType", fn.Name(), msgType.Type()) 141 | } 142 | 143 | unmarshal := protojson.UnmarshalOptions{ 144 | Resolver: registry, 145 | } 146 | decoded := protoMsgType.NewMessage() 147 | if err := unmarshal.Unmarshal([]byte(value), decoded); err != nil { 148 | return nil, err 149 | } 150 | return NewMessage(decoded) 151 | }) 152 | } 153 | 154 | func decodeText(registry *protoregistry.Types) starlark.Callable { 155 | return starlark.NewBuiltin("proto.decode_text", func( 156 | t *starlark.Thread, 157 | fn *starlark.Builtin, 158 | args starlark.Tuple, 159 | kwargs []starlark.Tuple, 160 | ) (starlark.Value, error) { 161 | var msgType starlark.Value 162 | var value starlark.String 163 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &msgType, &value); err != nil { 164 | return nil, err 165 | } 166 | protoMsgType, ok := msgType.(skyProtoMessageType) 167 | if !ok { 168 | return nil, fmt.Errorf("%s: for parameter 1: got %s, want proto.MessageType", fn.Name(), msgType.Type()) 169 | } 170 | 171 | unmarshal := prototext.UnmarshalOptions{ 172 | Resolver: registry, 173 | } 174 | decoded := protoMsgType.NewMessage() 175 | if err := unmarshal.Unmarshal([]byte(value), decoded); err != nil { 176 | return nil, err 177 | } 178 | return NewMessage(decoded) 179 | }) 180 | } 181 | 182 | var starlarkEncodeAny = starlark.NewBuiltin("proto.encode_any", func( 183 | t *starlark.Thread, 184 | fn *starlark.Builtin, 185 | args starlark.Tuple, 186 | kwargs []starlark.Tuple, 187 | ) (starlark.Value, error) { 188 | protoMsg, _, err := wantSingleProtoMessage(fn, args, kwargs) 189 | if err != nil { 190 | return nil, err 191 | } 192 | any := &any_pb.Any{} 193 | if err := any_pb.MarshalFrom(any, protoMsg, proto.MarshalOptions{ 194 | Deterministic: true, 195 | }); err != nil { 196 | return nil, err 197 | } 198 | 199 | return NewMessage(any) 200 | }) 201 | 202 | func encodeJSON(registry *protoregistry.Types) starlark.Callable { 203 | return starlark.NewBuiltin("proto.encode_json", func( 204 | t *starlark.Thread, 205 | fn *starlark.Builtin, 206 | args starlark.Tuple, 207 | kwargs []starlark.Tuple, 208 | ) (starlark.Value, error) { 209 | protoMsg, _, err := wantSingleProtoMessage(fn, args, nil) 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | marshal := protojson.MarshalOptions{ 215 | UseProtoNames: true, 216 | Resolver: registry, 217 | } 218 | 219 | if len(kwargs) > 0 { 220 | compact := true 221 | if err := starlark.UnpackArgs(fn.Name(), nil, kwargs, "compact", &compact); err != nil { 222 | return nil, err 223 | } 224 | if !compact { 225 | marshal.Multiline = true 226 | } 227 | } 228 | jsonData, err := marshal.Marshal(protoMsg) 229 | if err != nil { 230 | return nil, err 231 | } 232 | return starlark.String(jsonData), nil 233 | }) 234 | } 235 | 236 | func encodeText(registry *protoregistry.Types) starlark.Callable { 237 | return starlark.NewBuiltin("proto.encode_text", func( 238 | t *starlark.Thread, 239 | fn *starlark.Builtin, 240 | args starlark.Tuple, 241 | kwargs []starlark.Tuple, 242 | ) (starlark.Value, error) { 243 | protoMsg, _, err := wantSingleProtoMessage(fn, args, nil) 244 | if err != nil { 245 | return nil, err 246 | } 247 | 248 | marshal := prototext.MarshalOptions{ 249 | Resolver: registry, 250 | } 251 | 252 | if len(kwargs) > 0 { 253 | compact := true 254 | if err := starlark.UnpackArgs(fn.Name(), nil, kwargs, "compact", &compact); err != nil { 255 | return nil, err 256 | } 257 | if !compact { 258 | marshal.Multiline = true 259 | } 260 | } 261 | text, err := marshal.Marshal(protoMsg) 262 | if err != nil { 263 | return nil, err 264 | } 265 | return starlark.String(text), nil 266 | }) 267 | } 268 | 269 | var starlarkMerge = starlark.NewBuiltin("proto.merge", func( 270 | t *starlark.Thread, 271 | fn *starlark.Builtin, 272 | args starlark.Tuple, 273 | kwargs []starlark.Tuple, 274 | ) (starlark.Value, error) { 275 | var val1, val2 starlark.Value 276 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &val1, &val2); err != nil { 277 | return nil, err 278 | } 279 | 280 | dst := val1.(*protoMessage) 281 | src := val2.(*protoMessage) 282 | if src.Type() != dst.Type() { 283 | return nil, fmt.Errorf("%s: types are not the same: got %s and %s", fn.Name(), src.Type(), dst.Type()) 284 | } 285 | 286 | err := dst.Merge(src) 287 | if err != nil { 288 | return nil, err 289 | } 290 | 291 | return dst, nil 292 | }) 293 | 294 | var starlarkSetDefaults = starlark.NewBuiltin("proto.set_defaults", func( 295 | t *starlark.Thread, 296 | fn *starlark.Builtin, 297 | args starlark.Tuple, 298 | kwargs []starlark.Tuple, 299 | ) (starlark.Value, error) { 300 | _, skyProtoMsg, err := wantSingleProtoMessage(fn, args, kwargs) 301 | if err != nil { 302 | return nil, err 303 | } 304 | 305 | err = skyProtoMsg.SetDefaults() 306 | if err != nil { 307 | return nil, err 308 | } 309 | 310 | return skyProtoMsg, nil 311 | }) 312 | 313 | type skyProtoMessageType interface { 314 | NewMessage() protoreflect.ProtoMessage 315 | } 316 | 317 | type skyProtoMessage interface { 318 | starlark.Value 319 | MarshalJSON() ([]byte, error) 320 | Clear() error 321 | Merge(*protoMessage) error 322 | SetDefaults() error 323 | CheckMutable(string) error 324 | } 325 | 326 | func wantSingleProtoMessage( 327 | fn *starlark.Builtin, 328 | args starlark.Tuple, 329 | kwargs []starlark.Tuple, 330 | ) (proto.Message, skyProtoMessage, error) { 331 | var val starlark.Value 332 | if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &val); err != nil { 333 | return nil, nil, err 334 | } 335 | gotMsg, ok := AsProtoMessage(val) 336 | if !ok { 337 | return nil, nil, fmt.Errorf("%s: for parameter 1: got %s, want proto.Message", fn.Name(), val.Type()) 338 | } 339 | return gotMsg, val.(skyProtoMessage), nil 340 | } 341 | --------------------------------------------------------------------------------