├── .bazelrc ├── .bazelversion ├── .gitattributes ├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── AUTHORS ├── BUILD.bazel ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── WORKSPACE ├── _examples ├── envoy │ ├── address.cfg │ ├── cluster.cfg │ ├── envoy.cfg │ ├── envoy.yaml │ ├── go.mod │ ├── go.sum │ ├── listener.cfg │ ├── main.go │ └── start-envoy.sh ├── k8s │ ├── app.cfg │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── nginx.cfg ├── repl │ ├── go.mod │ ├── go.sum │ └── main.go └── wasm │ ├── README.md │ ├── addressbook.proto │ ├── addressbook │ └── addressbook.pb.go │ ├── demo.gif │ ├── go.mod │ ├── go.sum │ ├── index.html │ ├── main.go │ └── wasm_exec.js ├── build ├── BUILD ├── go_dependencies.bzl ├── go_version.bzl └── patches │ └── protobuf_b8100.patch ├── docs ├── modules.asciidoc └── protobuf.asciidoc ├── export_test.go ├── go.mod ├── go.sum ├── go ├── assertmodule │ ├── BUILD │ ├── assert.go │ ├── assert_test.go │ └── fail.go ├── hashmodule │ ├── BUILD │ ├── hashmodule.go │ └── hashmodule_test.go ├── protomodule │ ├── BUILD │ ├── merge.go │ ├── protomodule.go │ ├── protomodule_enum.go │ ├── protomodule_list.go │ ├── protomodule_map.go │ ├── protomodule_message.go │ ├── protomodule_message_test.go │ ├── protomodule_message_type.go │ ├── protomodule_package.go │ ├── protomodule_test.go │ └── type_conversions.go ├── urlmodule │ ├── BUILD │ ├── urlmodule.go │ └── urlmodule_test.go └── yamlmodule │ ├── BUILD │ ├── json_write.go │ ├── yamlmodule.go │ └── yamlmodule_test.go ├── internal └── test_proto │ ├── BUILD │ ├── package.go │ ├── test_proto_v2.proto │ └── test_proto_v3.proto ├── skycfg.go └── skycfg_test.go /.bazelrc: -------------------------------------------------------------------------------- 1 | common --noenable_bzlmod 2 | build --incompatible_strict_action_env 3 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.4.1 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sky linguist-language=Python 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: "Tests" 2 | on: ["push", "pull_request"] 3 | 4 | jobs: 5 | tests: 6 | name: "Go ${{ matrix.go.series }}" 7 | runs-on: "ubuntu-24.04" 8 | container: 9 | image: "docker://gcr.io/bazel-public/bazel:7.4.1" 10 | options: --user root 11 | 12 | strategy: 13 | matrix: 14 | go: 15 | # Keep minimum version up to date with go.mod 16 | - series: "1.22" 17 | version: "1.22.12" 18 | - series: "1.23" 19 | version: "1.23.8" 20 | - series: "1.24" 21 | version: "1.24.2" 22 | 23 | steps: 24 | - uses: "actions/checkout@v4" 25 | with: 26 | path: "src" 27 | - uses: "actions/cache@v4" 28 | with: 29 | path: cache 30 | key: "go=${{ matrix.go.version }};v=1" 31 | - name: "Prepare workspace" 32 | run: | 33 | echo "GO_VERSION = '${GO_VERSION}'" > src/build/go_version.bzl 34 | env: 35 | GO_VERSION: ${{ matrix.go.version }} 36 | - name: "bazel test //..." 37 | working-directory: src 38 | run: | 39 | exec bazel test \ 40 | --announce_rc \ 41 | --curses=no \ 42 | --color=yes \ 43 | --verbose_failures \ 44 | --test_output=errors \ 45 | --test_verbose_timeout_warnings \ 46 | --repository_cache="$GITHUB_WORKSPACE/cache/repository_cache" \ 47 | --disk_cache="$GITHUB_WORKSPACE/cache/disk_cache" \ 48 | //... 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-* 2 | *.pb.go 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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,BUILD.bazel 6 | # gazelle:go_naming_convention import 7 | 8 | # gazelle:exclude _examples 9 | 10 | go_library( 11 | name = "skycfg", 12 | srcs = ["skycfg.go"], 13 | importpath = "github.com/stripe/skycfg", 14 | visibility = ["//visibility:public"], 15 | deps = [ 16 | "//go/assertmodule", 17 | "//go/hashmodule", 18 | "//go/protomodule", 19 | "//go/urlmodule", 20 | "//go/yamlmodule", 21 | "@net_starlark_go//starlark", 22 | "@net_starlark_go//starlarkjson", 23 | "@net_starlark_go//starlarkstruct", 24 | "@org_golang_google_protobuf//proto", 25 | "@org_golang_google_protobuf//reflect/protoreflect", 26 | "@org_golang_google_protobuf//reflect/protoregistry", 27 | ], 28 | ) 29 | 30 | go_test( 31 | name = "skycfg_test", 32 | size = "small", 33 | srcs = [ 34 | "export_test.go", 35 | "skycfg_test.go", 36 | ], 37 | embed = [":skycfg"], 38 | deps = [ 39 | "//internal/test_proto:test_proto_go_proto", 40 | "@net_starlark_go//starlark", 41 | "@org_golang_google_protobuf//proto", 42 | "@org_golang_google_protobuf//types/known/wrapperspb", 43 | ], 44 | ) 45 | 46 | gazelle(name = "gazelle") 47 | 48 | gazelle( 49 | name = "gazelle-update-repos", 50 | args = [ 51 | "-from_file=go.mod", 52 | "-to_macro=build/go_dependencies.bzl%go_dependencies", 53 | "-prune", 54 | ], 55 | command = "update-repos", 56 | ) 57 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | $ bazel run //:gazelle-update-repos # if there are go.mod changes 175 | ``` 176 | 177 | However, the `go` toolchain is unofficially supported as well. To get started: 178 | 179 | ```bash 180 | $ go install google.golang.org/protobuf/cmd/protoc-gen-go 181 | $ go generate ./... 182 | $ go test ./... 183 | ``` 184 | 185 | ## Stability 186 | 187 | 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. 188 | 189 | 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*`. 190 | 191 | ## Known issues 192 | 193 | ### gogo/protobuf support 194 | 195 | Skycfg has dropped support for 196 | [gogo/protobuf](https://github.com/gogo/protobuf), an alternative protobuf go 197 | code generator, when upgrading to using the go-protobuf v2 api. For using gogo 198 | support, see `Skycfg@v0.1.0` (Skycfg prior to switching to v2). 199 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "com_github_stripe_skycfg") 2 | 3 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 4 | 5 | http_archive( 6 | name = "bazel_skylib", 7 | sha256 = "e3fea03ff75a9821e84199466799ba560dbaebb299c655b5307f4df1e5970696", 8 | strip_prefix = "bazel-skylib-1.7.1", 9 | urls = ["https://github.com/bazelbuild/bazel-skylib/archive/refs/tags/1.7.1.tar.gz"], 10 | ) 11 | 12 | http_archive( 13 | name = "io_bazel_rules_go", 14 | sha256 = "80a98277ad1311dacd837f9b16db62887702e9f1d1c4c9f796d0121a46c8e184", 15 | urls = [ 16 | "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.46.0/rules_go-v0.46.0.zip", 17 | "https://github.com/bazelbuild/rules_go/releases/download/v0.46.0/rules_go-v0.46.0.zip", 18 | ], 19 | ) 20 | 21 | load( 22 | "@io_bazel_rules_go//go:deps.bzl", 23 | "go_register_toolchains", 24 | "go_rules_dependencies", 25 | ) 26 | 27 | go_rules_dependencies() 28 | 29 | load("//build:go_version.bzl", "GO_VERSION") 30 | 31 | go_register_toolchains(go_version = GO_VERSION) 32 | 33 | http_archive( 34 | name = "bazel_gazelle", 35 | sha256 = "aefbf2fc7c7616c9ed73aa3d51c77100724d5b3ce66cfa16406e8c13e87c8b52", 36 | urls = [ 37 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.41.0/bazel-gazelle-v0.41.0.tar.gz", 38 | "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.41.0/bazel-gazelle-v0.41.0.tar.gz", 39 | ], 40 | ) 41 | 42 | load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") 43 | 44 | gazelle_dependencies() 45 | 46 | http_archive( 47 | name = "com_google_protobuf", 48 | patches = [ 49 | "//build:patches/protobuf_b8100.patch", 50 | ], 51 | sha256 = "6dd0f6b20094910fbb7f1f7908688df01af2d4f6c5c21331b9f636048674aebf", 52 | strip_prefix = "protobuf-3.14.0", 53 | urls = [ 54 | "https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protobuf-all-3.14.0.tar.gz", 55 | ], 56 | ) 57 | 58 | http_archive( 59 | name = "rules_proto", 60 | sha256 = "dc3fb206a2cb3441b485eb1e423165b231235a1ea9b031b4433cf7bc1fa460dd", 61 | strip_prefix = "rules_proto-5.3.0-21.7", 62 | urls = [ 63 | "https://github.com/bazelbuild/rules_proto/archive/refs/tags/5.3.0-21.7.tar.gz", 64 | ], 65 | ) 66 | 67 | load( 68 | "@rules_proto//proto:repositories.bzl", 69 | "rules_proto_dependencies", 70 | "rules_proto_toolchains", 71 | ) 72 | 73 | rules_proto_dependencies() 74 | 75 | rules_proto_toolchains() 76 | 77 | # gazelle:repository_macro build/go_dependencies.bzl%go_dependencies 78 | load("//build:go_dependencies.bzl", "go_dependencies") 79 | 80 | go_dependencies() 81 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_examples/envoy/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= 4 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 5 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 6 | github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 7 | github.com/cncf/udpa/go v0.0.0-20200909154343-1f710aca26a9 h1:cQ58MWbYGnI4x6Gk6FUzirMcMYUgvYOLa9fiO7chY1A= 8 | github.com/cncf/udpa/go v0.0.0-20200909154343-1f710aca26a9/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 13 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 14 | github.com/envoyproxy/go-control-plane v0.9.6 h1:GgblEiDzxf5ajlAZY4aC8xp7DwkrGfauFNMGdB2bBv0= 15 | github.com/envoyproxy/go-control-plane v0.9.6/go.mod h1:GFqM7v0B62MraO4PWRedIbhThr/Rf7ev6aHOOPXeaDA= 16 | github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= 17 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 18 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 19 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 20 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 21 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 22 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 23 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/golang/protobuf v1.3.2/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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 31 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 32 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 33 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 34 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 35 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 36 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 37 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 38 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 39 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 40 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 41 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 42 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 46 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 47 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 48 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 49 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 52 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 53 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 54 | github.com/stripe/skycfg v0.0.0-20200930043820-677b39f98fc6 h1:xm6W/85t2nySlyiIu147WczipGCb8Vz6YjZenlGZ+g4= 55 | github.com/stripe/skycfg v0.0.0-20200930043820-677b39f98fc6/go.mod h1:51M7cjklIBDknKJUVGuR9AnTpfk8IZQJDhxr1Yq7bb0= 56 | go.starlark.net v0.0.0-20190604130855-6ddc71c0ba77 h1:KPzANX1mXqnSWenqVWkSTsQWiaUSpTY5GyGZKI6lStw= 57 | go.starlark.net v0.0.0-20190604130855-6ddc71c0ba77/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= 58 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 59 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 60 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 61 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 62 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 63 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 64 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 65 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 66 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 67 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 68 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 69 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 70 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 71 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 72 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 73 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 74 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 75 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 77 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 78 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= 79 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 80 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 81 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 82 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 83 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 84 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 85 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 86 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 87 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 88 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 89 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 90 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 91 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 92 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 93 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 94 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 95 | google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= 96 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 97 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 98 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 99 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 100 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 101 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 102 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 103 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 104 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 105 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 106 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 107 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 108 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 109 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 110 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 111 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 112 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 113 | -------------------------------------------------------------------------------- /_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/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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_examples/k8s/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 | "errors" 23 | "flag" 24 | "fmt" 25 | "io/ioutil" 26 | "net/http" 27 | "os" 28 | "path" 29 | "strings" 30 | 31 | "github.com/gogo/protobuf/jsonpb" 32 | gogo_proto "github.com/gogo/protobuf/proto" 33 | "github.com/golang/protobuf/proto" 34 | yaml "gopkg.in/yaml.v2" 35 | 36 | apierrors "k8s.io/apimachinery/pkg/api/errors" 37 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 38 | "k8s.io/apimachinery/pkg/runtime" 39 | "k8s.io/apimachinery/pkg/runtime/schema" 40 | "k8s.io/client-go/discovery" 41 | "k8s.io/client-go/rest" 42 | "k8s.io/client-go/restmapper" 43 | "k8s.io/client-go/tools/clientcmd" 44 | 45 | _ "k8s.io/api/apps/v1" 46 | _ "k8s.io/api/batch/v1" 47 | _ "k8s.io/api/core/v1" 48 | _ "k8s.io/api/storage/v1" 49 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 50 | 51 | "github.com/stripe/skycfg" 52 | "github.com/stripe/skycfg/gogocompat" 53 | ) 54 | 55 | var ( 56 | dryRun = flag.Bool("dry_run", false, "Print objects rendered by Skycfg and don't do anything.") 57 | namespace = flag.String("namespace", "default", "Namespace to create/delete objects in.") 58 | configPath = flag.String("kubeconfig", os.Getenv("HOME")+"/.kube/config", "Kubernetes client config path.") 59 | ) 60 | 61 | var k8sProtoMagic = []byte("k8s\x00") 62 | 63 | // marshal wraps msg into runtime.Unknown object and prepends magic sequence 64 | // to conform with Kubernetes protobuf content type. 65 | func marshal(msg proto.Message) ([]byte, error) { 66 | msgBytes, err := proto.Marshal(msg) 67 | if err != nil { 68 | return nil, err 69 | } 70 | unknownBytes, err := proto.Marshal(&runtime.Unknown{Raw: msgBytes}) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return append(k8sProtoMagic, unknownBytes...), nil 75 | } 76 | 77 | // pathForResource returns absolute URL path to access a gvr under a namespace. 78 | func pathForResource(namespace string, gvr schema.GroupVersionResource) string { 79 | return pathForResourceWithName("", namespace, gvr) 80 | } 81 | 82 | // pathForResourceWithName is same as pathForResource but with name segment. 83 | func pathForResourceWithName(name, namespace string, gvr schema.GroupVersionResource) string { 84 | segments := []string{"/apis"} 85 | 86 | if gvr.Group == "" { 87 | segments = []string{"/api", gvr.Version} 88 | } else { 89 | segments = append(segments, gvr.Group, gvr.Version) 90 | } 91 | 92 | if namespace != "" { 93 | segments = append(segments, "namespaces", namespace) 94 | } 95 | 96 | if gvr.Resource != "" { // Skip explicit resource for namespaces. 97 | segments = append(segments, gvr.Resource) 98 | } 99 | 100 | if name != "" { 101 | segments = append(segments, name) 102 | } 103 | 104 | return path.Join(segments...) 105 | } 106 | 107 | // k8sAPIPrefix is a prefix for all Kubernetes types. 108 | const k8sAPIPrefix = "k8s.io.api." 109 | 110 | // gvkFromMsgType assembles schema.GroupVersionKind based on Protobuf 111 | // message type. 112 | // e.g "k8s.io.api.core.v1.Pod" turned into "", "v1", "Pod". 113 | // Returns error if message is not prefixed with k8sAPIPerfix. 114 | func gvkFromMsgType(m proto.Message) (group, version, kind string, err error) { 115 | t := gogo_proto.MessageName(m) 116 | if !strings.HasPrefix(t, k8sAPIPrefix) { 117 | err = errors.New("unexpected message type: " + t) 118 | return 119 | } 120 | ss := strings.Split(t[len(k8sAPIPrefix):], ".") 121 | if ss[0] == "core" { // Is there a better way? 122 | ss[0] = "" 123 | } 124 | group, version, kind = ss[0], ss[1], ss[2] 125 | return 126 | } 127 | 128 | type kube struct { 129 | dClient discovery.DiscoveryInterface 130 | httpClient *http.Client 131 | // host:port of the master. 132 | Master string 133 | dryRun bool 134 | } 135 | 136 | // resourceForMsg extract type information from msg and discovers appropriate 137 | // gvr for it using discovery client. 138 | func (k *kube) resourceForMsg(msg proto.Message) (*schema.GroupVersionResource, error) { 139 | g, v, kind, err := gvkFromMsgType(msg) 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | gr, err := restmapper.GetAPIGroupResources(k.dClient) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | mapping, err := restmapper.NewDiscoveryRESTMapper(gr).RESTMapping(schema.GroupKind{g, kind}, v) 150 | if err != nil { 151 | return nil, err 152 | } 153 | return &mapping.Resource, nil 154 | } 155 | 156 | // parseResponse parses response body to extract unstructred.Unstructured 157 | // and extracts http error code. 158 | // Returns status message on success and error on failure (includes HTTP 159 | // response codes not in 2XX). 160 | func parseResponse(r *http.Response) (status string, err error) { 161 | raw, err := ioutil.ReadAll(r.Body) 162 | if err != nil { 163 | return "", fmt.Errorf("failed to read body (response code: %d): %v", r.StatusCode, err) 164 | } 165 | 166 | obj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, raw) 167 | if err != nil { 168 | return "", fmt.Errorf("failed to parse Status (response code: %d): %v", r.StatusCode, err) 169 | } 170 | 171 | if r.StatusCode < 200 || r.StatusCode >= 300 { 172 | return "", fmt.Errorf("%s (response code: %d)", apierrors.FromObject(obj).Error(), r.StatusCode) 173 | } 174 | 175 | un := obj.(*unstructured.Unstructured) 176 | gvk := un.GroupVersionKind() 177 | 178 | if gvk.Kind == "Status" { 179 | ds, found, err := unstructured.NestedStringMap(un.Object, "details") 180 | if err != nil { 181 | return "", err 182 | } 183 | if !found { 184 | return "", errors.New("`details' map is missing from Status") 185 | } 186 | return fmt.Sprintf("%s.%s `%s'", ds["kind"], ds["group"], ds["name"]), nil 187 | } 188 | 189 | return fmt.Sprintf("%s.%s `%s'", strings.ToLower(gvk.Kind), gvk.Group, un.GetName()), nil 190 | } 191 | 192 | // Up creates msg object in Kubernetes. Determines the path based on msg 193 | // registered type. Object by the same path must not already exist. 194 | func (k *kube) Up(ctx context.Context, namespace string, msg proto.Message) (string, error) { 195 | gvr, err := k.resourceForMsg(msg) 196 | if err != nil { 197 | return "", err 198 | } 199 | uri := pathForResource(namespace, *gvr) 200 | bs, err := marshal(msg) 201 | if err != nil { 202 | return "", err 203 | } 204 | 205 | url := k.Master + uri 206 | if k.dryRun { 207 | return "POST to " + url, nil 208 | } 209 | 210 | req, err := http.NewRequest("POST", url, bytes.NewReader(bs)) 211 | 212 | // NB: Will not work for CRDs (only json encoding is supported). 213 | // Set body type as marshalled Protobuf. 214 | req.Header.Set("Content-Type", "application/vnd.kubernetes.protobuf") 215 | // Set reply type to accept Protobuf as well. 216 | //req.Header.Set("Accept", "application/vnd.kubernetes.protobuf") 217 | 218 | resp, err := k.httpClient.Do(req.WithContext(ctx)) 219 | if err != nil { 220 | return "", err 221 | } 222 | 223 | rMsg, err := parseResponse(resp) 224 | if err != nil { 225 | return "", err 226 | } 227 | return rMsg + " created", nil 228 | } 229 | 230 | // Down deletes object name in namespace. Resource is computed based on msg 231 | // registered type. 232 | func (k *kube) Down(ctx context.Context, name, namespace string, msg proto.Message) (string, error) { 233 | gvr, err := k.resourceForMsg(msg) 234 | if err != nil { 235 | return "", err 236 | } 237 | 238 | url := k.Master + pathForResourceWithName(name, namespace, *gvr) 239 | if k.dryRun { 240 | return "POST to " + url, nil 241 | } 242 | 243 | req, err := http.NewRequest("DELETE", url, nil) 244 | 245 | resp, err := k.httpClient.Do(req.WithContext(ctx)) 246 | if err != nil { 247 | return "", err 248 | } 249 | 250 | rMsg, err := parseResponse(resp) 251 | if err != nil { 252 | return "", err 253 | } 254 | return rMsg + " deleted", nil 255 | } 256 | 257 | func main() { 258 | flag.Parse() 259 | argv := flag.Args() 260 | 261 | if len(argv) != 3 { 262 | fmt.Fprintf(os.Stderr, `Demo Kubernetes CLI for Skycfg, a library for building complex typed configs. 263 | 264 | usage: %s [ up | down ] NAME FILENAME 265 | `, os.Args[0]) 266 | os.Exit(1) 267 | } 268 | action, name, filename := argv[0], argv[1], argv[2] 269 | 270 | restConfig, err := clientcmd.BuildConfigFromFlags("", *configPath) 271 | if err != nil { 272 | fmt.Fprintf(os.Stderr, "failed to build rest config: %v\n", err) 273 | os.Exit(1) 274 | } 275 | dc := discovery.NewDiscoveryClientForConfigOrDie(restConfig) 276 | t, err := rest.TransportFor(restConfig) 277 | if err != nil { 278 | fmt.Fprintf(os.Stderr, "failed to build HTTP roundtripper: %v\n", err) 279 | os.Exit(1) 280 | } 281 | k := &kube{ 282 | httpClient: &http.Client{ 283 | Transport: t, 284 | }, 285 | Master: restConfig.Host, 286 | dClient: dc, 287 | dryRun: *dryRun, 288 | } 289 | 290 | config, err := skycfg.Load(context.Background(), filename, skycfg.WithProtoRegistry(gogocompat.ProtoRegistry())) 291 | if err != nil { 292 | fmt.Fprintf(os.Stderr, "error loading %q: %v\n", filename, err) 293 | os.Exit(1) 294 | } 295 | 296 | var jsonMarshaler = &jsonpb.Marshaler{OrigName: true} 297 | protos, err := config.Main(context.Background()) 298 | if err != nil { 299 | fmt.Fprintf(os.Stderr, "error evaluating %q: %v\n", config.Filename(), err) 300 | os.Exit(1) 301 | } 302 | for _, msg := range protos { 303 | ctx := context.Background() 304 | var err error 305 | var reply string 306 | switch action { 307 | case "up": 308 | reply, err = k.Up(ctx, *namespace, msg) 309 | case "down": 310 | reply, err = k.Down(ctx, name, *namespace, msg) 311 | default: 312 | panic("Unknown action: " + action) 313 | } 314 | 315 | if err != nil { 316 | fmt.Fprintf(os.Stderr, "%v\n", err) 317 | } else { 318 | fmt.Printf("%s\n", reply) 319 | } 320 | 321 | if *dryRun { 322 | marshaled, err := jsonMarshaler.MarshalToString(msg) 323 | sep := "" 324 | if err != nil { 325 | fmt.Fprintf(os.Stderr, "json.Marshal: %v\n", err) 326 | continue 327 | } 328 | var yamlMap yaml.MapSlice 329 | if err := yaml.Unmarshal([]byte(marshaled), &yamlMap); err != nil { 330 | panic(fmt.Sprintf("yaml.Unmarshal: %v", err)) 331 | } 332 | yamlMarshaled, err := yaml.Marshal(yamlMap) 333 | if err != nil { 334 | panic(fmt.Sprintf("yaml.Marshal: %v", err)) 335 | } 336 | marshaled = string(yamlMarshaled) 337 | sep = "---\n" 338 | fmt.Printf("%s%s\n", sep, marshaled) 339 | } 340 | 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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/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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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/wasm/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe/skycfg/0514077398e07b43cf7d7c65c7681e9468578aca/_examples/wasm/demo.gif -------------------------------------------------------------------------------- /_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/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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /build/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe/skycfg/0514077398e07b43cf7d7c65c7681e9468578aca/build/BUILD -------------------------------------------------------------------------------- /build/go_dependencies.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:deps.bzl", "go_repository") 2 | 3 | def go_dependencies(): 4 | go_repository( 5 | name = "com_github_chzyer_logex", 6 | importpath = "github.com/chzyer/logex", 7 | sum = "h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=", 8 | version = "v1.1.10", 9 | ) 10 | go_repository( 11 | name = "com_github_chzyer_readline", 12 | importpath = "github.com/chzyer/readline", 13 | sum = "h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=", 14 | version = "v0.0.0-20180603132655-2972be24d48e", 15 | ) 16 | go_repository( 17 | name = "com_github_chzyer_test", 18 | importpath = "github.com/chzyer/test", 19 | sum = "h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=", 20 | version = "v0.0.0-20180213035817-a1ea475d72b1", 21 | ) 22 | go_repository( 23 | name = "com_github_golang_protobuf", 24 | importpath = "github.com/golang/protobuf", 25 | sum = "h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=", 26 | version = "v1.5.0", 27 | ) 28 | go_repository( 29 | name = "com_github_google_go_cmp", 30 | importpath = "github.com/google/go-cmp", 31 | sum = "h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=", 32 | version = "v0.5.5", 33 | ) 34 | go_repository( 35 | name = "com_github_spaolacci_murmur3", 36 | importpath = "github.com/spaolacci/murmur3", 37 | sum = "h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=", 38 | version = "v1.1.0", 39 | ) 40 | go_repository( 41 | name = "in_gopkg_check_v1", 42 | importpath = "gopkg.in/check.v1", 43 | sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=", 44 | version = "v0.0.0-20161208181325-20d25e280405", 45 | ) 46 | go_repository( 47 | name = "in_gopkg_yaml_v2", 48 | importpath = "gopkg.in/yaml.v2", 49 | sum = "h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=", 50 | version = "v2.2.1", 51 | ) 52 | go_repository( 53 | name = "net_starlark_go", 54 | importpath = "go.starlark.net", 55 | sum = "h1:yHUzJ1WfcdR1oOafytJ6K1/ntYwnEIXICNVzHb+FzbA=", 56 | version = "v0.0.0-20201204201740-42d4f566359b", 57 | ) 58 | go_repository( 59 | name = "org_golang_google_protobuf", 60 | importpath = "google.golang.org/protobuf", 61 | sum = "h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=", 62 | version = "v1.33.0", 63 | ) 64 | go_repository( 65 | name = "org_golang_x_sys", 66 | importpath = "golang.org/x/sys", 67 | sum = "h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=", 68 | version = "v0.1.0", 69 | ) 70 | go_repository( 71 | name = "org_golang_x_xerrors", 72 | importpath = "golang.org/x/xerrors", 73 | sum = "h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=", 74 | version = "v0.0.0-20191204190536-9bdfabe68543", 75 | ) 76 | -------------------------------------------------------------------------------- /build/go_version.bzl: -------------------------------------------------------------------------------- 1 | # This file specifies which version of Go to build and test with. It is 2 | # overridden in CI to implement multi-version testing. 3 | 4 | GO_VERSION = "1.24.2" 5 | -------------------------------------------------------------------------------- /build/patches/protobuf_b8100.patch: -------------------------------------------------------------------------------- 1 | Work around unnecessarily hard dependency on `@rules_python` in Protobuf. 2 | 3 | See https://github.com/protocolbuffers/protobuf/issues/8100 4 | 5 | diff --git BUILD BUILD 6 | index 112432160..8d59109d8 100644 7 | --- BUILD 8 | +++ BUILD 9 | @@ -3,7 +3,6 @@ 10 | load("@bazel_skylib//rules:common_settings.bzl", "string_flag") 11 | load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test", "objc_library", native_cc_proto_library = "cc_proto_library") 12 | load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain", "proto_library") 13 | -load("@rules_python//python:defs.bzl", "py_library") 14 | load(":cc_proto_blacklist_test.bzl", "cc_proto_blacklist_test") 15 | 16 | licenses(["notice"]) 17 | diff --git protobuf.bzl protobuf.bzl 18 | index 12d3edb94..d592a7f4a 100644 19 | --- protobuf.bzl 20 | +++ protobuf.bzl 21 | @@ -1,7 +1,9 @@ 22 | load("@bazel_skylib//lib:versions.bzl", "versions") 23 | load("@rules_cc//cc:defs.bzl", "cc_library") 24 | load("@rules_proto//proto:defs.bzl", "ProtoInfo") 25 | -load("@rules_python//python:defs.bzl", "py_library", "py_test") 26 | + 27 | +def py_library(*args, **kwargs): pass 28 | +def py_test(*args, **kwargs): pass 29 | 30 | def _GetPath(ctx, path): 31 | if ctx.label.workspace_root: 32 | -------------------------------------------------------------------------------- /docs/modules.asciidoc: -------------------------------------------------------------------------------- 1 | = Modules 2 | :sectanchors: 3 | 4 | == hash 5 | 6 | Functions for common hash algorithms. 7 | 8 | Hashing is useful in configuration files that need to generate deterministic 9 | identifiers. For example, a Kubernetes `ConfigMap` might have a name derived 10 | from the hash of its contents. 11 | 12 | Index: 13 | 14 | * `<>` 15 | * `<>` 16 | * `<>` 17 | 18 | === `hash.md5` 19 | [[hash.md5]] 20 | 21 | The https://en.wikipedia.org/wiki/MD5[MD5] hash function. 22 | 23 | >>> hash.md5("hello") 24 | "5d41402abc4b2a76b9719d911017c592" 25 | >>> 26 | 27 | 28 | === `hash.sha1` 29 | [[hash.sha1]] 30 | 31 | The https://en.wikipedia.org/wiki/SHA-1[SHA-1] hash function. 32 | 33 | >>> hash.sha1("hello") 34 | "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d" 35 | >>> 36 | 37 | === `hash.sha256` 38 | [[hash.sha256]] 39 | 40 | The https://en.wikipedia.org/wiki/SHA-2[SHA-256] hash function. 41 | 42 | >>> hash.sha256("hello") 43 | "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" 44 | >>> 45 | 46 | == url 47 | 48 | Functions for constructing https://en.wikipedia.org/wiki/URL[URL]s. 49 | 50 | Index: 51 | 52 | * `<>` 53 | 54 | === `url.encode_query` 55 | [[url.encode_query]] 56 | 57 | Converts a dictionary into a URL 58 | https://en.wikipedia.org/wiki/Query_string[query string], preserving iteration 59 | order. 60 | 61 | >>> url.encode_query({"hello": "a", "world": "b"}) 62 | "hello=a&world=b" 63 | >>> 64 | 65 | == proto 66 | 67 | Functions for constructing, modifying, and encoding 68 | https://en.wikipedia.org/wiki/Protocol_Buffers[Protobuf] messages. 69 | 70 | Index: 71 | 72 | * `<>` 73 | * `<>` 74 | * `<>` 75 | * `<>` 76 | * `<>` 77 | * `<>` 78 | * `<>` 79 | * `<>` 80 | * `<>` 81 | * `<>` 82 | * `<>` 83 | 84 | === `proto.clear` 85 | [[proto.clear]] 86 | 87 | Clears every field of a Protobuf message to an empty state. 88 | 89 | >>> pb = proto.package("google.protobuf") 90 | >>> msg = pb.FileDescriptorProto(name = "helloworld") 91 | >>> msg 92 | 93 | >>> proto.clear(msg) 94 | >>> msg 95 | 96 | >>> 97 | 98 | WARNING: For compatibility with earlier Skycfg versions, the provided message 99 | will also be returned. This behavior will change to returning `None` in the 100 | v1.0 release. 101 | 102 | === `proto.clone` 103 | [[proto.clone]] 104 | 105 | Clone returns a deep copy of a Protobuf message. 106 | 107 | >>> pb = proto.package("google.protobuf") 108 | >>> msg = pb.StringValue(value = "hello") 109 | >>> clone = proto.clone(msg) 110 | >>> clone.value = "world" 111 | >>> msg 112 | 113 | >>> clone 114 | 115 | >>> 116 | 117 | === `proto.decode_any` 118 | [[proto.decode_any]] 119 | 120 | Decodes a https://developers.google.com/protocol-buffers/docs/proto3#any[`google.protobuf.Any`] 121 | into a Protobuf message. 122 | 123 | >>> pb = proto.package("google.protobuf") 124 | >>> any = pb.Any( 125 | ... type_url = "type.googleapis.com/google.protobuf.StringValue", 126 | ... value = "\n\014hello world!", 127 | ... ) 128 | >>> proto.decode_any(any) 129 | 130 | >>> 131 | 132 | The message type must be registered with Skycfg -- this is typically handled by 133 | the underlying Protobuf library. 134 | 135 | === `proto.decode_json` 136 | [[proto.decode_json]] 137 | 138 | Decodes https://en.wikipedia.org/wiki/JSON[JSON] conforming to the Protobuf 139 | https://developers.google.com/protocol-buffers/docs/proto3#json[JSON mapping] 140 | into a Protobuf message of the given type. 141 | 142 | >>> pb = proto.package("google.protobuf") 143 | >>> text = '{"name":"example.proto","options":{"java_package":"com.example"}}' 144 | >>> proto.decode_json(pb.FileDescriptorProto, text) 145 | > 146 | >>> 147 | 148 | === `proto.decode_text` 149 | [[proto.decode_text]] 150 | 151 | Decodes a text-formatted Protobuf message of the given type. 152 | 153 | >>> pb = proto.package("google.protobuf") 154 | >>> text = 'name:"example.proto" options { java_package:"com.example" }' 155 | >>> proto.decode_text(pb.FileDescriptorProto, text) 156 | > 157 | >>> 158 | 159 | WARNING: The Protobuf text format is 160 | https://github.com/protocolbuffers/protobuf/issues/3755[intentionally unspecified], 161 | and may vary between implementations. 162 | 163 | === `proto.encode_any` 164 | [[proto.encode_any]] 165 | 166 | Encodes a Protobuf message to a 167 | https://developers.google.com/protocol-buffers/docs/proto3#any[`google.protobuf.Any`] 168 | wrapper message. 169 | 170 | >>> pb = proto.package("google.protobuf") 171 | >>> msg = pb.StringValue(value = "hello world!") 172 | >>> proto.encode_any(msg) 173 | 174 | >>> 175 | 176 | WARNING: The Protobuf binary encoder is deterministic for all executions of the 177 | same binary, but is not guaranteed to generate the same output between different 178 | binaries or Protobuf implementations. 179 | 180 | === `proto.encode_json` 181 | [[proto.encode_json]] 182 | 183 | Encodes a Protobuf message to JSON that conforms to the Protobuf 184 | https://developers.google.com/protocol-buffers/docs/proto3#json[JSON mapping]. 185 | 186 | >>> pb = proto.package("google.protobuf") 187 | >>> msg = pb.FileDescriptorProto( 188 | ... name = "example.proto", 189 | ... options = pb.FileOptions(java_package = "com.example"), 190 | ... ) 191 | >>> print(proto.encode_json(msg)) 192 | {"name":"example.proto","options":{"java_package":"com.example"}} 193 | >>> 194 | 195 | The `compact = False` option may be used to insert additional whitespace into 196 | the returned value. 197 | 198 | >>> print(proto.encode_json(msg, compact = False)) 199 | { 200 | "name": "example.proto", 201 | "options": { 202 | "java_package": "com.example" 203 | } 204 | } 205 | >>> 206 | 207 | === `proto.encode_text` 208 | [[proto.encode_text]] 209 | 210 | Encodes a Protobuf message to a human-readable text format. This function is 211 | useful for inspecting the content of large Protobuf messages, since it's easier 212 | to read than the output of `repr()`. 213 | 214 | >>> pb = proto.package("google.protobuf") 215 | >>> msg = pb.FileDescriptorProto( 216 | ... name = "example.proto", 217 | ... options = pb.FileOptions(java_package = "com.example"), 218 | ... ) 219 | >>> print(proto.encode_text(msg)) 220 | name:"example.proto" options:{java_package:"com.example"} 221 | >>> 222 | 223 | WARNING: The Protobuf text format is 224 | https://github.com/protocolbuffers/protobuf/issues/3755[intentionally unspecified], 225 | and may vary between implementations. 226 | 227 | The `compact = False` option may be used to insert additional whitespace into 228 | the returned value. 229 | 230 | >>> print(proto.encode_text(msg, compact = False)) 231 | name: "example.proto" 232 | options: { 233 | java_package: "com.example" 234 | } 235 | >>> 236 | 237 | === `proto.merge` 238 | [[proto.merge]] 239 | 240 | Merges one Protobuf message into another. Both messages must be of the same type. 241 | 242 | The destination message is modified in place, and also returned so that 243 | `proto.merge()` may be used for bulk modification of a message template. 244 | 245 | >>> pb = proto.package("google.protobuf") 246 | >>> msg = pb.FieldMask(paths = ["hello"]) 247 | >>> proto.merge(msg, pb.FieldMask(paths = ["world"])) 248 | 249 | >>> msg 250 | 251 | >>> 252 | 253 | The semantics of message merging match that of the underlying Protobuf 254 | implementation: 255 | 256 | * Scalar fields are replaced. 257 | * Repeated fields are concatenated. 258 | * Maps are unioned, with new keys overwriting old keys. 259 | * Message fields are merged recursively. 260 | 261 | === `proto.package` 262 | 263 | Returns a value representing a single Protobuf package. 264 | 265 | This is the primary entry point to the Skycfg subsystem that converts Starlark 266 | value to and from Protobuf messages. 267 | 268 | >>> pb = proto.package("google.protobuf") 269 | >>> pb 270 | 271 | >>> dir(pb)[:5] 272 | ["Any", "BoolValue", "BytesValue", "DescriptorProto", "DoubleValue"] 273 | 274 | See link:protobuf.asciidoc[/docs/protobuf] for more details on the Protobuf API 275 | exported by Skycfg. 276 | 277 | === `proto.set_defaults` 278 | [[proto.set_defaults]] 279 | 280 | Sets every field of a Protobuf message to its default value. 281 | 282 | >>> pb = proto.package("google.protobuf") 283 | >>> msg = pb.FileOptions() 284 | >>> msg.optimize_for 285 | >>> proto.set_defaults(msg) 286 | >>> msg.optimize_for 287 | 288 | >>> 289 | 290 | For `proto3` messages, a field's default value and empty value are identical and 291 | this function will have the same behavior as `<>`. 292 | 293 | WARNING: For compatibility with earlier Skycfg versions, the provided message 294 | will also be returned. This behavior will change to returning `None` in the 295 | v1.0 release. 296 | 297 | == yaml 298 | 299 | Functions for encoding and decoding https://en.wikipedia.org/wiki/YAML[YAML]. 300 | 301 | Index: 302 | 303 | * `<>` 304 | * `<>` 305 | 306 | === `yaml.decode` 307 | [[yaml.decode]] 308 | 309 | Decodes a single YAML expression into a Starlark value. 310 | 311 | >>> yaml.decode('"hello"') 312 | "hello" 313 | >>> yaml.decode('["hello"]') 314 | ["hello"] 315 | >>> yaml.decode("hello:\n- world\n") 316 | {"hello": ["world"]} 317 | >>> 318 | 319 | This function is intended for use in migrating from existing YAML-based 320 | configuration systems, for example by wrapping entire YAML files in a Skycfg 321 | expression. 322 | 323 | The YAML dialect and version is unspecified and may change between Skycfg 324 | releases. 325 | 326 | === `yaml.encode` 327 | [[yaml.encode]] 328 | 329 | Encodes a single Starlark value into YAML. 330 | 331 | >>> yaml.encode("hello") 332 | "hello\n" 333 | >>> yaml.encode(["hello"]) 334 | "- hello\n" 335 | >>> yaml.encode({"hello": ["world"]}) 336 | "hello:\n- world\n" 337 | 338 | This function is intended for use in migrating from existing YAML-based 339 | configuration systems, for example by diffing the output of a Skycfg function 340 | against a known-good YAML file. 341 | 342 | The YAML dialect and version is unspecified and may change between Skycfg 343 | releases. 344 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | go.starlark.net v0.0.0-20201204201740-42d4f566359b 8 | google.golang.org/protobuf v1.33.0 9 | gopkg.in/yaml.v2 v2.2.1 10 | ) 11 | 12 | require golang.org/x/sys v0.1.0 // indirect 13 | -------------------------------------------------------------------------------- /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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 5 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 6 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 7 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 8 | go.starlark.net v0.0.0-20201204201740-42d4f566359b h1:yHUzJ1WfcdR1oOafytJ6K1/ntYwnEIXICNVzHb+FzbA= 9 | go.starlark.net v0.0.0-20201204201740-42d4f566359b/go.mod h1:5YFcFnRptTN+41758c2bMPiqpGg4zBfYji1IQz8wNFk= 10 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 11 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 12 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 13 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 14 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 15 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 16 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 19 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 20 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "protomodule", 5 | srcs = [ 6 | "merge.go", 7 | "protomodule.go", 8 | "protomodule_enum.go", 9 | "protomodule_list.go", 10 | "protomodule_map.go", 11 | "protomodule_message.go", 12 | "protomodule_message_type.go", 13 | "protomodule_package.go", 14 | "type_conversions.go", 15 | ], 16 | importpath = "github.com/stripe/skycfg/go/protomodule", 17 | visibility = ["//visibility:public"], 18 | deps = [ 19 | "@net_starlark_go//starlark", 20 | "@net_starlark_go//starlarkstruct", 21 | "@net_starlark_go//syntax", 22 | "@org_golang_google_protobuf//encoding/protojson", 23 | "@org_golang_google_protobuf//encoding/prototext", 24 | "@org_golang_google_protobuf//proto", 25 | "@org_golang_google_protobuf//reflect/protoreflect", 26 | "@org_golang_google_protobuf//reflect/protoregistry", 27 | "@org_golang_google_protobuf//types/dynamicpb", 28 | "@org_golang_google_protobuf//types/known/anypb", 29 | "@org_golang_google_protobuf//types/known/wrapperspb", 30 | ], 31 | ) 32 | 33 | go_test( 34 | name = "protomodule_test", 35 | srcs = [ 36 | "protomodule_message_test.go", 37 | "protomodule_test.go", 38 | ], 39 | embed = [":protomodule"], 40 | deps = [ 41 | "//internal/test_proto:test_proto_go_proto", 42 | "@net_starlark_go//resolve", 43 | "@net_starlark_go//starlark", 44 | "@net_starlark_go//starlarkstruct", 45 | "@net_starlark_go//syntax", 46 | "@org_golang_google_protobuf//encoding/prototext", 47 | "@org_golang_google_protobuf//proto", 48 | "@org_golang_google_protobuf//reflect/protoreflect", 49 | "@org_golang_google_protobuf//reflect/protoregistry", 50 | "@org_golang_google_protobuf//types/known/anypb", 51 | "@org_golang_google_protobuf//types/known/wrapperspb", 52 | ], 53 | ) 54 | -------------------------------------------------------------------------------- /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 | 66 | for _, item := range dst.Items() { 67 | err := newMap.SetKey(item[0], item[1]) 68 | if err != nil { 69 | return nil, err 70 | } 71 | } 72 | 73 | for _, item := range src.Items() { 74 | err := newMap.SetKey(item[0], item[1]) 75 | if err != nil { 76 | return nil, err 77 | } 78 | } 79 | 80 | return newMap, nil 81 | case *protoMessage: 82 | src, ok := src.(*protoMessage) 83 | if !ok { 84 | return nil, mergeError(dst, src) 85 | } 86 | 87 | newMessage, err := NewMessage(dst.msg) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | newMessage.Merge(dst) 93 | newMessage.Merge(src) 94 | 95 | return newMessage, nil 96 | default: 97 | return src, nil 98 | } 99 | } 100 | 101 | func mergeError(dst, src starlark.Value) error { 102 | return fmt.Errorf("MergeError: Cannot merge protobufs of different types: Merge(%s, %s)", dst.Type(), src.Type()) 103 | } 104 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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) SetIndex(i int, v starlark.Value) error { 111 | err := scalarTypeCheck(r.fieldDesc, v) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | return r.list.SetIndex(i, v) 117 | } 118 | 119 | func (r *protoRepeated) Extend(iterable starlark.Iterable) error { 120 | iter := iterable.Iterate() 121 | defer iter.Done() 122 | 123 | var val starlark.Value 124 | for iter.Next(&val) { 125 | err := r.Append(val) 126 | if err != nil { 127 | return err 128 | } 129 | } 130 | 131 | return nil 132 | } 133 | 134 | func (r *protoRepeated) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) { 135 | if op == syntax.PLUS { 136 | if side == starlark.Left { 137 | switch y := y.(type) { 138 | case *starlark.List: 139 | return starlark.Binary(op, r.list, y) 140 | case *protoRepeated: 141 | return starlark.Binary(op, r.list, y.list) 142 | } 143 | return nil, nil 144 | } 145 | if side == starlark.Right { 146 | if _, ok := y.(*starlark.List); ok { 147 | return starlark.Binary(op, y, r.list) 148 | } 149 | return nil, nil 150 | } 151 | } 152 | return nil, nil 153 | } 154 | 155 | func (r *protoRepeated) wrapAppend() starlark.Value { 156 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 157 | var val starlark.Value 158 | if err := starlark.UnpackPositionalArgs("append", args, kwargs, 1, &val); err != nil { 159 | return nil, err 160 | } 161 | if err := r.Append(val); err != nil { 162 | return nil, err 163 | } 164 | return starlark.None, nil 165 | } 166 | return starlark.NewBuiltin("append", impl).BindReceiver(r) 167 | } 168 | 169 | func (r *protoRepeated) wrapExtend() starlark.Value { 170 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 171 | var val starlark.Iterable 172 | if err := starlark.UnpackPositionalArgs("extend", args, kwargs, 1, &val); err != nil { 173 | return nil, err 174 | } 175 | if err := r.Extend(val); err != nil { 176 | return nil, err 177 | } 178 | return starlark.None, nil 179 | } 180 | return starlark.NewBuiltin("extend", impl).BindReceiver(r) 181 | } 182 | -------------------------------------------------------------------------------- /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.Sequence = (*protoMap)(nil) 47 | var _ starlark.HasAttrs = (*protoMap)(nil) 48 | var _ starlark.HasSetKey = (*protoMap)(nil) 49 | var _ starlark.Comparable = (*protoMap)(nil) 50 | 51 | func newProtoMap(mapKey protoreflect.FieldDescriptor, mapValue protoreflect.FieldDescriptor) *protoMap { 52 | return &protoMap{ 53 | mapKey: mapKey, 54 | mapValue: mapValue, 55 | dict: starlark.NewDict(0), 56 | } 57 | } 58 | 59 | func newProtoMapFromDict(mapKey protoreflect.FieldDescriptor, mapValue protoreflect.FieldDescriptor, d *starlark.Dict) (*protoMap, error) { 60 | out := &protoMap{ 61 | mapKey: mapKey, 62 | mapValue: mapValue, 63 | dict: d, 64 | } 65 | 66 | // SetKey is used to typecheck fields appropriately but done on a temporary object 67 | // so that the underlying out.dict still has a reference to the given 68 | // dict rather than copying 69 | tmpMap := newProtoMap(mapKey, mapValue) 70 | for _, item := range d.Items() { 71 | err := tmpMap.SetKey(item[0], item[1]) 72 | if err != nil { 73 | return nil, err 74 | } 75 | } 76 | 77 | // Remove any None values from map, see SetKey for compatibility behavior 78 | for _, item := range d.Items() { 79 | if item[1] == starlark.None { 80 | _, _, err := d.Delete(item[0]) 81 | if err != nil { 82 | return nil, err 83 | } 84 | } 85 | } 86 | 87 | return out, nil 88 | } 89 | 90 | func (m *protoMap) Attr(name string) (starlark.Value, error) { 91 | wrapper, ok := allowedDictMethods[name] 92 | if !ok { 93 | return nil, nil 94 | } 95 | if wrapper != nil { 96 | return wrapper(m), nil 97 | } 98 | return m.dict.Attr(name) 99 | } 100 | 101 | func (m *protoMap) AttrNames() []string { return m.dict.AttrNames() } 102 | func (m *protoMap) Freeze() { m.dict.Freeze() } 103 | func (m *protoMap) Hash() (uint32, error) { return m.dict.Hash() } 104 | func (m *protoMap) Get(k starlark.Value) (starlark.Value, bool, error) { return m.dict.Get(k) } 105 | func (m *protoMap) Iterate() starlark.Iterator { return m.dict.Iterate() } 106 | func (m *protoMap) Len() int { return m.dict.Len() } 107 | func (m *protoMap) String() string { return m.dict.String() } 108 | func (m *protoMap) Truth() starlark.Bool { return m.dict.Truth() } 109 | func (m *protoMap) Items() []starlark.Tuple { return m.dict.Items() } 110 | 111 | func (m *protoMap) Type() string { 112 | return fmt.Sprintf("map<%s, %s>", typeName(m.mapKey), typeName(m.mapValue)) 113 | } 114 | 115 | func (m *protoMap) CompareSameType(op syntax.Token, y starlark.Value, depth int) (bool, error) { 116 | other, ok := y.(*protoMap) 117 | if !ok { 118 | return false, nil 119 | } 120 | 121 | return starlark.CompareDepth(op, m.dict, other.dict, depth) 122 | } 123 | 124 | func (m *protoMap) wrapSetDefault() starlark.Value { 125 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 126 | var key, defaultValue starlark.Value = nil, starlark.None 127 | if err := starlark.UnpackPositionalArgs("setdefault", args, kwargs, 1, &key, &defaultValue); err != nil { 128 | return nil, err 129 | } 130 | if val, ok, err := m.dict.Get(key); err != nil { 131 | return nil, err 132 | } else if ok { 133 | return val, nil 134 | } 135 | return defaultValue, m.SetKey(key, defaultValue) 136 | } 137 | return starlark.NewBuiltin("setdefault", impl).BindReceiver(m) 138 | } 139 | 140 | func (m *protoMap) wrapUpdate() starlark.Value { 141 | impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 142 | // Use the underlying starlark `dict.update()` to get a Dict containing 143 | // all the new values, so we don't have to recreate the API here. After 144 | // the temp dict is constructed, type check. 145 | tempDict := &starlark.Dict{} 146 | tempUpdate, _ := tempDict.Attr("update") 147 | if _, err := starlark.Call(thread, tempUpdate, args, kwargs); err != nil { 148 | return nil, err 149 | } 150 | for _, item := range tempDict.Items() { 151 | if err := m.SetKey(item[0], item[1]); err != nil { 152 | return nil, err 153 | } 154 | } 155 | 156 | return starlark.None, nil 157 | } 158 | return starlark.NewBuiltin("update", impl).BindReceiver(m) 159 | } 160 | 161 | func (m *protoMap) SetKey(k, v starlark.Value) error { 162 | // Typecheck key 163 | err := scalarTypeCheck(m.mapKey, k) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | // Pre 1.0 compatibility allowed maps to be constructed with None in proto2 169 | // with the value treated as nil. protoreflect does not allow setting 170 | // with a value of nil, so instead treat it as an unset 171 | if fieldAllowsNone(m.mapValue) && v == starlark.None { 172 | _, _, err = m.dict.Delete(k) 173 | return err 174 | } 175 | 176 | // Typecheck value 177 | err = scalarTypeCheck(m.mapValue, v) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | return m.dict.SetKey(k, v) 183 | } 184 | -------------------------------------------------------------------------------- /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 | for attrName, starlarkValue := range parsedKwargs { 149 | if *starlarkValue == nil { 150 | continue 151 | } 152 | 153 | fieldName := attrNameToFieldName(attrName) 154 | 155 | if err := out.SetField(fieldName, *starlarkValue); err != nil { 156 | return nil, err 157 | } 158 | } 159 | 160 | return out, nil 161 | } 162 | 163 | func (t *protoMessageType) NewMessage() protoreflect.ProtoMessage { 164 | return proto.Clone(t.emptyMsg) 165 | } 166 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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//starlark", 14 | "@net_starlark_go//starlarkjson", 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "go.starlark.net/starlark" 26 | "go.starlark.net/starlarkjson" 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/test_proto/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 2 | load("@rules_proto//proto:defs.bzl", "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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------