Full Code of googlecloudrobotics/core for AI

main 086c5c4d4aeb cached
462 files
3.5 MB
951.7k tokens
1320 symbols
1 requests
Download .txt
Showing preview only (3,802K chars total). Download the full file or copy to clipboard to get everything.
Repository: googlecloudrobotics/core
Branch: main
Commit: 086c5c4d4aeb
Files: 462
Total size: 3.5 MB

Directory structure:
gitextract_mwr70acn/

├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .dockerignore
├── .editorconfig
├── .github/
│   ├── ci/
│   │   ├── .bazelrc
│   │   ├── Dockerfile.integration-test-image
│   │   ├── common.sh
│   │   ├── deploy_navtest.sh
│   │   ├── deploy_navtest_cloudbuild.yaml
│   │   ├── deployments/
│   │   │   ├── robco-integration-test/
│   │   │   │   ├── config.sh
│   │   │   │   └── kubernetes/
│   │   │   │       └── k8s-relay-rollout.yaml
│   │   │   └── robco-navtest/
│   │   │       └── config.sh
│   │   ├── integration_test.sh
│   │   ├── integration_test_cloudbuild.yaml
│   │   ├── integration_test_image_builder.sh
│   │   ├── presubmit.sh
│   │   └── release_binary.sh
│   ├── dependabot.yml
│   └── workflows/
│       ├── check-bazel.yml
│       ├── postsubmit.yml
│       ├── presubmit.yml
│       └── release.yml
├── .gitignore
├── .pep8
├── BUILD.bazel
├── CONTRIBUTING.md
├── LICENSE
├── METADATA
├── MODULE.bazel
├── README.md
├── bazel/
│   ├── BUILD.bazel
│   ├── BUILD.sysroot
│   ├── app.bzl
│   ├── app_chart.bzl
│   ├── build_rules/
│   │   ├── app_chart/
│   │   │   ├── BUILD.bazel
│   │   │   ├── Chart.yaml.template
│   │   │   ├── cache_gcr_credentials.bzl
│   │   │   ├── cache_gcr_credentials.sh.tpl
│   │   │   ├── push_all.bzl
│   │   │   ├── push_all.sh.tpl
│   │   │   ├── run_parallel.bzl
│   │   │   ├── run_parallel.sh.tpl
│   │   │   ├── values-cloud.yaml
│   │   │   └── values-robot.yaml
│   │   ├── copy.bzl
│   │   ├── helm_chart.bzl
│   │   └── helm_template.bzl
│   ├── container_push.bzl
│   └── debug_repository.bzl
├── config.sh.tmpl
├── current_versions.txt
├── deploy.sh
├── docs/
│   ├── .gitignore
│   ├── _config.yml
│   ├── concepts/
│   │   ├── app-management.md
│   │   ├── config.md
│   │   ├── device_identity.md
│   │   └── federation.md
│   ├── developers/
│   │   └── debug-auth.md
│   ├── how-to/
│   │   ├── connecting-robot.md
│   │   ├── creating-declarative-api.md
│   │   ├── deploy-from-sources.md
│   │   ├── deploying-grpc-service.md
│   │   ├── deploying-service.md
│   │   ├── examples/
│   │   │   ├── charge-service/
│   │   │   │   ├── Dockerfile
│   │   │   │   ├── charge-action.yaml
│   │   │   │   ├── charge-controller.yaml
│   │   │   │   ├── charge-crd.yaml
│   │   │   │   └── server.py
│   │   │   ├── greeter-service/
│   │   │   │   ├── Makefile
│   │   │   │   ├── client/
│   │   │   │   │   ├── Dockerfile
│   │   │   │   │   └── client.cc
│   │   │   │   ├── deploy.sh
│   │   │   │   ├── greeter-server.yaml.tmpl
│   │   │   │   ├── proto/
│   │   │   │   │   └── helloworld.proto
│   │   │   │   └── server/
│   │   │   │       ├── Dockerfile
│   │   │   │       └── server.cc
│   │   │   └── hello-service/
│   │   │       ├── client/
│   │   │       │   ├── Dockerfile
│   │   │       │   └── client.py
│   │   │       └── server/
│   │   │           ├── Dockerfile
│   │   │           ├── hello-server.yaml
│   │   │           └── server.py
│   │   ├── running-ros-node.md
│   │   ├── setting-up-oauth.md
│   │   └── using-cloud-storage.md
│   ├── index.md
│   ├── overview.md
│   └── quickstart.md
├── new_versions.txt
├── non_module_deps.bzl
├── nvchecker.toml
├── scripts/
│   ├── BUILD.bazel
│   ├── backup_robots.sh
│   ├── check-images.sh
│   ├── common.sh
│   ├── config.sh
│   ├── include-config.sh
│   ├── migrate.sh
│   ├── pre-commit
│   ├── robot-sim.sh
│   └── set-config.sh
├── src/
│   ├── .gitignore
│   ├── BUILD.bazel
│   ├── README.md
│   ├── app_charts/
│   │   ├── BUILD.bazel
│   │   ├── README.md
│   │   ├── akri/
│   │   │   ├── BUILD.bazel
│   │   │   ├── akri-robot.values.yaml
│   │   │   ├── robot/
│   │   │   │   └── akri.yaml
│   │   │   └── values-robot.yaml
│   │   ├── base/
│   │   │   ├── BUILD.bazel
│   │   │   ├── README.md
│   │   │   ├── app_management_test.sh
│   │   │   ├── cert-manager-cloud.values.yaml
│   │   │   ├── cert-manager-google-cas-issuer-cloud.values.yaml
│   │   │   ├── cert-manager-robot.values.yaml
│   │   │   ├── cloud/
│   │   │   │   ├── app-management-policy.yaml
│   │   │   │   ├── app-management.yaml
│   │   │   │   ├── apps-crd.yaml
│   │   │   │   ├── cert-ingress.yaml
│   │   │   │   ├── cert-manager-certificates.yaml
│   │   │   │   ├── cert-manager-google-cas-issuer.yaml
│   │   │   │   ├── cert-manager-issuers.yaml
│   │   │   │   ├── cert-manager.yaml
│   │   │   │   ├── cr-syncer-auth-webhook.yaml
│   │   │   │   ├── cr-syncer-policy.yaml
│   │   │   │   ├── domain-redirect.yaml
│   │   │   │   ├── fluentd-metrics.yaml
│   │   │   │   ├── kubernetes-api.yaml
│   │   │   │   ├── namespace.yaml
│   │   │   │   ├── nginx-ingress-controller-policy.yaml
│   │   │   │   ├── nginx-ingress-controller.yaml
│   │   │   │   ├── oauth2-proxy.yaml
│   │   │   │   ├── registry-crd.yaml
│   │   │   │   ├── registry-policy.yaml
│   │   │   │   ├── relay-dashboards.yaml
│   │   │   │   ├── token-vendor-app-fwd.yaml
│   │   │   │   └── token-vendor-rollout.yaml
│   │   │   ├── fluent-bit-helm.sh
│   │   │   ├── fluent-bit-values.yaml
│   │   │   ├── relay-dashboard.json
│   │   │   ├── robot/
│   │   │   │   ├── app-management.yaml
│   │   │   │   ├── cert-manager-certificates.yaml
│   │   │   │   ├── cert-manager-issuers.yaml
│   │   │   │   ├── cert-manager.yaml
│   │   │   │   ├── cr-syncer.yaml
│   │   │   │   ├── fluent-bit.yaml
│   │   │   │   ├── fluentd-gcp-addon.yaml
│   │   │   │   ├── fluentd-metrics.yaml
│   │   │   │   ├── gcr-credential-refresher.yaml
│   │   │   │   └── metadata-server.yaml
│   │   │   ├── values-cloud.yaml
│   │   │   └── values-robot.yaml
│   │   ├── k8s-relay/
│   │   │   ├── BUILD.bazel
│   │   │   ├── cloud/
│   │   │   │   ├── ingress.yaml
│   │   │   │   ├── kubernetes-relay-server.yaml
│   │   │   │   ├── service-monitor.yaml
│   │   │   │   └── service.yaml
│   │   │   ├── robot/
│   │   │   │   └── kubernetes-relay-client.yaml
│   │   │   ├── values-cloud.yaml
│   │   │   └── values-robot.yaml
│   │   ├── mission-crd/
│   │   │   ├── BUILD.bazel
│   │   │   ├── mission_crd.yaml
│   │   │   └── values.yaml
│   │   ├── platform-apps/
│   │   │   ├── BUILD.bazel
│   │   │   └── values.yaml
│   │   ├── prometheus/
│   │   │   ├── BUILD.bazel
│   │   │   ├── README.md
│   │   │   ├── cloud/
│   │   │   │   ├── app.yaml
│   │   │   │   ├── base-alerts.yaml
│   │   │   │   ├── federation-service-monitor.yaml
│   │   │   │   ├── grafana-ingress.yaml
│   │   │   │   ├── prometheus-ingress.yaml
│   │   │   │   ├── prometheus-operator.yaml
│   │   │   │   ├── prometheus-relay.yaml
│   │   │   │   └── storage-class.yaml
│   │   │   ├── prometheus-cloud.values.yaml
│   │   │   ├── prometheus-robot.values.yaml
│   │   │   ├── robot/
│   │   │   │   ├── hw-exporter.yaml
│   │   │   │   ├── prometheus-adapter.yaml
│   │   │   │   ├── prometheus-operator.yaml
│   │   │   │   ├── prometheus-relay-client.yaml
│   │   │   │   └── smartctl-exporter.yaml
│   │   │   ├── update_prometheus_adapter.sh
│   │   │   └── values-cloud.yaml
│   │   └── token-vendor/
│   │       ├── BUILD.bazel
│   │       ├── cloud/
│   │       │   ├── dashboard.yaml
│   │       │   ├── ingress.yaml
│   │       │   ├── service-monitor.yaml
│   │       │   ├── service.yaml
│   │       │   ├── token-vendor-policy.yaml
│   │       │   └── token-vendor.yaml
│   │       └── dashboard.json
│   ├── bootstrap/
│   │   ├── cloud/
│   │   │   ├── BUILD.bazel
│   │   │   ├── INSTALL_FROM_BINARY
│   │   │   ├── run-install.sh
│   │   │   └── terraform/
│   │   │       ├── .gitignore
│   │   │       ├── BUILD.bazel
│   │   │       ├── README.md
│   │   │       ├── address.tf
│   │   │       ├── certificate-authority.tf
│   │   │       ├── cluster.tf
│   │   │       ├── dns.tf
│   │   │       ├── endpoints.tf
│   │   │       ├── gcs.tf
│   │   │       ├── input.tf
│   │   │       ├── logging.tf
│   │   │       ├── multi-cluster-ingress.tf
│   │   │       ├── output.tf
│   │   │       ├── project.tf
│   │   │       ├── provider.tf
│   │   │       ├── registry.tf
│   │   │       ├── service-account.tf
│   │   │       ├── versions.tf
│   │   │       ├── workload-identity.tf
│   │   │       └── www.yaml
│   │   └── robot/
│   │       ├── BUILD.bazel
│   │       └── setup_robot.sh
│   ├── go/
│   │   ├── cmd/
│   │   │   ├── app-rollout-controller/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── main.go
│   │   │   ├── chart-assignment-controller/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── main.go
│   │   │   ├── cr-syncer/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── health.go
│   │   │   │   ├── health_test.go
│   │   │   │   ├── main.go
│   │   │   │   ├── main_test.go
│   │   │   │   ├── syncer.go
│   │   │   │   └── syncer_test.go
│   │   │   ├── cr-syncer-auth-webhook/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   ├── request.go
│   │   │   │   └── request_test.go
│   │   │   ├── gcr-credential-refresher/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── main.go
│   │   │   ├── http-relay-client/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── client/
│   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   ├── client.go
│   │   │   │   │   └── client_test.go
│   │   │   │   └── main.go
│   │   │   ├── http-relay-server/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── README.md
│   │   │   │   ├── main.go
│   │   │   │   └── server/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── broker.go
│   │   │   │       ├── broker_test.go
│   │   │   │       ├── server.go
│   │   │   │       └── server_test.go
│   │   │   ├── hw-exporter/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   └── main_test.go
│   │   │   ├── metadata-server/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── coredns.go
│   │   │   │   ├── coredns_test.go
│   │   │   │   ├── main.go
│   │   │   │   ├── main_test.go
│   │   │   │   ├── metadata.go
│   │   │   │   ├── metadata_test.go
│   │   │   │   └── nftables.go
│   │   │   ├── setup-dev/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   └── setup-dev.md
│   │   │   ├── setup-robot/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   └── main_test.go
│   │   │   ├── synk/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── README.md
│   │   │   │   └── synk.go
│   │   │   └── token-vendor/
│   │   │       ├── BUILD.bazel
│   │   │       ├── README.md
│   │   │       ├── api/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── api.go
│   │   │       │   └── v1/
│   │   │       │       ├── BUILD.bazel
│   │   │       │       ├── testdata/
│   │   │       │       │   ├── README.md
│   │   │       │       │   ├── cloudiot/
│   │   │       │       │   │   ├── describe_device.json
│   │   │       │       │   │   └── describe_device_expired_key.json
│   │   │       │       │   ├── rsa_cert.pem
│   │   │       │       │   └── rsa_private.pem
│   │   │       │       ├── v1.go
│   │   │       │       └── v1_test.go
│   │   │       ├── app/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── tokenvendor.go
│   │   │       │   └── tokenvendor_test.go
│   │   │       ├── main.go
│   │   │       ├── oauth/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── cache.go
│   │   │       │   ├── cache_test.go
│   │   │       │   ├── jwt/
│   │   │       │   │   ├── BUILD.bazel
│   │   │       │   │   ├── jwt.go
│   │   │       │   │   └── jwt_test.go
│   │   │       │   └── verifier.go
│   │   │       ├── repository/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── k8s/
│   │   │       │   │   ├── BUILD.bazel
│   │   │       │   │   ├── k8s.go
│   │   │       │   │   └── k8s_test.go
│   │   │       │   ├── memory/
│   │   │       │   │   ├── BUILD.bazel
│   │   │       │   │   ├── memory.go
│   │   │       │   │   └── memory_test.go
│   │   │       │   └── repository.go
│   │   │       ├── testdata/
│   │   │       │   ├── describe_device_a.json
│   │   │       │   ├── describe_device_b.json
│   │   │       │   ├── describe_device_b_blocked.json
│   │   │       │   └── list_devices.json
│   │   │       └── tokensource/
│   │   │           ├── BUILD.bazel
│   │   │           ├── gcp.go
│   │   │           └── gcp_test.go
│   │   ├── generate.sh
│   │   ├── pkg/
│   │   │   ├── apis/
│   │   │   │   ├── apps/
│   │   │   │   │   └── v1alpha1/
│   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │       ├── doc.go
│   │   │   │   │       ├── register.go
│   │   │   │   │       ├── types.go
│   │   │   │   │       └── zz_generated.deepcopy.go
│   │   │   │   └── registry/
│   │   │   │       └── v1alpha1/
│   │   │   │           ├── BUILD.bazel
│   │   │   │           ├── doc.go
│   │   │   │           ├── register.go
│   │   │   │           ├── types.go
│   │   │   │           └── zz_generated.deepcopy.go
│   │   │   ├── client/
│   │   │   │   ├── informers/
│   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   ├── apps/
│   │   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   │   ├── interface.go
│   │   │   │   │   │   └── v1alpha1/
│   │   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │   │       ├── app.go
│   │   │   │   │   │       ├── approllout.go
│   │   │   │   │   │       ├── chartassignment.go
│   │   │   │   │   │       ├── interface.go
│   │   │   │   │   │       └── resourceset.go
│   │   │   │   │   ├── factory.go
│   │   │   │   │   ├── generic.go
│   │   │   │   │   ├── internalinterfaces/
│   │   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   │   └── factory_interfaces.go
│   │   │   │   │   └── registry/
│   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │       ├── interface.go
│   │   │   │   │       └── v1alpha1/
│   │   │   │   │           ├── BUILD.bazel
│   │   │   │   │           ├── interface.go
│   │   │   │   │           └── robot.go
│   │   │   │   ├── listers/
│   │   │   │   │   ├── apps/
│   │   │   │   │   │   └── v1alpha1/
│   │   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │   │       ├── app.go
│   │   │   │   │   │       ├── approllout.go
│   │   │   │   │   │       ├── chartassignment.go
│   │   │   │   │   │       ├── expansion_generated.go
│   │   │   │   │   │       └── resourceset.go
│   │   │   │   │   └── registry/
│   │   │   │   │       └── v1alpha1/
│   │   │   │   │           ├── BUILD.bazel
│   │   │   │   │           ├── expansion_generated.go
│   │   │   │   │           └── robot.go
│   │   │   │   └── versioned/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── clientset.go
│   │   │   │       ├── doc.go
│   │   │   │       ├── fake/
│   │   │   │       │   ├── BUILD.bazel
│   │   │   │       │   ├── clientset_generated.go
│   │   │   │       │   ├── doc.go
│   │   │   │       │   └── register.go
│   │   │   │       ├── scheme/
│   │   │   │       │   ├── BUILD.bazel
│   │   │   │       │   ├── doc.go
│   │   │   │       │   └── register.go
│   │   │   │       └── typed/
│   │   │   │           ├── apps/
│   │   │   │           │   └── v1alpha1/
│   │   │   │           │       ├── BUILD.bazel
│   │   │   │           │       ├── app.go
│   │   │   │           │       ├── approllout.go
│   │   │   │           │       ├── apps_client.go
│   │   │   │           │       ├── chartassignment.go
│   │   │   │           │       ├── doc.go
│   │   │   │           │       ├── fake/
│   │   │   │           │       │   ├── BUILD.bazel
│   │   │   │           │       │   ├── doc.go
│   │   │   │           │       │   ├── fake_app.go
│   │   │   │           │       │   ├── fake_approllout.go
│   │   │   │           │       │   ├── fake_apps_client.go
│   │   │   │           │       │   ├── fake_chartassignment.go
│   │   │   │           │       │   └── fake_resourceset.go
│   │   │   │           │       ├── generated_expansion.go
│   │   │   │           │       └── resourceset.go
│   │   │   │           └── registry/
│   │   │   │               └── v1alpha1/
│   │   │   │                   ├── BUILD.bazel
│   │   │   │                   ├── doc.go
│   │   │   │                   ├── fake/
│   │   │   │                   │   ├── BUILD.bazel
│   │   │   │                   │   ├── doc.go
│   │   │   │                   │   ├── fake_registry_client.go
│   │   │   │                   │   └── fake_robot.go
│   │   │   │                   ├── generated_expansion.go
│   │   │   │                   ├── registry_client.go
│   │   │   │                   └── robot.go
│   │   │   ├── configutil/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── config_reader.go
│   │   │   │   └── config_reader_test.go
│   │   │   ├── controller/
│   │   │   │   ├── approllout/
│   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   ├── controller.go
│   │   │   │   │   └── controller_test.go
│   │   │   │   └── chartassignment/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── controller.go
│   │   │   │       ├── release.go
│   │   │   │       ├── release_test.go
│   │   │   │       ├── validator.go
│   │   │   │       └── validator_test.go
│   │   │   ├── gcr/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── update_gcr_credential_test.go
│   │   │   │   └── update_gcr_credentials.go
│   │   │   ├── kubetest/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── kubetest.go
│   │   │   ├── kubeutils/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── kubeutils.go
│   │   │   ├── robotauth/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── robotauth.go
│   │   │   │   └── robotauth_test.go
│   │   │   ├── setup/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── setupcommon.go
│   │   │   │   ├── setupcommon_test.go
│   │   │   │   └── util/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── factory.go
│   │   │   │       └── fake.go
│   │   │   └── synk/
│   │   │       ├── BUILD.bazel
│   │   │       ├── interface.go
│   │   │       ├── sort.go
│   │   │       ├── sort_test.go
│   │   │       ├── synk.go
│   │   │       └── synk_test.go
│   │   └── tests/
│   │       ├── BUILD.bazel
│   │       ├── apps/
│   │       │   ├── BUILD.bazel
│   │       │   ├── apps_test.go
│   │       │   └── run.sh
│   │       ├── k8s_integration_test.go
│   │       ├── k8s_integration_test_auth_helper.go
│   │       ├── relay/
│   │       │   ├── BUILD.bazel
│   │       │   ├── in_process_relay_test.go
│   │       │   └── nok8s_relay_test.go
│   │       ├── relay-bench.sh
│   │       └── relay_test.sh
│   ├── go.mod
│   ├── go.sum
│   ├── gomod.sh
│   └── proto/
│       └── http-relay/
│           ├── BUILD.bazel
│           ├── http_over_rpc.proto
│           └── unused.go
└── third_party/
    ├── BUILD
    ├── BUILD.bazel
    ├── README.md
    ├── akri/
    │   ├── BUILD.bazel
    │   ├── akri-0.12.9.tgz
    │   ├── akri-configuration-crd.yaml
    │   ├── akri-instance-crd.yaml
    │   └── update-akri.sh
    ├── app_crd.BUILD
    ├── cert-manager/
    │   ├── BUILD.bazel
    │   └── cert-manager-v1.16.3.tgz
    ├── cert-manager-google-cas-issuer/
    │   ├── BUILD.bazel
    │   └── cert-manager-google-cas-issuer-v0.6.2.tgz
    ├── fluentd_gcp_addon/
    │   ├── BUILD.bazel
    │   ├── fluentd-gcp-configmap.yaml
    │   └── fluentd-gcp-ds.yaml
    ├── helm2/
    │   └── BUILD.bazel
    ├── helm3/
    │   └── BUILD.bazel
    ├── ingress-nginx.BUILD
    ├── kube-prometheus-stack/
    │   ├── 00-crds.yaml
    │   ├── 01-crds.yaml
    │   ├── BUILD.bazel
    │   ├── kube-prometheus-stack-72.9.1.tgz
    │   └── update_crd.sh
    ├── kubernetes_proto/
    │   ├── meta/
    │   │   ├── BUILD.bazel
    │   │   ├── README.md
    │   │   └── generated.proto
    │   ├── runtime/
    │   │   ├── BUILD.bazel
    │   │   └── generated.proto
    │   └── schema/
    │       ├── BUILD.bazel
    │       └── generated.proto
    └── terraform.BUILD

================================================
FILE CONTENTS
================================================

================================================
FILE: .bazelignore
================================================
src/.gopath


================================================
FILE: .bazelrc
================================================
# Enable Bzlmod for every Bazel command
common --enable_bzlmod

# Work around go issue with LLVM 15+: https://github.com/bazelbuild/rules_go/issues/3691#issuecomment-2263999685
build --@io_bazel_rules_go//go/config:linkmode=pie

# Enforce stricter environment rules, which eliminates some non-hermetic
# behavior and therefore improves both the remote_cache cache hit rate and the
# correctness and repeatability of the build.
build --incompatible_strict_action_env=true

# Make sure that no regressions are introduced until the flag is flipped
# See: https://github.com/bazelbuild/bazel/issues/8195
build --incompatible_disallow_empty_glob

# Use the new paths.
# https://github.com/bazelbuild/bazel/issues/23127
common --incompatible_use_plus_in_repo_names

# Always use the pre-configured toolchain.
build --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
build --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1

# Set a higher timeout value, just in case.
build --remote_timeout=3600

# Platform flags
# The toolchain container used for execution is defined in the target indicated
# by "extra_execution_platforms", "host_platform" and "platforms".
# More about platforms: https://docs.bazel.build/versions/master/platforms.html
build:linux_x86_64 --extra_execution_platforms=//bazel:linux_x86_64
build:linux_x86_64 --host_platform=//bazel:linux_x86_64
build:linux_x86_64 --platforms=//bazel:linux_x86_64

build:remote       --remote_executor=grpcs://remotebuildexecution.googleapis.com
build:remote_cache --remote_cache=grpcs://remotebuildexecution.googleapis.com

# Enable authentication. This will pick up application default credentials by
# default. You can use --google_credentials=some_file.json to use a service
# account credential instead.
build:remote       --google_default_credentials=true
build:remote_cache --google_default_credentials=true

# RBE builds only support linux_x86_64.
build:remote       --config=linux_x86_64
build:remote_cache --config=linux_x86_64

# Don't run integration tests and tests that need docker by default
test --test_tag_filters="-external,-requires-docker"


================================================
FILE: .bazelversion
================================================
8.4.2


================================================
FILE: .dockerignore
================================================
*


================================================
FILE: .editorconfig
================================================
# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.go]
indent_style = tab
indent_size = 8

[*.md]
max_line_length = off
trim_trailing_whitespace = false


================================================
FILE: .github/ci/.bazelrc
================================================
# Bazel config for CI/CD builds.

# Default to keep going
build --keep_going

# Use rbe remote execution and caching on robco-integration-test.
build --config=remote
build --remote_instance_name=projects/robco-integration-test/instances/default_instance
build --google_default_credentials=true
# Slightly higher than the numer of available remote workers (10 in default_instance).
# This has not been tuned a lot.
build --jobs=12
# No neeed to download every intermediate output to the local runner.
build --remote_download_toplevel

# Use Result Store to store Build and Test logs .
build --bes_backend=buildeventservice.googleapis.com
build --bes_results_url=https://source.cloud.google.com/results/invocations
build --bes_timeout=600s
build --bes_instance_name=robco-integration-test
# Try to mitigate DEADLINE_EXCEEDED errors (b/346715839).
# Remove experimental_ prefix when updating Bazel.
build --experimental_build_event_upload_max_retries=8


================================================
FILE: .github/ci/Dockerfile.integration-test-image
================================================
# Image used for integration_test.sh on Cloud Build.
# Allows access to GKE and to run Bazel commands.
FROM gcr.io/cloud-builders/kubectl

# Install Bazelisk
RUN \
    VERSION="v1.21.0" && \
    curl -L https://github.com/bazelbuild/bazelisk/releases/download/${VERSION}/bazelisk-linux-amd64 --output /usr/bin/bazelisk && \
    chmod +x /usr/bin/bazelisk && \
    ln -s /usr/bin/bazelisk /usr/bin/bazel

RUN mkdir -p /builder /output /workspace && chmod -R 777 /output

# rules_python is not happy if bazel runs as root so create a new user
# https://github.com/bazelbuild/rules_python/pull/713
# https://github.com/GoogleCloudPlatform/cloud-builders/issues/641
RUN adduser builder --disabled-password

# Allow running sudo without password
# Add libtinfo5, which is required locally until we can upgrade to LLVM 19
RUN apt-get update && apt-get install -y sudo libtinfo5 && apt-get clean && rm -rf /var/lib/apt/lists/* && \
    usermod -aG sudo builder && \
    echo "builder ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/builder" && chmod 440 "/etc/sudoers.d/builder"

# For some reason //src/go/tests:go_default_test is expecting
# the kubeconfig in /home/builder/.kube/config, i.e. it does not use $HOME
# (which is /builder/home). alexanderfaxa@ could not figure out why so just
# copy the config there.
RUN mkdir -p /home/builder/.kube && \
    ln -s /builder/home/.kube/config /home/builder/.kube/config

USER builder


================================================
FILE: .github/ci/common.sh
================================================
#!/bin/bash

# Format for the xtrace lines
export 'PS4=+$(date --rfc-3339=seconds):${BASH_SOURCE}:${LINENO}: '
set -o errexit   # exit immediately, if a pipeline command fails
set -o pipefail  # returns the last command to exit with a non-zero status
set -o xtrace    # print command traces before executing command

RUNFILES_ROOT="_main"

# Wraps the common Bazel flags for CI for brevity.
function bazel_ci {
  bazelisk --bazelrc="${DIR}/.bazelrc" "$@"
}

function generate_build_id() {
   # Considerations for a build identifier: It must be unique, it shouldn't break
   # if we try multiple dailies in a day, and it would be nice if a textual sort
   # would put newest releases last.
   git_hash=$(echo "$GITHUB_SHA" | cut -c1-6)
   date "+daily-%Y-%m-%d-${git_hash}"
}

# Pushes images and releases a binary to a specified bucket.
# bucket: target GCS bucket to release to
# name:  name of the release tar ball
# labels: optional list of filename aliases for the release, these are one-line
#   text files with the release name as a bucket local path
function release_binary {
  local bucket="$1"
  local name="$2"

  # This function is called from test and release pipelines. We (re)build the binary and push the
  # app images here to ensure the app images which are referenced in the binary exist in the
  # registry.
  bazel_ci build \
      //src/bootstrap/cloud:crc-binary \
      //src/app_charts:push \
      //src/go/cmd/setup-robot:setup-robot.push

  # The push scripts depends on binaries in the runfiles.
  local oldPwd
  oldPwd=$(pwd)
  # The tag variable must be called 'TAG', see cloud-robotics/bazel/container_push.bzl
  for t in latest ${DOCKER_TAG}; do
    cd ${oldPwd}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh.runfiles/${RUNFILES_ROOT}
    ${oldPwd}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh \
      --repository="${CLOUD_ROBOTICS_CONTAINER_REGISTRY}/setup-robot" \
      --tag="${t}"

    cd ${oldPwd}/bazel-bin/src/app_charts/push.runfiles/${RUNFILES_ROOT}
    TAG="$t" ${oldPwd}/bazel-bin/src/app_charts/push "${CLOUD_ROBOTICS_CONTAINER_REGISTRY}"
  done
  cd ${oldPwd}

  gcloud storage cp \
      --predefined-acl=publicRead \
      bazel-bin/src/bootstrap/cloud/crc-binary.tar.gz \
      "gs://${bucket}/${name}.tar.gz"

  # Overwrite cache control as we want changes to run-install.sh and version files to be visible
  # right away.
  gcloud storage cp \
      --predefined-acl=publicRead \
      --cache-control="private, max-age=0, no-transform" \
      src/bootstrap/cloud/run-install.sh \
      "gs://${bucket}/"

  # The remaining arguments are version labels. GCS does not support symlinks, so we use version
  # files instead.
  local vfile
  vfile=$(mktemp)
  echo "${name}.tar.gz" >${vfile}
  shift 2
  # Loop over remianing args in $* and creat alias files.
  for label; do
    gcloud storage cp \
        --predefined-acl=publicRead \
        --cache-control="private, max-age=0, no-transform" \
        ${vfile} "gs://${bucket}/${label}"
  done
}




================================================
FILE: .github/ci/deploy_navtest.sh
================================================
#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "${DIR}/common.sh"

PROJECT_DIR="${DIR}/deployments/robco-navtest"
source "${PROJECT_DIR}/config.sh"

# TODO(skopecki) These variables should be declared in the run-install.sh and removed from this script.
export BUCKET_URI="https://storage.googleapis.com/robco-ci-binary-builds"
export SOURCE_CONTAINER_REGISTRY="gcr.io/robco-team"

# Deploy the binary release that was pushed by the last successful integration test.
curl --silent --show-error --fail "${BUCKET_URI}/run-install.sh" \
    | bash -x -s -- ${GCP_PROJECT_ID}



================================================
FILE: .github/ci/deploy_navtest_cloudbuild.yaml
================================================
# Call deploy_navtest.sh on Cloud Build.
# TODO(b/323509860): Run directly on the Action runner when it supports WIF.
steps:
  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: "bash"
    args: ["./.github/ci/deploy_navtest.sh"]
timeout: 1200s


================================================
FILE: .github/ci/deployments/robco-integration-test/config.sh
================================================
#!/usr/bin/env bash

# Enable cloud robotics layer 2
APP_MANAGEMENT=true

GCP_PROJECT_ID=robco-integration-test
GCP_REGION=europe-west1
GCP_ZONE=europe-west1-c
CLOUD_ROBOTICS_SHARED_OWNER_GROUP=cloud-robotics-cloud-owner-acl@twosync.google.com
CLOUD_ROBOTICS_DEPLOY_ENVIRONMENT=GCP-testing
TERRAFORM_GCS_BUCKET="robco-team-terraform-state"
TERRAFORM_GCS_PREFIX="state/${GCP_PROJECT_ID}"
CLOUD_ROBOTICS_CONTAINER_REGISTRY=gcr.io/robco-team
PRIVATE_DOCKER_PROJECTS=robco-team
CLOUD_ROBOTICS_CTX=gke_robco-integration-test_europe-west1-c_cloud-robotics


================================================
FILE: .github/ci/deployments/robco-integration-test/kubernetes/k8s-relay-rollout.yaml
================================================
apiVersion: apps.cloudrobotics.com/v1alpha1
kind: AppRollout
metadata:
  name: k8s-relay
  labels:
    app: k8s-relay
spec:
  appName: k8s-relay-dev
  cloud: {}
  robots:
  - selector:
      any: true


================================================
FILE: .github/ci/deployments/robco-navtest/config.sh
================================================
#!/usr/bin/env bash

# Enable google cloud robotics layer 2
APP_MANAGEMENT=true

GCP_PROJECT_ID=robco-navtest
GCP_REGION=europe-west1
GCP_ZONE=europe-west1-c
CLOUD_ROBOTICS_SHARED_OWNER_GROUP=cloud-robotics-cloud-owner-acl@twosync.google.com
TERRAFORM_GCS_BUCKET="robco-team-terraform-state"
TERRAFORM_GCS_PREFIX="state/${GCP_PROJECT_ID}"
CLOUD_ROBOTICS_CONTAINER_REGISTRY=gcr.io/robco-team
PRIVATE_DOCKER_PROJECTS=robco-team
CLOUD_ROBOTICS_CTX=gke_robco-navtest_europe-west1-c_cloud-robotics


================================================
FILE: .github/ci/integration_test.sh
================================================
#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "${DIR}/common.sh"
source "./scripts/common.sh"

# Because the format from common.sh is not recognized by Cloud Build.
export 'PS4='

# Need to source the project config from here
PROJECT_DIR="${DIR}/deployments/robco-integration-test"
source "${PROJECT_DIR}/config.sh"
gcloud config set project ${GCP_PROJECT_ID}
gke_get_credentials "${GCP_PROJECT_ID}" "cloud-robotics" "${GCP_REGION}" "${GCP_ZONE}"

BUILD_IDENTIFIER=$(generate_build_id)
echo "INFO: Build identifier is $BUILD_IDENTIFIER"

export BAZEL_FLAGS="--bazelrc=${DIR}/.bazelrc"
bash -x ./deploy.sh update "${GCP_PROJECT_ID}"

# Create a GKE cluster with a single robot for the relay test.
ROBOT_RELAY_CLUSTER="relay-test"
export SKIP_LOCAL_PULL=true
bash -x ./scripts/robot-sim.sh create "${GCP_PROJECT_ID}" "${ROBOT_RELAY_CLUSTER}"

bazel_ci run //src/go/cmd/setup-dev -- --project="${GCP_PROJECT_ID}" --robot-name="${ROBOT_RELAY_CLUSTER}"

DOMAIN=${CLOUD_ROBOTICS_DOMAIN:-"www.endpoints.${GCP_PROJECT_ID}.cloud.goog"}
ROBOT_CONTEXT="gke_${GCP_PROJECT_ID}_${GCP_ZONE}_${ROBOT_RELAY_CLUSTER}"

# Output state of cloud and robot k8s context to inspect the health of pods.
kubectl config get-contexts || true
kubectl --context ${CLOUD_ROBOTICS_CTX} get pods || true
kubectl --context ${GCP_PROJECT_ID}-robot get pods || true
kubectl --context ${ROBOT_CONTEXT} get pods || true

bazel_ci test \
  --test_env GCP_PROJECT_ID=${GCP_PROJECT_ID} \
  --test_env GCP_REGION=${GCP_REGION} \
  --test_env GCP_ZONE=${GCP_ZONE} \
  --test_env CLUSTER=${ROBOT_RELAY_CLUSTER} \
  --test_env PATH=$PATH \
  --jvmopt="-DCLOUD_ROBOTICS_DOMAIN=${DOMAIN}" \
  --test_output=streamed \
  --test_tag_filters="external" \
  --strategy=TestRunner=standalone \
  //...

# If this is running on main (ie, not a manual run) then update the `latest`
# binary.
if [[ "$MANUAL_RUN" == "false" ]] ; then
  release_binary "robco-ci-binary-builds" "crc-${BUILD_IDENTIFIER}" "latest"
fi


================================================
FILE: .github/ci/integration_test_cloudbuild.yaml
================================================
# A Cloud Build job for running integration_test.sh.
# TODO(b/323509860): Run directly on the Action runner when it supports WIF.
steps:
  # Needed for cloud build to allow running Bazel as non-root, see
  # https://github.com/GoogleCloudPlatform/cloud-builders/issues/641#issuecomment-604599102
  # Not part of the Dockerfile since the chmod layer adds significant image size.
  - name: ubuntu
    entrypoint: "bash"
    args: ["-c", "chmod -R 777 /builder && chmod -R 777 /workspace"]

  # This runs on a custom image that has kubectl, gcloud and bazel installed.
  # See Dockerfile.integration-test-image.
  - name: "gcr.io/robco-integration-test/integration-test-image@sha256:87e7cde1d2923eed014ee8ee0c365d683fa7a8a99985bbc77acb951c2e6faefc"
    entrypoint: "bash"
    args: ["./.github/ci/integration_test.sh"]
    env:
      - "GITHUB_SHA=${_GITHUB_SHA}"
      - "MANUAL_RUN=${_MANUAL_RUN}"

substitutions:
  _GITHUB_SHA: ""
  _MANUAL_RUN: ""

options:
  dynamicSubstitutions: true
  substitutionOption: "MUST_MATCH"
timeout: 1800s


================================================
FILE: .github/ci/integration_test_image_builder.sh
================================================
#!/bin/bash
# Builds and pushes a docker image that can be used in Cloud Build to run
# the integration test (see integration_test_cloudbuild.yaml).
#
# To be manually invoked and the resulting sha256 copied to
# integration_test_cloudbuild.yaml after changing the Dockerfile.

set -euo pipefail

NAME="gcr.io/robco-integration-test/integration-test-image"
docker build --network=host -t "${NAME}" - \
  < .github/ci/Dockerfile.integration-test-image
docker push "${NAME}"


================================================
FILE: .github/ci/presubmit.sh
================================================
#!/bin/bash
#
# Presubmit script for testing cloud robotics.
# Expected to run remotely on a GitHub Actions runner, not locally.

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

# shellcheck source=ci/common.sh
source "${DIR}/common.sh"

echo "Timestamp: build started"
bazel_ci build --nobuild  //...
echo "Timestamp: build-deps fetched"
bazel_ci build //...
echo "Timestamp: build done"
bazel_ci test --test_output=errors //...
echo "Timestamp: test done"

# Some of the tests below pull Docker images from the repository. We need to
# make sure they are pushed and provide an access token.
gcloud auth configure-docker --quiet

REGISTRY="gcr.io/robco-integration-test"
TAG="latest" bazel_ci run \
  //src/app_charts:push "${REGISTRY}"

# We're running into timeouts at CI and also don't see the actual failure
# reasons. Disable the test for now until someone has time and ideas how to
# resurrect it.
#
# set +o xtrace  # Don't put the access token in the logs.
# ACCESS_TOKEN="$(gcloud auth application-default print-access-token)"
# Note: --strategy=TestRunner=standalone means that the tests are run locally
# and not on a remote worker (which does not have the Docker environment).
# bazel_ci test \
#   --flaky_test_attempts 3 \
#   --test_env ACCESS_TOKEN="${ACCESS_TOKEN}" \
#   --test_env REGISTRY="${REGISTRY}" \
#   --test_tag_filters="requires-docker" \
#   --test_output=errors \
#   --strategy=TestRunner=standalone //src/go/tests/apps:go_default_test
# 
# set -o xtrace

echo "Timestamp: presubmit.sh done"


================================================
FILE: .github/ci/release_binary.sh
================================================
#!/bin/bash

set -euo pipefail

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# shellcheck source=ci/common.sh
source "${DIR}/common.sh"

gcloud auth configure-docker --quiet

# Set defaults used for the release, they can only be overriden when testing
# manually.
GCP_BUCKET=${GCP_BUCKET:-"cloud-robotics-releases"}
VERSION=${VERSION:-"0.1.0"}
SHA=$(git rev-parse --short "$FULL_SHA")
RELEASE_NAME="v$VERSION-$SHA"
# TAG is a global variable that is used in the container push rules.
export TAG="crc-${VERSION}-${SHA}"
LABELS=${LABELS:-"latest crc-${VERSION}/crc-${VERSION}+latest"}

# Get the last release. We only create a new release if the main branch has moved since
# as trying to re-create an existing release is an error.
output=$(curl --fail-with-body -sS \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: token $GITHUB_TOKEN" \
  https://api.github.com/repos/$REPO/releases/latest)
PREVIOUS_RELEASE_NAME="$(jq -r '.tag_name'   <<< $output)"

if [ "$RELEASE_NAME" = "$PREVIOUS_RELEASE_NAME" ]; then
    echo "Release $RELEASE_NAME already exists. Nothing more to do."
    exit 0
else
    echo "Previous release is $PREVIOUS_RELEASE_NAME"
fi

CLOUD_ROBOTICS_CONTAINER_REGISTRY="gcr.io/cloud-robotics-releases"
# DOCKER_TAG is a global variable that is used in release_binary.
DOCKER_TAG=${DOCKER_TAG:-"crc-${VERSION}-${SHA}"}

release_binary "${GCP_BUCKET}" "crc-${VERSION}/crc-${VERSION}+${SHA}" ${LABELS}

# Generate release notes comparing against the previous release.
output=$(curl --fail-with-body -sS \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: token $GITHUB_TOKEN" \
  https://api.github.com/repos/$REPO/releases/generate-notes \
  -d '{"tag_name":"'$RELEASE_NAME'","previous_tag_name":"'$PREVIOUS_RELEASE_NAME'"}')
# Code newlines as literal \n and escape double quotes to generate valid JSON.
BODY="$(jq -r '.body'   <<< $output | awk '{printf "%s\\n", $0}' | sed 's/"/\\"/g')"
echo "Generated release notes for $RELEASE_NAME"

# Create the release on GitHub.
curl --fail-with-body -sS \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: token $GITHUB_TOKEN" \
  https://api.github.com/repos/$REPO/releases \
  --data-binary @- << EOF
{
  "tag_name": "$RELEASE_NAME",
  "name": "$RELEASE_NAME",
  "body": "$BODY"
}
EOF


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "gomod"
    directory: "/src/"
    schedule:
      interval: "weekly"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

  - package-ecosystem: "bazel"
    directory: "/"
    schedule:
      interval: "weekly"


================================================
FILE: .github/workflows/check-bazel.yml
================================================
name: Check Bazel

on:
  workflow_call:

permissions:
  contents: read
  id-token: write

jobs:
  check-bazel:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2
      - name: Auth
        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # tag=v3.0.0
        with:
          create_credentials_file: true # also sets GOOGLE_APPLICATION_CREDENTIALS
          service_account: "github-automation-bot@gha-crc-dev.iam.gserviceaccount.com"
          workload_identity_provider: "projects/1043719249528/locations/global/workloadIdentityPools/github-automation/providers/crc-dev"
      - name: Print error on auth fail
        if: failure()
        run: |
          echo >&2 "This PR appears to be from a fork or authored by a non-org member, rather than from the primary repo."
          echo >&2 "This means it can't run the presubmit, which requires access to GCR."
          echo >&2 "If you are a project member, please push your branch to github.com/googlecloudrobotics/core instead."
          exit 1
      - name: Run .github/ci/presubmit.sh
        run: ./.github/ci/presubmit.sh
      - name: Get bazel server logs
        if: success() || failure()
        run: cat ~/.cache/bazel/_bazel_*/*/java.log || true


================================================
FILE: .github/workflows/postsubmit.yml
================================================
name: Postsubmit

on:
  schedule:
    - cron: "0 4 * * *" # Once a day at 4am.
  # Manual runs through Actions tab in the UI
  workflow_dispatch:
    inputs:
      force-binary-release:
        description: >-
          force-binary-release: Set to non-empty when running from main to
          create a binary release that can be used by 'Create release'.

permissions:
  contents: read
  id-token: write

concurrency:
  group: integration_test
  cancel-in-progress: true

jobs:
  call-bazel:
    uses: ./.github/workflows/check-bazel.yml

  integration-test:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2
      - name: Auth
        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # tag=v3.0.0
        with:
          create_credentials_file: true # also sets GOOGLE_APPLICATION_CREDENTIALS
          service_account: "github-automation-bot@gha-crc-dev.iam.gserviceaccount.com"
          workload_identity_provider: "projects/1043719249528/locations/global/workloadIdentityPools/github-automation/providers/crc-dev"
      - name: Run integration_test.sh on Cloud Build
        env:
          MANUAL_RUN: "${{ github.event_name == 'workflow_dispatch' && inputs.force-binary-release == '' }}"
        run: |
          gcloud builds submit \
            --project robco-integration-test \
            --region europe-west1 \
            --config .github/ci/integration_test_cloudbuild.yaml \
            --substitutions _GITHUB_SHA=${GITHUB_SHA},_MANUAL_RUN=${MANUAL_RUN}


================================================
FILE: .github/workflows/presubmit.yml
================================================
name: Presubmit

on:
  pull_request:
    branches: ["main"]
  workflow_dispatch:

permissions:
  contents: read
  id-token: write
  pull-requests: read

# Cancel previous runs if a new one is started.
concurrency:
  group: ${{ github.ref }}
  cancel-in-progress: true

jobs:
  setup-presubmit:
    runs-on: ubuntu-22.04
    outputs:
      presubmit_digest: ${{ steps.pr-digest.outputs.digest }}
      presubmit_status: ${{ steps.status.outputs.status }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2
      - name: Get PR digest
        id: pr-digest
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          set -euo pipefail

          # Get the list of changed files in the PR.
          gh pr view ${{github.event.number}} --json files -q '.files[].path' > /tmp/changed_files.txt

          # Create a tarball of the changed files and compute its SHA256.
          # --ignore-failed-read to not fail on deleted files.
          tar -cvf /tmp/changed_files.tar \
            --owner=root --group=root --numeric-owner --mtime="2010-01-01" --sort=name \
            -T /tmp/changed_files.txt \
            --ignore-failed-read
          digest=$(cat /tmp/changed_files.txt /tmp/changed_files.tar | sha256sum | cut -d " " -f1)
          echo "digest=$digest" >> $GITHUB_OUTPUT
      - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ~/PRESUBMITS_SUCCEEDED
          key: PRESUBMITS_SUCCEEDED-${{ steps.pr-digest.outputs.digest }}
      - name: Check for previous runs
        id: status
        run: |
          if [ -f ~/PRESUBMITS_SUCCEEDED ]; then
            echo "status=success" >> $GITHUB_OUTPUT
          fi

  call-check-bazel:
    needs: [setup-presubmit]
    if: ${{ needs.setup-presubmit.outputs.presubmit_status != 'success' }}
    uses: ./.github/workflows/check-bazel.yml

  presubmits-ok:
    needs: [setup-presubmit, call-check-bazel]
    runs-on: ubuntu-22.04
    # To ensure this job always runs even if the "heavy" jobs were skipped.
    # This allows us to guard merging on this check in Branch Protection.
    if: ${{ always() }}
    steps:
      - name: Fail if tests failed
        # always() because GitHub requires a status macro to be included or else this gets skipped.
        # https://github.com/actions/runner/issues/491#issuecomment-850884422
        if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
        run: exit 1
      - name: Create presubmits succeeded marker
        run: touch ~/PRESUBMITS_SUCCEEDED
      - uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ~/PRESUBMITS_SUCCEEDED
          key: PRESUBMITS_SUCCEEDED-${{ needs.setup-presubmit.outputs.presubmit_digest }}


================================================
FILE: .github/workflows/release.yml
================================================
name: Create release

on:
  schedule:
    - cron: "0 5 * * *" # Once a day at 5am.
  # Manual runs through Actions tab in the UI
  workflow_dispatch:

permissions:
  actions: read
  contents: write
  id-token: write
  pull-requests: read

# Cancel previous runs if a new one is started.
concurrency:
  group: ${{ github.ref }}
  cancel-in-progress: true

jobs:
  create_release:
    runs-on: ubuntu-22.04
    steps:
      # Check out repo at latest green postsubmit commit on the main branch.
      - name: Get latest passing commit
        id: latest-green
        env:
          REPO: ${{ github.repository }}
        run: |
          set -euo pipefail
          output=$(curl --fail-with-body -sS \
            -H "Accept: application/vnd.github+json" \
            "https://api.github.com/repos/$REPO/actions/workflows/postsubmit.yml/runs?per_page=1&branch=main&event=schedule&status=success")
          repo_id=$(jq -r '.workflow_runs[0].head_repository.id' <<< $output)
          if [[ "${repo_id}" != "${{ github.repository_id }}" ]] ; then
            echo >&2 "Unexpected head repository ID: ${repo_id} - check postsubmit.yml configuration"
            exit 1
          fi
          sha=$(jq -r '.workflow_runs[0].head_sha' <<< $output)
          echo "latest_green=$sha" >> $GITHUB_OUTPUT
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2
        with:
          ref: ${{ steps.latest-green.outputs.latest_green }}
      - name: Auth
        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # tag=v3.0.0
        with:
          create_credentials_file: true # also sets GOOGLE_APPLICATION_CREDENTIALS
          service_account: "github-automation-bot@gha-crc-prod.iam.gserviceaccount.com"
          workload_identity_provider: "projects/695270090783/locations/global/workloadIdentityPools/github-automation/providers/crc-prod"
      - name: "Set up gcloud"
        uses: "google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db" # tag=v3.0.1
        with:
          skip_install: true

      - name: Deploy Navtest on Cloud Build
        run: |
          gcloud builds submit \
            --project robco-navtest \
            --config .github/ci/deploy_navtest_cloudbuild.yaml

      # Now we are ready to create the release.
      - name: Run release_binary.sh
        env:
          REPO: ${{ github.repository }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          FULL_SHA: ${{ steps.latest-green.outputs.latest_green }}
        run: ./.github/ci/release_binary.sh


================================================
FILE: .gitignore
================================================
.cache/
*.pyc
env/

# auto-generated by scripts/backport_kubeadm.sh
nsenter

# IntelliJ files
.project/

# Bazel
bazel-*
buildprofile.out
buildprofile.out.html


================================================
FILE: .pep8
================================================
[pep8]
aggressive=2
indent-size=2
max-line-length=80


================================================
FILE: BUILD.bazel
================================================
# Description:
#   Root BUILD file for cloud-robotics

load("@bazel_gazelle//:def.bzl", "gazelle")

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

exports_files([
    "config.sh.tmpl",
    "deploy.sh",
])

# Gazelle uses this to build importpath attributes.
# gazelle:prefix github.com/googlecloudrobotics/core

# Gazelle is used to generate BUILD.bazel files for WORKSPACE dependencies
# running this manually via "bazel run //:gazelle" will regenerate BUILD.bazel files that
# contain go-rules.
gazelle(
    name = "gazelle",
)

# Libraries are named go_default_library, tests are named go_default_test.
# gazelle:go_naming_convention go_default_library

# We ignore the build files generated by bazel-deps as it doesn't use buildifer.
# gazelle:exclude third_party

# Also ignore the Go sources downloaded by src/go/deps.sh.
# gazelle:exclude src/.gopath

# Don't created build files for these examples
# gazelle:exclude  docs/how-to/examples/greeter-service/proto/


================================================
FILE: CONTRIBUTING.md
================================================
# How to Contribute

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

## Code formatting

We have a pre-commit hook to check code formatting, which you can install with:

```
ln -s ../../scripts/pre-commit .git/hooks/
```

It depends on external tools for formatting, which you may be prompted to
install when it first runs.

## Community Guidelines

This project follows
[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).



================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: METADATA
================================================
name: "Cloud Robotics"

third_party {
  # https://nvd.nist.gov/products/cpe/search
  security {
    tag: "NVD-CPE2.3:cpe:/a:grafana:grafana:10.3.1"
    tag: "NVD-CPE2.3:cpe:/a:kubernetes:ingress-nginx:1.8.4"
    tag: "NVD-CPE2.3:cpe:/a:oauth2_proxy_project:oauth2_proxy:7.5.1"
    tag: "NVD-CPE2.3:cpe:/a:prometheus:prometheus:2.49.1"
  }
}


================================================
FILE: MODULE.bazel
================================================
bazel_dep(name = "aspect_bazel_lib", version = "2.21.2")
bazel_dep(name = "bazel_skylib", version = "1.8.1")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "protobuf", version = "29.0", repo_name = "com_google_protobuf")
bazel_dep(name = "rules_oci", version = "2.0.1")
bazel_dep(name = "rules_pkg", version = "1.0.1")
bazel_dep(name = "rules_shell", version = "0.4.1")
# -- bazel_dep definitions -- #

non_module_deps = use_extension("//:non_module_deps.bzl", "non_module_deps")
use_repo(non_module_deps, "kubernetes_helm")
use_repo(non_module_deps, "kubernetes_helm3")
use_repo(non_module_deps, "hashicorp_terraform")
use_repo(non_module_deps, "com_github_kubernetes_sigs_application")
use_repo(non_module_deps, "ingress-nginx")
# End of extension `non_module_deps`

#######
# C++ #
#######

bazel_dep(name = "toolchains_llvm", version = "1.7.0")

# Inspect supported toolchains at https://github.com/bazel-contrib/toolchains_llvm/blob/master/toolchain/internal/llvm_distributions.bzl
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm")
llvm.toolchain(
    llvm_version = "18.1.4",
)

use_repo(non_module_deps, "com_googleapis_storage_chrome_linux_amd64_sysroot")

llvm.sysroot(
    label = "@com_googleapis_storage_chrome_linux_amd64_sysroot//:all_files",
    targets = ["linux-x86_64"],
)
use_repo(llvm, "llvm_toolchain")

register_toolchains("@llvm_toolchain//:all")

bazel_dep(name = "rules_cc", version = "0.1.5")

######
# Go #
######

bazel_dep(name = "rules_go", version = "0.59.0", repo_name = "io_bazel_rules_go")

go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
go_sdk.download(version = "1.25.4")

bazel_dep(name = "gazelle", version = "0.47.0", repo_name = "bazel_gazelle")

go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//src:go.mod")
use_repo(
    go_deps,
    "com_github_cenkalti_backoff",
    "com_github_form3tech_oss_jwt_go",
    "com_github_fsnotify_fsnotify",
    "com_github_getlantern_httptest",
    "com_github_golang_glog",
    "com_github_golang_mock",
    "com_github_google_go_cmp",
    "com_github_google_nftables",
    "com_github_googlecloudrobotics_ilog",
    "com_github_jaypipes_ghw",
    "com_github_jaypipes_pcidb",
    "com_github_motemen_go_loghttp",
    "com_github_onsi_gomega",
    "com_github_pkg_errors",
    "com_github_prometheus_client_golang",
    "com_github_spf13_cobra",
    "com_github_spf13_pflag",
    "com_google_cloud_go_compute_metadata",
    "com_google_cloud_go_storage",
    "in_gopkg_h2non_gock_v1",
    "io_k8s_api",
    "io_k8s_apiextensions_apiserver",
    "io_k8s_apimachinery",
    "io_k8s_cli_runtime",
    "io_k8s_client_go",
    "io_k8s_helm",
    "io_k8s_klog",
    "io_k8s_klog_v2",
    "io_k8s_sigs_controller_runtime",
    "io_k8s_sigs_kind",
    "io_k8s_sigs_yaml",
    "io_opencensus_go",
    "io_opencensus_go_contrib_exporter_prometheus",
    "io_opencensus_go_contrib_exporter_stackdriver",
    "org_golang_google_api",
    "org_golang_google_grpc",
    "org_golang_google_protobuf",
    "org_golang_x_crypto",
    "org_golang_x_net",
    "org_golang_x_oauth2",
    "org_golang_x_sync",
)

#######
# OCI #
#######

oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")

# gcloud container images describe gcr.io/distroless/base:latest --format='value(image_summary.digest)'
oci.pull(
    name = "distroless_base",
    digest = "sha256:b31a6e02605827e77b7ebb82a0ac9669ec51091edd62c2c076175e05556f4ab9",
    image = "gcr.io/distroless/base",
    platforms = ["linux/amd64"],
)

# gcloud container images describe gcr.io/distroless/cc:latest --format='value(image_summary.digest)'
oci.pull(
    name = "distroless_cc",
    digest = "sha256:8aad707f96620ee89e27febef51b01c6ff244277a3560fcfcfbe68633ef09193",
    image = "gcr.io/distroless/cc",
    platforms = ["linux/amd64"],
)
oci.pull(
    name = "iptables_base",
    digest = "sha256:656e45c00083359107b1d6ae0411ff3894ba23011a8533e229937a71be84e063",
    image = "gcr.io/google-containers/debian-iptables",
    platforms = ["linux/amd64"],
)
use_repo(
    oci,
    "distroless_base",
    "distroless_base_linux_amd64",
    "distroless_cc",
    "distroless_cc_linux_amd64",
    "iptables_base",
    "iptables_base_linux_amd64",
)


================================================
FILE: README.md
================================================
# Cloud Robotics Core

Google's Cloud Robotics Core is an open source platform that provides
infrastructure essential to building and running robotics solutions for business
automation. Cloud Robotics Core makes managing robot fleets easy for developers,
integrators, and operators. It enables:

* packaging and distribution of applications
* secure, bidirectional robot-cloud communication
* easy access to Google Cloud services such as ML, logging, and monitoring.

![Cloud Robotics Core overview](docs/cloud-robotics-core-overview.png)

Cloud Robotics Core is open source and pre-alpha. Support is currently limited
to a small set of early access partners. We will gladly accept contributions
and feedback, but we are making no stability or support guarantees at this
point in time.

# Documentation

Documentation of the platform and related How-to guides can be found at: https://googlecloudrobotics.github.io/core/

# Get Involved

If you want to get involved, please refer to [CONTRIBUTING.md](CONTRIBUTING.md),
reach out to [cloud-robotics-discuss@googlegroups.com](https://groups.google.com/forum/#!forum/cloud-robotics-discuss)
or ask Stack Overflow questions with [#google-cloud-robotics](https://stackoverflow.com/questions/tagged/google-cloud-robotics).

# Source Code

Most interesting bits are under `src`:

* app_charts: contains kubernetes resources for the core platform and apps
* bootstrap: provisioning for the cloud (terraform) and the robot (debian package)
* go/: the code that goes into images referenced from `app_charts`

The root directory contains a `deploy.sh` script for building and installing the software. More
details on that are in the [building from sources](how-to/deploy-from-sources) guide.


================================================
FILE: bazel/BUILD.bazel
================================================
exports_files([
    "app.bzl",
    "app_chart.bzl",
    "container_push.bzl",
    "repositories.bzl",
])

platform(
    name = "linux_x86_64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
        "@bazel_tools//tools/cpp:clang",
    ],
    exec_properties = {
        "container-image": "docker://gcr.io/cloud-robotics-releases/bazel-rbe-executor@sha256:3ee043e7a322caaff8c9edaa302373deb80e67ad6e42ae35d34b8f3597b8995e",
        "OSFamily": "Linux",
    },
    parents = ["@local_config_platform//:host"],
)


================================================
FILE: bazel/BUILD.sysroot
================================================
filegroup(
    name = "all_files",
    srcs = glob(
        [
            "lib/x86_64-linux-gnu/ld*",
            "lib/x86_64-linux-gnu/libc*",
            "lib/x86_64-linux-gnu/libdl*",
            "lib/x86_64-linux-gnu/libgcc*",
            "lib/x86_64-linux-gnu/libm*",
            "lib/x86_64-linux-gnu/libpthread*",
            "lib/x86_64-linux-gnu/librt*",
            "lib/x86_64-linux-gnu/libutil*",
            "lib64/**",
            "usr/include/*.h",
            "usr/include/arpa/**",
            "usr/include/asm-generic/**",
            "usr/include/c++/**",
            "usr/include/linux/**",
            "usr/include/net/**",
            "usr/include/netinet/**",
            "usr/include/rpc/**",
            "usr/include/sys/**",
            "usr/include/x86_64-linux-gnu/**",
            "usr/lib/gcc/**",
            "usr/lib/x86_64-linux-gnu/*crt*.o",
            "usr/lib/x86_64-linux-gnu/libc_nonshared.a",
            "usr/lib/x86_64-linux-gnu/libc.a",
            "usr/lib/x86_64-linux-gnu/libc.so",
            "usr/lib/x86_64-linux-gnu/libdl*",
            "usr/lib/x86_64-linux-gnu/libm*",
            "usr/lib/x86_64-linux-gnu/libpthread*",
            "usr/lib/x86_64-linux-gnu/libresolv.so",
            "usr/lib/x86_64-linux-gnu/librt*",
            "usr/lib/x86_64-linux-gnu/libutil*",
        ],
    ),
    visibility = ["//visibility:public"],
)


================================================
FILE: bazel/app.bzl
================================================
load("//bazel/build_rules/app_chart:run_parallel.bzl", "run_parallel")

def app(name, charts, visibility = None):
    """Macro for a standard Cloud Robotics app.

    This macro establishes two subrules for app name "foo":
    - :foo.push pushes the Docker images for the app.
    - :foo.yaml is a YAML file with the app CR that you need to push to
      Kubernetes. Use k8s_object to push it, or compile it into a Helm chart.

    Args:
      name: string. Name of the app.
      charts: list of targets. Helm charts for this app.
      visibility: Visibility.
    """
    pkg = Label("{}//{}".format(native.repository_name(), native.package_name()))
    chart_labels = [pkg.relative(c) for c in charts]
    run_parallel(
        name = name + ".push",
        targets = ["//{}:{}.push".format(c.package, c.name) for c in chart_labels],
        visibility = visibility,
    )

    native.genrule(
        # we name this differently than the file we produce to silence:
        #   target 'xxx.yaml' is both a rule and a file; please choose another name for the rule
        name = name + ".manifest",
        srcs = [
            "//{}:{}.snippet-yaml".format(c.package, c.name)
            for c in chart_labels
        ],
        outs = [name + ".yaml"],
        cmd = """cat - $(SRCS) > $@ <<EOF
apiVersion: apps.cloudrobotics.com/v1alpha1
kind: App
metadata:
  name: {name}-dev
spec:
  components:
EOF
""".format(name = name),
        visibility = visibility,
    )


================================================
FILE: bazel/app_chart.bzl
================================================
load("//bazel:build_rules/helm_chart.bzl", "helm_chart")
load("//bazel/build_rules/app_chart:cache_gcr_credentials.bzl", "cache_gcr_credentials")
load("//bazel/build_rules/app_chart:push_all.bzl", "push_all")

def _impl(ctx):
    chart_yaml = ctx.actions.declare_file(ctx.label.name + "-chart.yaml")

    ctx.actions.expand_template(
        template = ctx.file._chart_yaml_template,
        output = chart_yaml,
        substitutions = {"${name}": ctx.label.name, "${version}": "0.0.1"},
    )

    values_yaml = ctx.actions.declare_file(ctx.label.name + "-values.yaml")
    source_digests = []
    cmds = [
        "cat {} - > {} <<EOF".format(ctx.file.values.path, values_yaml.path),
        "### Generated by app_chart ###",
        "images:",
    ]
    jq_bin = ctx.toolchains["@aspect_bazel_lib//lib:jq_toolchain_type"].jqinfo.bin

    images = ctx.attr.images or {}
    for key, value in images.items():
        key_files = key[DefaultInfo].files
        for file in key_files.to_list():
            image_dir = file

        # keep the leading '/' since helm charts prepend the registry without one
        cmds.append("  {nick}: /{image}@$({jq} -r '.manifests[0].digest' {digest}/index.json)".format(
            nick = value.replace("-", "_"),
            image = value,
            digest = image_dir.path,
            jq = jq_bin.path,
        ))
        source_digests.append(image_dir)
    cmds.append("EOF")

    ctx.actions.run_shell(
        tools = [jq_bin],
        outputs = [values_yaml],
        inputs = ctx.files.values + source_digests,
        command = "\n".join(cmds),
        toolchain = None,
    )

    helm_chart(
        ctx,
        name = ctx.label.name,
        chart = chart_yaml,
        values = values_yaml,
        # TODO(b/72936439): This is currently unused and fixed to 0.0.1.
        version = "0.0.1",
        templates = ctx.files.templates,
        files = ctx.files.files,
        helm = ctx.file._helm,
        out = ctx.outputs.chart,
    )

    return [DefaultInfo(
        runfiles = ctx.runfiles(files = [ctx.outputs.chart]),
        files = depset([ctx.outputs.chart]),
    )]

_app_chart_backend = rule(
    implementation = _impl,
    attrs = {
        "chart": attr.string(
            doc = "the chart name (robot/cloud/cloud-per-robot)",
            mandatory = True,
        ),
        "values": attr.label(
            allow_single_file = True,
            doc = "The values.yaml file.",
        ),
        "templates": attr.label_list(
            allow_empty = True,
            allow_files = True,
            default = [],
            doc = "Files for the chart's templates/ directory.",
        ),
        "files": attr.label_list(
            allow_empty = True,
            allow_files = True,
            default = [],
            doc = "Extra non-template files for the chart's files/ directory.",
        ),
        "images": attr.label_keyed_string_dict(
            allow_empty = True,
            doc = "Images referenced by the chart.",
        ),
        "_chart_yaml_template": attr.label(
            default = Label("//bazel/build_rules/app_chart:Chart.yaml.template"),
            allow_single_file = True,
        ),
        "_helm": attr.label(
            default = Label("@kubernetes_helm//:helm"),
            allow_single_file = True,
        ),
    },
    outputs = {
        "chart": "%{name}-0.0.1.tgz",
    },
    toolchains = ["@aspect_bazel_lib//lib:jq_toolchain_type"],
)

def app_chart(
        name,
        values = None,
        extra_templates = None,
        files = None,
        images = None,
        visibility = None):
    """Macro for a standard Cloud Robotics helm chart.

    This macro establishes two subrules for chart name "foo-cloud":
    - :foo-cloud.push pushes the Docker images for the chart (if relevant).
    - :foo-cloud.snippet-yaml is a snippet of YAML defining the chart, which is
      used by app() to generate an App CR containing multiple inline
      charts.

    Args:
      name: string. Must be in the format {app}-{chart}, where chart is
        robot, cloud, or cloud-per-robot.
      values: file. The values.yaml file.
      extra_templates: list of files. Extra files for the chart's templates/ directory.
      files: list of files. Extra non-template files for the chart's files/ directory.
      images: dict. Images referenced by the chart.
      visibility: Visibility.
    """

    _, chart = name.rsplit("-", 1)
    if name.endswith("cloud-per-robot"):
        chart = "cloud-per-robot"

    if not values:
        if chart == "cloud":
            values = Label("//bazel/build_rules/app_chart:values-cloud.yaml")
        else:
            values = Label("//bazel/build_rules/app_chart:values-robot.yaml")

    # We have a dict of string:target, but bazel rules only support target:string.
    reversed_images = {}
    if images:
        for k, v in images.items():
            reversed_images[v] = k

    _app_chart_backend(
        name = name,
        chart = chart,
        values = values,
        templates = native.glob([chart + "/*.yaml"], allow_empty = True) + (extra_templates or []),
        files = files,
        images = reversed_images,
        visibility = visibility,
    )

    push_all(
        name = name + ".push-all-containers",
        images = images,
    )

    cache_gcr_credentials(
        name = name + ".push",
        target = name + ".push-all-containers",
        visibility = visibility,
    )

    native.genrule(
        name = name + ".snippet-yaml",
        srcs = [name],
        outs = [name + ".snippet.yaml"],
        cmd = """cat <<EOF > $@
    {target}:
      inline: $$(base64 -w 0 $<)
EOF
""".format(name = name, target = chart),
    )


================================================
FILE: bazel/build_rules/app_chart/BUILD.bazel
================================================
exports_files([
    "cache_gcr_credentials.sh.tpl",
    "Chart.yaml.template",
    "push_all.sh.tpl",
    "run_parallel.sh.tpl",
    "values-cloud.yaml",
    "values-robot.yaml",
])


================================================
FILE: bazel/build_rules/app_chart/Chart.yaml.template
================================================
apiVersion: v1
name: ${name}
version: ${version}
# Linter expects an icon.
icon: https://google.com/icon.png


================================================
FILE: bazel/build_rules/app_chart/cache_gcr_credentials.bzl
================================================
def _get_runfile_path(ctx, f):
    """Return the runfiles relative path of f."""
    if ctx.workspace_name:
        return "${RUNFILES}/" + ctx.workspace_name + "/" + f.short_path
    else:
        return "${RUNFILES}/" + f.short_path

def _impl(ctx):
    runfiles = ctx.attr._sh_tpl.default_runfiles.files.to_list()
    runfiles.append(ctx.attr.target.files_to_run.executable)
    runfiles.extend(ctx.attr.target.default_runfiles.files.to_list())

    variables = "PYTHON_RUNFILES=\"${RUNFILES}\" "
    ctx.actions.expand_template(
        template = ctx.file._sh_tpl,
        substitutions = {
            "%{gcr_registry}": ctx.attr.gcr_registry,
            "%{command}": variables + _get_runfile_path(ctx, ctx.attr.target.files_to_run.executable),
        },
        output = ctx.outputs.executable,
        is_executable = True,
    )

    return [DefaultInfo(runfiles = ctx.runfiles(files = runfiles))]

cache_gcr_credentials = rule(
    attrs = {
        "target": attr.label(
            mandatory = True,
        ),
        "gcr_registry": attr.string(
            default = "gcr.io",
            doc = "If set, credentials for this GCR registry's domain will be precached",
        ),
        "_sh_tpl": attr.label(
            default = Label("//bazel/build_rules/app_chart:cache_gcr_credentials.sh.tpl"),
            allow_single_file = True,
        ),
    },
    executable = True,
    implementation = _impl,
)
"""Cache gcr credentials before running a command.

This rule executes docker-credential-gcloud before running the command, and
replaces the binary with a helper that is safe for concurrent execution. Works
around around https://github.com/bazelbuild/rules_docker/issues/511.

Args:
  target: A target that can be run with "bazel run".
  gcr_registry: string. A GCR Docker registry (gcr.io/myproject).
"""


================================================
FILE: bazel/build_rules/app_chart/cache_gcr_credentials.sh.tpl
================================================
#!/usr/bin/env bash

set -eu

function guess_runfiles() {
    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1
    pwd
    popd > /dev/null 2>&1
}

RUNFILES="${PYTHON_RUNFILES:-$(guess_runfiles)}"

# app() uses run_parallel() to push images to GCR, which relies on
# gcloud to get credentials. That, however, has a race condition:
# https://github.com/google/containerregistry/issues/115
# As such, we cache credentials and create a script that prints them to
# replace the racy credential helper. This script is added to to the start of
# PATH. This is harmless if run_parallel() is being used for something else.
# It also saves ~5s of CPU time.
tmp_bin=$(mktemp --tmpdir= -d deploy-XXXXXXXX-bin)
export PATH="${tmp_bin}:${PATH}"
function rm_tmp_bin {
  rm -r "${tmp_bin}"
}
trap rm_tmp_bin EXIT

credential_script=$tmp_bin/docker-credential-gcloud
credential_file=$tmp_bin/docker-credential-gcloud.json
gcp_registry=$(echo "%{gcr_registry}" | cut -d'/' -f 1)
docker-credential-gcloud get <<<"https://${gcp_registry}" > "${credential_file}"
cat > "${credential_script}" << EOF
#!/bin/bash
cat "${credential_file}"
EOF
chmod +x "${credential_script}"

%{command} "$@"


================================================
FILE: bazel/build_rules/app_chart/push_all.bzl
================================================
load("//bazel:container_push.bzl", "container_push")

def _get_runfile_path(ctx, f):
    """Return the runfiles relative path of f."""
    if ctx.workspace_name:
        return "${RUNFILES}/" + ctx.workspace_name + "/" + f.short_path
    else:
        return "${RUNFILES}/" + f.short_path

def _impl(ctx):
    runfiles = ctx.attr._sh_tpl.default_runfiles.files.to_list()
    for target in ctx.attr.push_targets:
        runfiles.append(target.files_to_run.executable)
        runfiles.extend(target.default_runfiles.files.to_list())

    ctx.actions.expand_template(
        template = ctx.file._sh_tpl,
        substitutions = {
            "%{commands}": "\n".join(
                [
                    "if [[ -z \"${TAG:-}\" ]]; then echo >&2 \"$0: TAG environment variable must be set when pushing images.\"; exit 1; fi",
                ] + [
                    "async {command} --repository=\"${{CONTAINER_REGISTRY}}/{repository}\" --tag=\"${{TAG}}\"".format(
                        command = _get_runfile_path(ctx, target.files_to_run.executable),
                        repository = repository,
                    )
                    for target, repository in zip(ctx.attr.push_targets, ctx.attr.images.keys())
                ],
            ),
        },
        output = ctx.outputs.executable,
        is_executable = True,
    )

    return [DefaultInfo(runfiles = ctx.runfiles(files = runfiles))]

_push_all = rule(
    attrs = {
        # Implicit dependencies.
        "push_targets": attr.label_list(
            allow_files = True,
        ),
        "images": attr.string_dict(
            default = {},
        ),
        "_sh_tpl": attr.label(
            default = Label("//bazel/build_rules/app_chart:push_all.sh.tpl"),
            allow_single_file = True,
        ),
    },
    executable = True,
    implementation = _impl,
)

def push_all(name, images = {}, **kwargs):
    """Creates a script to push several container images to a docker registry.
    The registry has to be specified as parameter when invoking the script.

    Args:
      images: dict. Repository names as keys and images to be pushed as values.
    """
    if "push_targets" in kwargs:
        fail("reserved for internal use by push_all macro", attr = "push_targets")

    images = images or {}
    push_targets = []
    for repository, image in images.items():
        push_target = name + "." + repository + ".push"
        push_targets.append(push_target)
        container_push(
            name = push_target,
            image = image,
        )

    _push_all(name = name, images = images, push_targets = push_targets, **kwargs)


================================================
FILE: bazel/build_rules/app_chart/push_all.sh.tpl
================================================
#!/usr/bin/env bash

set -eu

if [[ "$#" -lt 1 ]]; then
  echo "Usage: $0 <container-registry>"
  exit 1
fi
CONTAINER_REGISTRY="$1"

function guess_runfiles() {
    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1
    pwd
    popd > /dev/null 2>&1
}

RUNFILES="${PYTHON_RUNFILES:-$(guess_runfiles)}"

PIDS=()
function async() {
    # Launch the command asynchronously and track its process id.
    PYTHON_RUNFILES=${RUNFILES} "$@" &
    PIDS+=($!)
}

%{commands}

if [[ "${#PIDS[@]}" = 0 ]]; then
    # It is valid to generate this script without pushing any images.
    # Bash before v4.4 considers an empty array an unbound variable and would
    # choke on the for-loop below.
    exit 0
fi

# Wait for all of the subprocesses, failing the script if any of them failed.
exitcode=0
for pid in "${PIDS[@]}"; do
    wait ${pid} || exitcode=$?
done
exit $exitcode


================================================
FILE: bazel/build_rules/app_chart/run_parallel.bzl
================================================
def _get_runfile_path(ctx, f):
    """Return the runfiles relative path of f."""
    if ctx.workspace_name:
        return "${RUNFILES}/" + ctx.workspace_name + "/" + f.short_path
    else:
        return "${RUNFILES}/" + f.short_path

def _impl(ctx):
    runfiles = ctx.attr._sh_tpl.default_runfiles.files.to_list()
    for target in ctx.attr.targets:
        runfiles.append(target.files_to_run.executable)
        runfiles.extend(target.default_runfiles.files.to_list())

    ctx.actions.expand_template(
        template = ctx.file._sh_tpl,
        substitutions = {
            "%{commands}": "\n".join([
                "async \"%s\" \"$@\"" % _get_runfile_path(ctx, command.files_to_run.executable)
                for command in ctx.attr.targets
            ]),
        },
        output = ctx.outputs.executable,
        is_executable = True,
    )

    return [DefaultInfo(runfiles = ctx.runfiles(files = runfiles))]

run_parallel = rule(
    attrs = {
        "targets": attr.label_list(
            allow_empty = False,
            mandatory = True,
        ),
        "_sh_tpl": attr.label(
            default = Label("//bazel/build_rules/app_chart:run_parallel.sh.tpl"),
            allow_single_file = True,
        ),
    },
    executable = True,
    implementation = _impl,
)
"""Run multiple targets in parallel.

This rule builds a "bazel run" target that runs a series of subtargets in
parallel. If a subtarget has errors, execution results in an error when all
subtargets have completed.

Args:
  targets: A list of targets that can be run with "bazel run".
"""


================================================
FILE: bazel/build_rules/app_chart/run_parallel.sh.tpl
================================================
#!/usr/bin/env bash

set -eu

function guess_runfiles() {
    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1
    pwd
    popd > /dev/null 2>&1
}

RUNFILES="${PYTHON_RUNFILES:-$(guess_runfiles)}"

PIDS=()
function async() {
    # Launch the command asynchronously and track its process id.
    PYTHON_RUNFILES=${RUNFILES} "$@" &
    PIDS+=($!)
}

%{commands}

# Wait for all of the subprocesses, failing the script if any of them failed.
exitcode=0
for pid in "${PIDS[@]}"; do
    wait ${pid} || exitcode=$?
done
exit $exitcode


================================================
FILE: bazel/build_rules/app_chart/values-cloud.yaml
================================================
domain: "example.com"
project: "my-gcp-project"
deploy_environment: "GCP"
registry: "gcr.io/my-gcp-project"
robots: []
region: example-gcp-region
# Token Vendor feature flags
use_tv_k8s_verbose: false

================================================
FILE: bazel/build_rules/app_chart/values-robot.yaml
================================================
domain: "example.com"
project: "my-gcp-project"
deploy_environment: "GCP"
registry: "gcr.io/my-gcp-project"

robot:
  name: ""


================================================
FILE: bazel/build_rules/copy.bzl
================================================
"""Macros to help rearrange files."""

def copy_files(name, srcs, outdir, visibility = None):
    """Creates copies of files within a directory.

    For each source file, a copy with the same basename is created in outdir. A
    filegroup target <name> is created containing the new files.

    Args:
      srcs: list of files.
      outdir: output directory.
      visibility: handled through to the targets.
    """
    outs = []
    for src in srcs:
        filename = src.split("/")[-1]
        out = "%s/%s" % (outdir, filename)
        native.genrule(
            name = "%s_%s" % (name, filename),
            srcs = [src],
            outs = [out],
            cmd = "cp $< $@",
            visibility = visibility,
        )
        outs.append(out)

    native.filegroup(
        name = name,
        srcs = outs,
        visibility = visibility,
    )


================================================
FILE: bazel/build_rules/helm_chart.bzl
================================================
def helm_chart(ctx, name, chart, files, templates, values, version, helm, out):
    """Starlark function that builds a helm chart.

    Args:
      name: string. Must match the name in Chart.yaml.
      chart: file. The Chart.yaml file.
      files: list of non-template files to put in files/.
      templates: list of template files.
      values: file. The values.yaml file.
      version: string. Overwrites any version in Chart.yaml.
      helm: file. The Helm tool.
      out: file. The file that the chart is built to.
    """

    cmd = """
      mkdir {name} {name}/templates
      cp {chart} {name}/Chart.yaml
      cp {values} {name}/values.yaml
      """.format(name = name, chart = chart.path, values = values.path)
    if templates:
        template_files = " ".join([t.path for t in templates])

        # Use a single cp invocation to detect filename clashes.
        cmd += "cp {templates} {name}/templates\n".format(name = name, templates = template_files)
    if files:
        cmd += "mkdir {name}/files\n".format(name = name)
        files_locations = " ".join([f.path for f in files])

        # Use a single cp invocation to detect filename clashes.
        cmd += "cp {files} {name}/files\n".format(name = name, files = files_locations)
    cmd += """
      # Linter is too noisy, swallow its output when not failing
      {helm} lint --strict {name} >/dev/null 2>&1 || \\
        {helm} lint --strict {name}
      {helm} package \\
          --save=false --version={version} {name} \\
        | (grep -v "Successfully packaged" || true)
      mv $(basename {output}) {output}
      rm -rf {name}""".format(name = name, version = version, helm = helm.path, output = out.path)
    ctx.actions.run_shell(
        inputs = [chart, values] + templates + (files or []),
        tools = [helm],
        outputs = [out],
        command = cmd,
        toolchain = None,
    )


================================================
FILE: bazel/build_rules/helm_template.bzl
================================================
def helm_template(name, release_name, chart, values, namespace = None, helm_version = 2):
    """Locally expand a helm chart.

    Args:
      chart: build label, referencing the chart to expand.
      values: label. File with expand-time values.
    """

    tool = ""
    cmd = ""
    if helm_version == 2:
        tool = "@kubernetes_helm//:helm"
        cmd = "$(location {tool}) template --name {name} --namespace {namespace} --values $(location {values}) $(location {chart}) > $@".format(name = release_name, namespace = namespace or "default", chart = chart, tool = tool, values = values)
    elif helm_version == 3:
        tool = "@kubernetes_helm3//:helm"
        cmd = "$(location {tool}) template {name} $(location {chart}) --namespace {namespace} --values $(location {values}) > $@".format(name = release_name, namespace = namespace or "default", chart = chart, tool = tool, values = values)
    else:
        fail("Unsupported helm version. Expected {2,3}, got ", helm_version)

    native.genrule(
        name = name,
        srcs = [chart, values],
        outs = [name + ".yaml"],
        cmd = cmd,
        tools = [tool],
    )


================================================
FILE: bazel/container_push.bzl
================================================
load("@rules_oci//oci:defs.bzl", "oci_push")

def container_push(*args, **kwargs):
    """Creates a script to push a container image to a Docker registry. The
    target name must be specified when invoking the push script."""
    if "repository" in kwargs:
        fail(
            "Cannot set 'repository' attribute on container_push",
            attr = "repository",
        )
    kwargs["repository"] = "IGNORE"
    oci_push(*args, **kwargs)


================================================
FILE: bazel/debug_repository.bzl
================================================
"""Debug util for repository definitions."""

def debug_repository(repo, *fields):
    """debug_repository(repo) identifies which version of a repository has been
    defined in the WORKSPACE by printing some of its fields. Example:

    # at the bottom of the WORKSPACE file
    load("//bazel:debug_repository.bzl", "debug_repository")

    debug_repository("org_golang_x_net")

    If needed, you can override the printed fields by passing additional parameters:

    debug_repository("io_grpc_grpc_java", "patches", "urls")
    """

    if len(fields) == 0:
        fields = ["branch", "commit", "tag", "url", "urls"]

    rule = native.existing_rule(repo)
    if rule == None:
        print(repo, "not found")
        return

    for f in fields:
        if f in rule and len(rule[f]) > 0:
            print(repo, f, rule[f])


================================================
FILE: config.sh.tmpl
================================================
#!/usr/bin/env bash

### Required settings ###

# Project ID of your Cloud Robotics GCP project. This project can be created
# for you as part of the Terraform setup, or it can be created and configured
# manually, then imported with `deploy.sh set-project` or `terraform import`.
GCP_PROJECT_ID=my-project

# GCP region and zone where resources should be created.
GCP_REGION=europe-west1
GCP_ZONE=europe-west1-c

### Optional settings ###

# The Docker registry all Cloud Robotics images are deployed to when installing
# from sources. It is ignored during binary installs.
# If unset, defaults to "gcr.io/${GCP_PROJECT_ID}"
#CLOUD_ROBOTICS_CONTAINER_REGISTRY=gcr.io/my-project

# A space-separated list of GCP alphanumeric project IDs for private image
# repositories. The installer will provision GCR access to these projects,
# both for the gke-node service account and for the robot service account.
#PRIVATE_DOCKER_PROJECTS="my-project my-other-project"

# A Google Group that should be a co-owner of the created GCP project.
#CLOUD_ROBOTICS_SHARED_OWNER_GROUP=my-group@googlegroups.com

# If you want to store your Terraform state in a GCS bucket, give a bucket name
# and a subdirectory of the bucket here. See
# https://www.terraform.io/docs/backends/types/gcs.html for docs.
#TERRAFORM_GCS_BUCKET="my-gcs-bucket"
#TERRAFORM_GCS_PREFIX="my/sub/directory"

# Symmetric cookie encryption key for the oauth2-proxy. Generate with:
# python -c 'import os,base64; print base64.urlsafe_b64encode(os.urandom(16))'
#CLOUD_ROBOTICS_COOKIE_SECRET=A_CyACoujODhfn2yDMy5tw==

# Oauth2 client ID and client secret from
# https://console.cloud.google.com/apis/credentials. If you leave these empty,
# you won't be able to log in with a browser (but CLI access will work fine).
#CLOUD_ROBOTICS_OAUTH2_CLIENT_ID=....apps.googleusercontent.com
#CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET=...

# Domain to be used for the ingress
# If unset, defaults to "www.endpoints.${GCP_PROJECT_ID}.cloud.goog"
#CLOUD_ROBOTICS_DOMAIN=www.example.com

# Enable google cloud robotics layer 2
APP_MANAGEMENT=true

# Enable google cloud robotics layer 1
ONPREM_FEDERATION=true

# Disable the secret manager integration by default
GKE_SECRET_MANAGER_PLUGIN=false


================================================
FILE: current_versions.txt
================================================
{
  "cert-manager": "1.16.3",
  "ingress-nginx": "1.8.4",
  "oauth2-proxy": "7.5.1",
  "stackdriver-logging-agent": "1.9.5"
}


================================================
FILE: deploy.sh
================================================
#!/bin/bash
#
# Copyright 2019 The Cloud Robotics Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Manage a deployment

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "${DIR}/scripts/common.sh"
source "${DIR}/scripts/config.sh"
source "${DIR}/scripts/include-config.sh"

set -o pipefail -o errexit

PROJECT_NAME="cloud-robotics"
RUNFILES_ROOT="_main"

if is_source_install; then
  # Not using bazel run to not clobber the bazel-bin dir
  TERRAFORM="${DIR}/bazel-out/../../../external/+non_module_deps+hashicorp_terraform/terraform"
  HELM_COMMAND="${DIR}/bazel-out/../../../external/+non_module_deps+kubernetes_helm/helm"
  SYNK_COMMAND="${DIR}/bazel-bin/src/go/cmd/synk/synk_/synk"
else
  TERRAFORM="${DIR}/bin/terraform"
  HELM_COMMAND="${DIR}/bin/helm"
  SYNK_COMMAND="${DIR}/bin/synk"
fi

TERRAFORM_DIR="${DIR}/src/bootstrap/cloud/terraform"
TERRAFORM_APPLY_FLAGS=${TERRAFORM_APPLY_FLAGS:- -auto-approve}
# utility functions

function include_config_and_defaults {
  include_config "$1"

  CLOUD_ROBOTICS_DOMAIN=${CLOUD_ROBOTICS_DOMAIN:-"www.endpoints.${GCP_PROJECT_ID}.cloud.goog"}
  APP_MANAGEMENT=${APP_MANAGEMENT:-false}
  ONPREM_FEDERATION=${ONPREM_FEDERATION:-true}
  GKE_SECRET_MANAGER_PLUGIN=${GKE_SECRET_MANAGER_PLUGIN:-false}

  # lets-encrypt is used as the default certificate provider for backwards compatibility purposes
  CLOUD_ROBOTICS_CERTIFICATE_PROVIDER=${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER:-lets-encrypt}
  CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME=${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME:-GCP_PROJECT_ID}
  CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION=${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION:-GCP_PROJECT_ID}

  CLOUD_ROBOTICS_OWNER_EMAIL=${CLOUD_ROBOTICS_OWNER_EMAIL:-$(gcloud config get-value account)}
  CLOUD_ROBOTICS_CTX=${CLOUD_ROBOTICS_CTX:-"gke_${GCP_PROJECT_ID}_${GCP_ZONE}_${PROJECT_NAME}"}
}

function update_config_var {
  cloud_bucket="gs://${1}-cloud-robotics-config"
  name="${2}"
  value="${3}"

  config_file="$(mktemp)"
  gcloud storage cp "${cloud_bucket}/config.sh" "${config_file}" 2>/dev/null || return

  save_variable "${config_file}" "${name}" "${value}"

  gcloud storage mv "${config_file}" "${cloud_bucket}/config.sh"
}

function prepare_source_install {
  # For whatever reasons different combinations of bazel environemnt seem to
  # work differently wrt bazel-bin. This hack ensure that both synk and the
  # files that synk will install are in bazel-bin
  tmpdir="$(mktemp -d)"
  bazel ${BAZEL_FLAGS} build //src/go/cmd/synk
  cp -a ${DIR}/bazel-bin/src/go/cmd/synk/synk_/synk ${tmpdir}/synk

  bazel ${BAZEL_FLAGS} build \
      "@hashicorp_terraform//:terraform" \
      "@kubernetes_helm//:helm" \
      //src/app_charts/base:base-cloud \
      //src/app_charts/platform-apps:platform-apps-cloud \
      //src/app_charts:push \
      //src/bootstrap/cloud:setup-robot.digest \
      //src/go/cmd/setup-robot:setup-robot.push

  mkdir -p ${DIR}/bazel-bin/src/go/cmd/synk/synk_/
  mv -n ${tmpdir}/synk ${DIR}/bazel-bin/src/go/cmd/synk/synk_/synk
  rm -f ${tmpdir}/synk
  rmdir ${tmpdir} || /bin/true

  # TODO(rodrigoq): the artifactregistry API would be enabled by Terraform, but
  # that doesn't run until later, as it needs the digest of the setup-robot
  # image. Consider splitting prepare_source_install into source_install_build
  # and source_install_push and using Terraform to enable the API in between.
  gcloud services enable artifactregistry.googleapis.com \
    --project "${GCP_PROJECT_ID}"

  # `setup-robot.push` is the first container push to avoid a GCR bug with parallel pushes on newly
  # created projects (see b/123625511).
  local oldPwd
  oldPwd=$(pwd)
  cd ${DIR}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh.runfiles/${RUNFILES_ROOT}
  ${DIR}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh \
    --repository="${CLOUD_ROBOTICS_CONTAINER_REGISTRY}/setup-robot" \
    --tag="latest"

  # The tag variable must be called 'TAG', see cloud-robotics/bazel/container_push.bzl
  # Running :push outside the build system shaves ~3 seconds off an incremental build.
  cd ${DIR}/bazel-bin/src/app_charts/push.runfiles/${RUNFILES_ROOT}
  TAG="latest" ${DIR}/bazel-bin/src/app_charts/push "${CLOUD_ROBOTICS_CONTAINER_REGISTRY}"
  cd ${oldPwd}
}

function terraform_exec {
  ( cd "${TERRAFORM_DIR}" && ${TERRAFORM} "$@" )
}

function terraform_init {
  local ROBOT_IMAGE_DIGEST
  ROBOT_IMAGE_DIGEST=$(cat bazel-bin/src/bootstrap/cloud/setup-robot.digest)

  # We only need to create dns resources if a custom domain is used.
  local CUSTOM_DOMAIN
  if [[ "${CLOUD_ROBOTICS_DOMAIN}" != "www.endpoints.${GCP_PROJECT_ID}.cloud.goog" ]]; then
    CUSTOM_DOMAIN="${CLOUD_ROBOTICS_DOMAIN}"
  fi

  # This variable is set by src/bootstrap/cloud/run-install.sh for binary installs
  local CRC_VERSION
  if [[ -z "${TARGET}" ]]; then
    # TODO(ensonic): keep this in sync with the nightly release script
    VERSION=${VERSION:-"0.1.0"}
    if [[ -d .git ]]; then
      SHA=$(git rev-parse --short HEAD)
    else
      echo "WARNING: no git dir and no \$TARGET env set"
      SHA="unknown"
    fi
    CRC_VERSION="crc-${VERSION}/crc-${VERSION}+${SHA}"
  else
    CRC_VERSION="${TARGET%.tar.gz}"
  fi

  cat > "${TERRAFORM_DIR}/terraform.tfvars" <<EOF
# autogenerated by deploy.sh, do not edit!
name = "${GCP_PROJECT_ID}"
id = "${GCP_PROJECT_ID}"
domain = "${CUSTOM_DOMAIN}"
zone = "${GCP_ZONE}"
region = "${GCP_REGION}"
shared_owner_group = "${CLOUD_ROBOTICS_SHARED_OWNER_GROUP}"
robot_image_reference = "${SOURCE_CONTAINER_REGISTRY}/setup-robot@${ROBOT_IMAGE_DIGEST}"
crc_version = "${CRC_VERSION}"
certificate_provider = "${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}"
cluster_type = "${GKE_CLUSTER_TYPE}"
datapath_provider = "${GKE_DATAPATH_PROVIDER}"
onprem_federation = ${ONPREM_FEDERATION}
secret_manager_plugin = ${GKE_SECRET_MANAGER_PLUGIN}
EOF

# Add certificate information if the configured provider requires it
  if [[ ! "none self-signed" =~ (" "|^)"${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}"(" "|$) ]]; then
    cat >> "${TERRAFORM_DIR}/terraform.tfvars" <<EOF
certificate_subject_common_name = "${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME}"
certificate_subject_organization = "${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION}"
EOF

  if [[ -n "${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT}" ]]; then
      cat >> "${TERRAFORM_DIR}/terraform.tfvars" <<EOF
certificate_subject_organizational_unit = "${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT}"
EOF
    fi
  fi

  echo 'additional_regions = {' >> "${TERRAFORM_DIR}/terraform.tfvars"
  local AR
  for AR in "${ADDITIONAL_REGIONS[@]}"; do
    local AR_NAME
    local AR_REGION
    local AR_ZONE

    AR_NAME=$(jq -r .name <<<"${AR}")
    AR_REGION=$(jq -r .region <<<"${AR}")
    AR_ZONE=$(jq -r .zone <<<"${AR}")

    cat >> "${TERRAFORM_DIR}/terraform.tfvars" <<EOF
    ${AR_NAME} = { region: "${AR_REGION}", zone: "${AR_ZONE}" },
EOF
  done
  echo '}' >> "${TERRAFORM_DIR}/terraform.tfvars"

# Docker private projects

  if [[ -n "${PRIVATE_DOCKER_PROJECTS:-}" ]]; then
    cat >> "${TERRAFORM_DIR}/terraform.tfvars" <<EOF
private_image_repositories = ["${PRIVATE_DOCKER_PROJECTS// /\", \"}"]
EOF
  fi

# Terraform bucket

  if [[ -n "${TERRAFORM_GCS_BUCKET:-}" ]]; then
    cat > "${TERRAFORM_DIR}/backend.tf" <<EOF
# autogenerated by deploy.sh, do not edit!
terraform {
  backend "gcs" {
    bucket = "${TERRAFORM_GCS_BUCKET}"
    prefix = "${TERRAFORM_GCS_PREFIX}"
  }
}
EOF
  else
    rm -f "${TERRAFORM_DIR}/backend.tf"
  fi

  terraform_exec init -upgrade -reconfigure \
    || die "terraform init failed"
}

function terraform_apply {
  terraform_init

  terraform_exec apply ${TERRAFORM_APPLY_FLAGS} \
    || die "terraform apply failed"
  terraform_post
}

function terraform_post {
  local OLD_CLOUD_ROBOTICS_CTX
  local location

  OLD_CLOUD_ROBOTICS_CTX="${CLOUD_ROBOTICS_CTX}"
  CLOUD_ROBOTICS_CTX=$(gke_context_name "${GCP_PROJECT_ID}" "cloud-robotics" "${GCP_REGION}" "${GCP_ZONE}")
  [[ -z "${CLOUD_ROBOTICS_CTX}" ]] && die "no cloud-robotics cluster found"
  if [[ "${OLD_CLOUD_ROBOTICS_CTX}" != "${CLOUD_ROBOTICS_CTX}" ]]; then
    echo "updating CLOUD_ROBOTICS_CTX from ${OLD_CLOUD_ROBOTICS_CTX} to ${CLOUD_ROBOTICS_CTX}"
    update_config_var ${GCP_PROJECT_ID} "CLOUD_ROBOTICS_CTX" "${CLOUD_ROBOTICS_CTX}"
  fi
}

function terraform_delete {
  terraform_init
  terraform_exec destroy -auto-approve || die "terraform destroy failed"
}

function helm_region_shared {
  local CLUSTER_CONTEXT
  local CLUSTER_DOMAIN
  local INGRESS_IP
  local CLUSTER_REGION
  local CLUSTER_ZONE
  local CLUSTER_NAME

  CLUSTER_CONTEXT="${1}"
  CLUSTER_DOMAIN="${2}"
  INGRESS_IP="${3}"
  CLUSTER_REGION="${4}"
  CLUSTER_ZONE="${5}"
  CLUSTER_NAME="${6}"

  gke_get_credentials "${GCP_PROJECT_ID}" "${CLUSTER_NAME}" "${CLUSTER_REGION}" "${CLUSTER_ZONE}"

  # Wait for the GKE cluster to be reachable.
  i=0
  until kubectl --context "${CLUSTER_CONTEXT}" get serviceaccount default &>/dev/null; do
    sleep 1
    i=$((i + 1))
    if ((i >= 60)) ; then
      # Try again, without suppressing stderr this time.
      if ! kubectl --context "${CLUSTER_CONTEXT}" get serviceaccount default >/dev/null; then
        die "'kubectl get serviceaccount default' failed"
      fi
    fi
  done

  local BASE_NAMESPACE
  BASE_NAMESPACE="default"

  # Remove old unmanaged cert
  if ! kubectl --context "${CLUSTER_CONTEXT}" get secrets cluster-authority -o yaml | grep -q "cert-manager.io/certificate-name: selfsigned-ca"; then
    kubectl --context "${CLUSTER_CONTEXT}" delete secrets cluster-authority 2> /dev/null || true
  fi

  # Delete permissive binding if it exists from previous deployments
  if kubectl --context "${CLUSTER_CONTEXT}" get clusterrolebinding permissive-binding &>/dev/null; then
    kubectl --context "${CLUSTER_CONTEXT}" delete clusterrolebinding permissive-binding
  fi


  local values
  values=(
    --set-string "domain=${CLUSTER_DOMAIN}"
    --set-string "ingress_ip=${INGRESS_IP}"
    --set-string "project=${GCP_PROJECT_ID}"
    --set-string "region=${CLUSTER_REGION}"
    --set-string "registry=${SOURCE_CONTAINER_REGISTRY}"
    --set-string "owner_email=${CLOUD_ROBOTICS_OWNER_EMAIL}"
    --set-string "app_management=${APP_MANAGEMENT}"
    --set-string "onprem_federation=${ONPREM_FEDERATION}"
    --set-string "certificate_provider=${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}"
    --set-string "deploy_environment=${CLOUD_ROBOTICS_DEPLOY_ENVIRONMENT}"
    --set-string "oauth2_proxy.client_id=${CLOUD_ROBOTICS_OAUTH2_CLIENT_ID}"
    --set-string "oauth2_proxy.client_secret=${CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET}"
    --set-string "oauth2_proxy.cookie_secret=${CLOUD_ROBOTICS_COOKIE_SECRET}"
    --set "use_tv_verbose=${CRC_USE_TV_VERBOSE}"
  )

  ${SYNK_COMMAND} --context "${CLUSTER_CONTEXT}" init
  echo "synk init done"

  echo "installing base-cloud to ${CLUSTER_CONTEXT}..."
  ${HELM_COMMAND} --kube-context "${CLUSTER_CONTEXT}" template -n base-cloud --namespace=${BASE_NAMESPACE} "${values[@]}" \
      ./bazel-bin/src/app_charts/base/base-cloud-0.0.1.tgz \
    | ${SYNK_COMMAND} --context "${CLUSTER_CONTEXT}" apply base-cloud -n ${BASE_NAMESPACE} -f - \
    || die "Synk failed for base-cloud"

  # This is the main region. Only run this here!
  if [[ "${CLUSTER_NAME}" = "${PROJECT_NAME}" ]]; then
    echo "installing platform-apps-cloud to ${CLOUD_ROBOTICS_CTX}..."
    ${HELM_COMMAND} --kube-context "${CLUSTER_CONTEXT}" template -n platform-apps-cloud "${values[@]}" \
        ./bazel-bin/src/app_charts/platform-apps/platform-apps-cloud-0.0.1.tgz \
      | ${SYNK_COMMAND} --context "${CLUSTER_CONTEXT}" apply platform-apps-cloud -f - \
      || die "Synk failed for platform-apps-cloud"
  fi
}

function helm_main_region {
  local INGRESS_IP
  INGRESS_IP=$(terraform_exec output ingress-ip | tr -d '"')

  helm_region_shared \
    "${CLOUD_ROBOTICS_CTX}" \
    "${CLOUD_ROBOTICS_DOMAIN}" \
    "${INGRESS_IP}" \
    "${GCP_REGION}" \
    "${GCP_ZONE}" \
    "${PROJECT_NAME}"
}

function helm_additional_region {
  local ar_description
  ar_description="${1}"

  local AR_NAME
  local AR_REGION
  local AR_ZONE

  AR_NAME=$(jq -r .name <<<"${ar_description}")
  AR_REGION=$(jq -r .region <<<"${ar_description}")
  AR_ZONE=$(jq -r .zone <<<"${ar_description}")

  local CLUSTER_NAME
  CLUSTER_NAME="${AR_NAME}-ar-cloud-robotics"

  local INGRESS_IP
  INGRESS_IP=$(terraform_exec output -json ingress-ip-ar | jq -r ."\"${CLUSTER_NAME}\"")

  helm_region_shared \
    $(gke_context_name "${GCP_PROJECT_ID}" "${CLUSTER_NAME}" "${AR_REGION}" "${AR_ZONE}") \
    "${AR_NAME}.${CLOUD_ROBOTICS_DOMAIN}" \
    "${INGRESS_IP}" \
    "${AR_REGION}" \
    "${AR_ZONE}" \
    "${CLUSTER_NAME}"
}

function helm_charts {
  helm_main_region

  local AR
  for AR in "${ADDITIONAL_REGIONS[@]}"; do
    helm_additional_region "${AR}"
  done
}

# commands

function set_config {
  local project_id="$1"
  ${DIR}/scripts/set-config.sh "${project_id}"
}

function create {
  include_config_and_defaults $1
  if is_source_install; then
    prepare_source_install
  fi
  terraform_apply
  helm_charts
}

function delete {
  include_config_and_defaults $1
  if is_source_install; then
    bazel ${BAZEL_FLAGS} build "@hashicorp_terraform//:terraform"
  fi
  terraform_delete
}

# Alias for create.
function update {
  create $1
}

# This is a shortcut for skipping Terraform config checks if you know the config has not changed.
function fast_push {
  include_config_and_defaults $1
  if is_source_install; then
    prepare_source_install
  fi
  helm_charts
}

# This is a shortcut for skipping building and applying Terraform configs if you know the build has not changed.
function update_infra {
  include_config_and_defaults $1
  terraform_apply
}

# main
if [[ "$#" -lt 2 ]] || [[ ! "$1" =~ ^(set_config|create|delete|update|fast_push|update_infra)$ ]]; then
  die "Usage: $0 {set_config|create|delete|update|fast_push|update_infra} <project id>"
fi

# log and call arguments verbatim:
log $2 $0 $1
"$@"


================================================
FILE: docs/.gitignore
================================================
# Files created when running Jekyll locally, following
# https://help.github.com/en/articles/setting-up-your-github-pages-site-locally-with-jekyll
.sass-cache/
_site/
Gemfile
Gemfile.lock


================================================
FILE: docs/_config.yml
================================================
theme: jekyll-theme-slate


================================================
FILE: docs/concepts/app-management.md
================================================
# App Management

The Cloud Robotics Core application management (Layer 2) makes it easy to define and deploy
arbitrary applications across a fleet of cloud and robot clusters. The
[Helm v2 chart format](https://helm.sh/docs/developing_charts/) is used to define an application
at the scope of a single cluster. Additional custom resources tie them together to a
cross-cluster application and define their deployment. It relies on the Cloud Robotics Core
[federation layer](federation.md) to distribute the resources to the right clusters.

## App resource

The App resource defines a Cloud Robotics Core application by simply describing Helm charts for
two classes of clusters: cloud and robots. Charts may be specified inline as base64-encoded Helm
chart files or by referencing a Helm repository. App resources will be deployed to the cloud
cluster.

Example 1: application referencing charts from helm repositories

```yaml
apiVersion: apps.cloudrobotics.com/v1alpha1
kind: App
metadata:
  name: ros-v1
spec:
  repository: https://my.repo
  version: 1.2.1
  components:
    cloud:
      name: ros-cloud
    robot:
      name: ros-robot
```

Example 2: application using inline as base64-encoded charts (development workflow)

```yaml
apiVersion: apps.cloudrobotics.com/v1alpha1
kind: App
metadata:
  name: ros-v1
spec:
  components:
    cloud:
      chart:
        inline: <base64>
    robot:
      chart:
        inline: <base64>
```

Right now we only have bazel build rules to produce inline charts.

## AppRollout Resource

An AppRollout describes how a defined App should be deployed across a fleet of clusters. It allows
to flexibly select robots which should run an application and to inject fine-grained configuration.

Example 3: AppRollout with different configuration options per target

```yaml
apiVersion: apps.cloudrobotics.com/v1alpha1
kind: AppRollout
metadata:
  name: ros-stable
  labels:
    app.kubernetes.io/name: ros
    role: navtest
    release: stable
spec:
  appName: ros-v1
  cloud:
    values:
      override: foo
  robots:
  - selector:
      matchLabels:
        model: mir100
    values:
      override1: bar
  - selector:
      matchLabels:
        model: mir200
    values:
      override1: baz
    version: v1.2.2   # Chart version override for canarying
```

AppRollouts are deployed into the cloud cluster, where a controller (app-rollout-controller) handles them.
The controller applies the specified selectors and creates or updates the internal
ChartAssignments. A ChartAssignment represents a single instance of a chart that should be
installed into a single cluster. These internal objects describe the task of installing parts of
the application.

The federation layer will sync ChartAssignments to robots as needed. The actual
installation is done by another controller (chart-assignment-controller), this
time running both in the cloud and on the robots. The AppRollout controller will
watch the status updates and consolidate the information into status updates on
the AppRollout.

## Sharing secrets

If you create a Secret in the `default` namespace labelled
`cloudrobotics.com/copy-to-chart-namespaces=true`, it will be copied into all
namespaces created by the chart-assignment-controller. This is useful for
cluster-specific license keys that can be used by applications.

## Opt a pod out of status checking

During rollout, the chart-assignment-controller checks for Pods in the rollout being `Running` or `Completed`.
In some cases, this check is not necessary or might need to be opted out of.
In this case, add a label `cloudrobotics.com/opt-out-error-checking=true` to your pods. Adding this
instructs the chart-assignment-controller to not block the status from reaching `Ready`.


================================================
FILE: docs/concepts/config.md
================================================
# Project configuration

The project configuration that one has entered during the initial setup is
stored with the project in GCS. One can look at the options with the following
command:

```shell
gcloud storage cat gs://${PROJECT_ID}-cloud-robotics-config/config.sh
```

The settings contained in the config file are used by terraform to setup the
project infrastructure and used by the cloud and chart-assignment-controller services running
in kubernetes to configure apps.

The terraform support is encapsulated in deploy.sh that creates a temporary
`terraform.tfvars` file.

To support configuring apps, we pass the settings to app-rollout-controller where they are
provided as additional variables for helm templating. The command below prints
the settings we pass to app-rollout-controller:

```shell
kubectl get deployment app-rollout-controller -o=jsonpath='{.spec.template.spec.containers[0].args[0]}'
```



================================================
FILE: docs/concepts/device_identity.md
================================================
# Device Identity

Device Identity, part of Layer 1, provides an identity for robot clusters and
services to integrate those identities into a cloud based IAM system.

The following components are part of the whole setup:
* Cloud:
  * `IAM`: Cloud Identity and Access Management
  * `Kubernetes configmaps`: used as a Key Management Service
  * `Token Vendor`: token exchange service for OAuth2 service accounts
  * `robot service-account`: a GCP IAM service account that has the union of
    permissions that applications running on the robot cluster require
* Robot cluster (on-prem or edge):
  * `Metadata Server`: provides default credentials + project metadata
  * `Setup`: special app used to register the workcell
  * `<App>`: any app accessing the cloud

The following chapters explain the flows in more detail. Further information
about the Token Vendor can be found in its
[docs](https://github.com/googlecloudrobotics/core/tree/master/src/go/cmd/token-vendor/README.md)

## Setup

The setup flow is used to register a new robot cluster to a cloud project.

![setup](device_identity_setup.png)

* (1) (Admin-)user runs `Setup`, which generates a RSA key-pair and stores it as
  a K8S secret
* (2) `Setup` uploads the public key to `Token Vendor`
* (3) `Token Vendor` stores key in `Kubernetes`


## Authentication

The authentication flow is used to transparently make cloud API calls work for
on-prem robot clusters.

![setup](device_identity_auth.png)

* `<App>` creates an API client without loading any custom key material
* (1) API client library probes `Metadata Server` to get ADCs (Application
  Default Credentials)
* (2) `Metadata Server` talks to `Token Vendor` get an Access Token for the
  `robot service-account`
* (3) `Token Vendor` verifies the key the request has been signed with against
  the device registry
* (4) `Token Vendor` gets an Access Token for the `robot service-account` from
  `IAM`
* `Token Vendor` returns Access Token through `Metadata Server` to the
  `<App>` and that can use it to call Cloud APIs under the scope of the
  `robot service-account`



================================================
FILE: docs/concepts/federation.md
================================================
# Federation

Federation, part of Layer 1, is responsible for synchronizing the state between robot and cloud
clusters. Configuration state in Cloud Robotics Core is primarily expressed through [custom
resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
by platform and user applications alike. Our federation system enables other components to use
custom resources locally without needing to be aware of the multi cluster setup and the quality
of the network connection.

## Semantics
A Kubernetes resource is typically divided into a [“spec” and a “status”](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#object-spec-and-status)
section. The `spec` section expresses the intent of the resource, typically authored by a user
or another application along with all its metadata.
The `status` section must generally only be written to by the controller that is responsible for
realizing the specification. Consequently, `spec` and `status` typically each have one distinct
author.

For federating resources, this means that `spec` and `status` of a resource are owned by at most
one cluster respectively (possibly the same one). The cluster owning the spec is also the main
owner of the resource overall and controls its lifecycle, i.e. deletion.
A resource’s spec is always synced from an upstream cluster to a downstream
cluster and its status synced back from downstream to upstream.

All resources of a specific type may either be synchronized to all robots or to exactly one robot.
There is no direct synchronization between robots. However, a robot may create a resource in the
cloud cluster that will be distributed to other robots.

If a resource owned by the upstream cluster has been synchronized to one or more
downstream clusters, it can only be permanently deleted upstream: if deleted
downstream, it will be recreated. If deleted in the upstream cluster, it will be
asynchronously deleted in other clusters that hold a copy of the resource.
Upstream deletion can complete before the downstream resource is deleted.

## cr-syncer
The cr-syncer component (Custom Resource Syncer) is a controller that runs inside each robot
cluster. It is connected to the Kubernetes API servers of the cloud and the robot cluster alike
and continuously watches for updates on custom resources. The controller contains retry and resync
logic to address intermittent connectivity.

![federation](federation.png)

The behavior of the cr-syncer can be configured per custom resource definition (CRD) by setting
annotations on its CRD:

* `cr-syncer.cloudrobotics.com/spec-source`: may be `cloud` or `robot`. It determines which
  cluster type owns metadata, spec, and lifecycle of all resources of the CRD. It implies
  that the other cluster type owns the status section.
* `cr-syncer.cloudrobotics.com/filter-by-robot-name`: a boolean that determines whether resources
  will be synced to all robots or just a single one. An individual resource is labeled with
  `cloudrobotics.com/robot-name` to indicate which robot it should be synced to. If the label
  is missing on a resource, it will not be synced at all.
* `cr-syncer.cloudrobotics.com/status-subtree`: a string key, which defines which sub-section of
  the resource status is synced from the downstream cluster. This lets you split
  a resource’s status into `robot` and `cloud` sections, for example. Using this
  annotation is generally discouraged as it likely points to a flaw in the
  modeling of the respective CRD.

## Deletion
When the cr-syncer sees a resource in the downstream cluster with no
corresponding resource in upstream cluster, it deletes it. This handles orphaned
resources when the upstream resource was deleted while the cr-syncer was
restarting. It also means that you can't create a resource directly in the
downstream cluster. The upstream resource is identified using the namespace and
name, but not the UID, so deletion and recreation upstream may result in an
update in the downstream cluster.

> **Note**: if you create a resource directly in the downstream cluster, the
> behavior will depend on how the CRD is annotated.  If `filter-by-robot-name`
> is false, the cr-syncer will delete all downstream resources that don't
> correspond to upstream resources. This means that by listing CRs in the
> upstream cluster, you can reason about which CRs will exist in the downstream
> cluster.
>
> If `cr-syncer.cloudrobotics.com/filter-by-robot-name` is true, then the
> cr-syncer will ignore any downstream resources that are not labelled with a
> matching robot name. This means that a robot can run ChartAssignments that are
> synced from the cloud as well as those created directly in the robot cluster.

In some cases, downstream deletion may be blocked. For example, if we have
deleted an upstream ChartAssignment, but the chart-assignment-controller has failed to remove
its finalizer from the downstream ChartAssignment. This edge case leads to
surprising behavior:

- An upstream ChartAssignment can be recreated before the downstream
  ChartAssignment is deleted.
- The old status from the downstream cluster will be synced to the new upstream
  ChartAssignment.

If needed, this can be detected by watching the downstream cluster after
deleting the resource from the downstream cluster. The situation will clean up
once downstream deletion is complete.

Note: previously, the cr-syncer used finalizers to block upstream deletion
until the downstream resource was deleted. This gave the original deleter more
information: for example, once an AppRollout had been deleted in the cloud, it
means that all robots have terminated the app's pods. However, this caused
problems with offline or renamed clusters: an admin would have to manually clean
up the old finalizers. The new asynchronous behavior is not affected by offline
clusters.

## Resource generations

Custom resources have a field `.metadata.generation` that starts at 1 and is
incremented when the resource changes. Specifically, if the CRD enables the
/status subresource, the generation increases by 1 every time the resource spec
changes, but not when the status changes. The resource controller can set
`.status.observedGeneration` to the latest generation it has observed, so the
user can change the spec, then wait for `observedGeneration` to catch up before
looking at the status. For example:

* Create a Deployment for one pod (generation=1), and wait for the status to be Ready.
* Change the Deployment's image reference (generation=2): the status is still
  Ready, but this refers the old spec (observedGeneration=1).
* Wait for the status to update (observedGeneration=2): now the status is
  non-ready, referring to the newer spec.
* Wait for the status to be Ready. The new image is now running.

`generation` and `observedGeneration` can **only be compared in the downstream
cluster**. As the generation is managed by the Kubernetes apiserver, the
cr-syncer cannot guarantee that the upstream generation matches the downstream
generation. On the other hand, `observedGeneration` will be copied from
downstream to upstream with the rest of `.status`. This means that `generation`
is cluster-specific but `observedGeneration` always refers to the downstream
generation.


================================================
FILE: docs/developers/debug-auth.md
================================================
# Debugging authentication problems

Useful tips for working with Authentication and Authorization systems.

## Run a sample request with various credentials

You can call Cloud APIs with curl to see whether authorization works.

### Your own credentials

```bash
PROJECT_NUMBER=201199916163
curl -v -H "Content-Type: application/json" \
        -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
        "https://cloudroboticssensordata.googleapis.com/v1eap/projects/${PROJECT_NUMBER}/sensors"
```

### Service account JSON file

You can create a JSON file with the robot account's credentials on
the [Cloud console's credentials page](https://console.cloud.google.com/apis/credentials).

```bash
PROJECT_NUMBER=201199916163
JSON_CREDENTIALS=/tmp/my-project-b7364a68fa92.json
curl -v -H "Content-Type: application/json" \
        -H "Authorization: Bearer $(GOOGLE_APPLICATION_CREDENTIALS=${JSON_CREDENTIALS} gcloud auth application-default print-access-token)" \
        "https://cloudroboticssensordata.googleapis.com/v1eap/projects/${PROJECT_NUMBER}/sensors"
```

### Get an OAuth token from IAM

The token vendor doesn't have its own keys, but instead calls IAM's
generateAccessToken method. You can emulate its behavior by using the [API
Explorer](https://developers.google.com/apis-explorer/#search/iam%20credentials/iamcredentials/v1/iamcredentials.projects.serviceAccounts.generateAccessToken)
to call `iamcredentials.projects.serviceAccounts.generateAccessToken`. The name
parameter is
`projects/-/serviceAccounts/robot-service@my-project.iam.gserviceaccount.com`,
and the `scope` is `https://www.googleapis.com/auth/cloud-platform`.

You can pass the returned access token in an Authorization header as above.

## Check whether the client's request is well-formed and authenticated

The easiest way to verify that metadata server and the gRPC client library
are doing the right thing is to use a logging HTTP server as the gRPC server.
Instead of setting the gRPC host to the Cloud API server
(`cloudroboticssensordata.googleapis.com`), you set it to an
HTTPS-capable server under your control. You need HTTPS support because
otherwise the gRPC library will rightfully decline to send access tokens.

Luckily, your Cloud Robotics Core setup already runs an HTTPS server. Suppose
you're calling the gRPC service
`google.cloud.robotics.sensordata.v1eap.SensorDataService`. You can
hook a very simple Python HTTP server into your cloud nginx setup like
this:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: debug
spec:
  server.py: |
    import BaseHTTPServer
    import SocketServer

    class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):

      def do_GET(self):
        print "Got request for ", self.path, " with auth ", self.headers.get('Authorization')

      def do_POST(self):
        print "Got request for ", self.path, " with auth ", self.headers.get('Authorization')

    httpd = SocketServer.TCPServer(("", 8080), MyHandler)
    httpd.serve_forever()
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: debug
spec:
  selector:
    matchLabels:
      app: debug
  replicas: 1
  template:
    metadata:
      labels:
        app: debug
    spec:
      containers:
      - name: python
        image: python:2
        args: ["python", "/src/server.py"]
        volumeMounts:
        - name: src-volume
          mountPath: /src
      volumes:
      - name: src-volume
        configMap:
          name: debug
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: debug
  name: debug
spec:
  ports:
  - name: http
    port: 8082
    protocol: TCP
    targetPort: 8080
  selector:
    app: debug
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTP
  name: debug
spec:
  ingressClassName: nginx
  rules:
  - host: www.endpoints.my-project.cloud.goog
    http:
      paths:
      - path: /google.cloud.robotics.sensordata.v1eap.SensorDataService
        pathType: Prefix
        backend:
          service:
            name: debug
            port
              number: 8080
```

This will log the Authorization header to the pod's stdout, so you can view it
with `kubectl logs`. Save the token to a file (don't paste it into the command
line because it will end up in your shell history).

## Checking tokens with the tokeninfo service

You can check the token's contents and sanity with Google's tokeninfo endpoint:

```shell
curl https://oauth2.googleapis.com/tokeninfo?access_token=$(cat /tmp/token.txt)
```


================================================
FILE: docs/how-to/connecting-robot.md
================================================
# Connecting a robot to the cloud

Estimated time: 10 min

This page describes how to connect a Kubernetes cluster on a robot running Ubuntu 20.04 to the cloud.

Once you've done this, you can:

* Run a private Docker container from the Google Container Registry
* Securely communicate with cloud services
* See logs from the robot in the Cloud Console

## Setting up the GCP project

1. If you haven't already, complete the [Setting up the GCP project](../quickstart.md) steps.

1. On the computer you used to set up the cloud project, generate an access token, which you'll use to give the robot access to the cloud:

    ```shell
    gcloud auth application-default print-access-token
    ```

> **Note:** If you want to reduce the risk that your cloud project is
> compromised using this token during its 1h lifetime, you can generate a less
> privileged service account token:
>
> ```
> SA="human-acl@${PROJECT_ID}.iam.gserviceaccount.com"
> gcloud iam service-accounts add-iam-policy-binding "${SA}" \
>   --role=roles/iam.serviceAccountTokenCreator \
>   --project="${PROJECT_ID}" --member="user:${YOUR_EMAIL_ADDRESS:?}"
> gcloud auth print-access-token --impersonate-service-account="${SA}"
> ```
>
> If you see `ERROR: Failed to impersonate ...`, wait a few minutes for the IAM
> policy to propagate.
>
> You can ignore the "WARNING: This command is using service account
> impersonation."

## Installing the cluster on the robot

## Installing Kubernetes

You'll need to install a Kubernetes cluster on the robot before you can connect it to the cloud. The cluster manages and supports the processes that communicate with the cloud.

Please see external references for setting up k8s. For simplicity we recommend
[k3s](https://k3s.io/) or a single node
[kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)
cluster (untested).

## Setting up the robot

1. Set up the robot cluster to connect to the cloud. When running `setup_robot.sh`, you'll need to enter the access token you generated earlier. You may find it easiest if you SSH into the robot from the workstation you used to set up the project.

    ```shell
    mkdir -p ~/cloud-robotics-core
    cd ~/cloud-robotics-core
    curl https://raw.githubusercontent.com/googlecloudrobotics/core/master/src/bootstrap/robot/setup_robot.sh >setup_robot.sh
    bash setup_robot.sh my-robot --project ${PROJECT_ID} \
      --robot-type my-robot-type
    ```

    Set `${PROJECT_ID}` to your GCP project ID. When prompted for an access token, provide the authentication token you generated earlier.

    > **Note:** `my-robot-type` is a placeholder and you can ignore it for now.

## What's next

* [Using Cloud Storage from a robot](using-cloud-storage.md).


================================================
FILE: docs/how-to/creating-declarative-api.md
================================================
# Creating a declarative API

<!-- Estimated time: TODO -->

In this guide we will use a Kubernetes-style declarative API to interface to an external Charge Service for a robot.
This API is built around the concept of a ChargeAction resource, which instructs a robot to drive to a charger.
While the robot is charging, the status of the ChargeAction resource is kept up-to-date and can be observed.

## Motivation

RPC-based systems like ROS's [actionlib](http://wiki.ros.org/actionlib), while proven to be scalable, maintainable and useful, leave a few things to be desired:

1. **Synchronization**. The intent for a controller is stored in-memory in multiple components and we rely on correct synchronization.
For example, the motion planner sends the "turn wheel 3 times per second" message to the wheel actuator, then trusts that the wheel actuator will have received the intent and waits for it to act on the shared intent.
If a second process (such as an emergency stop) overwrites the intent of the wheel actuator, there's no standard channel to notify the motion planner.

2. **Persistence**. Since the intent is stored in-memory, it is lost when any process restarts.
This is the core reason that software in ROS systems can't be updated on the fly.

3. **Inspection**. For debugging, a coherent view into the current system intent would be great.
In RPC-based APIs, the intent is often updated differentially (eg "a little more to the left"), so our only hope of debugging is to log all messages ever sent.

In a declarative API, all actions and feedback are stored in a shared database&mdash;an approach built on Kubernetes' experience building robust distributed systems&mdash;which addresses these issues.
The latency added by going through the shared database means that
declarative APIs are best suited to latency-tolerant applications like
high-level control.

<!-- ## Concepts (describe resources, controllers, etc, and link to docs) -->

## Prerequisites

* Completed the [Quickstart Guide](../quickstart.md), after which the GCP project is set up and `gcloud-sdk` and `kubectl` are installed and configured.
* `docker` is installed and configured on the workstation ([instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/)).
<!-- * For the last part of the guide: A robot that has been successfully [connected to the cloud](connecting-robot.md). -->

Create a directory for the code examples of this guide, e.g.:

```shell
mkdir charge-service
cd charge-service
```

Set your GCP project ID as an environment variable:

```
export PROJECT_ID=[YOUR_GCP_PROJECT_ID]
```

All files created in this tutorial can be found in
[docs/how-to/examples/charge-service](https://github.com/googlecloudrobotics/core/tree/master/docs/how-to/examples/charge-service).
If you download the files, you have to replace the placeholder `[PROJECT_ID]` with your GCP project ID:

```shell
sed -i "s/\[PROJECT_ID\]/$PROJECT_ID/g" charge-controller.yaml
```

## Installing metacontroller

This tutorial is based on [metacontroller](https://metacontroller.github.io/metacontroller/intro.html), an add-on for Kubernetes that makes it easy to write and deploy [custom controllers](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-controllers).
Custom controllers implement the logic behind a declarative API.

First, make sure that `kubectl` points to the correct GKE cluster:

```shell
kubectl config get-contexts
```

If the correct cluster is not marked with an asterisk in the output, you can switch to it with `kubectl config use-context [...]`.

Now install metacontroller to the cloud-cluster:

```shell
kubectl create namespace metacontroller
kubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-rbac.yaml
kubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-crds-v1.yaml
kubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller.yaml
```

Let's check that all resources came up:

```console
> kubectl get pods --namespace metacontroller
NAME               READY   STATUS    RESTARTS   AGE
metacontroller-0   1/1     Running   0          1m
```

You can learn more details in the metacontroller's [install instructions](https://metacontroller.github.io/metacontroller/guide/install.html).

> **Limitations of metacontroller**:
> Writing custom controllers with metacontroller is easy, and you can use
> whatever programming language you prefer.
> However, it has some limitations.
>
> 1. metacontroller can't directly detect changes in external state, although
>    you can configure it to periodically reconcile your resources with external
>    systems. This introduces latency corresponding to the reconciliation
>    frequency.
> 1. The information available to your controller is limited.
>    You can't use metacontroller to create a controller that only acts on a
>    single resource out of many (for example, a controller that only executes
>    the highest-priority action).
>
> For advanced use cases, writing a controller in Golang offers more
> flexibility. See
> [sample-controller](https://github.com/kubernetes/sample-controller) for an
> example.

## Defining the controller logic

The core of a declarative API is the controller logic, which defines how the resources should be handled and reports the current status.
For the Charge Service, we've implemented the logic in a Python script.
Download [server.py](examples/charge-service/server.py):

```shell
curl -O https://raw.githubusercontent.com/googlecloudrobotics/core/master/docs/how-to/examples/charge-service/server.py
```

This Python program implements a server that listens on port 80 for incoming HTTP POST requests from metacontroller.
The controller logic is contained in the `sync()` method, which handles new ChargeActions by calling `charge_service.start_charging()`, and handles in-progress ChargeActions by updating the status.

[embedmd]:# (examples/charge-service/server.py python /.*state = current_status.get/ /return.*status.*/)
```python
    state = current_status.get("state", "CREATED")

    if state == "CREATED":
      # The ChargeAction has just been created. Use the external Charge Service
      # to start charging. Store the request ID in the status so we can use it
      # to check the state of the charge request.
      request_id = self.charge_service.start_charging()
      desired_status["state"] = "IN_PROGRESS"
      desired_status["request_id"] = request_id

    elif state == "IN_PROGRESS":
      try:
        # Get the progress of the charge request from the external service.
        progress = self.charge_service.get_progress(
            current_status["request_id"])
        desired_status["charge_level_percent"] = progress

        if progress == 100:
          # Charging has completed.
          desired_status["state"] = "OK"

      except ValueError as e:
        # The charge request was not found. This could be because the robot was
        # restarted during a charge, and the request was forgotten.
        desired_status["state"] = "ERROR"
        desired_status["message"] = str(e)

    elif state in ["OK", "CANCELLED", "ERROR"]:
      # Terminal state, do nothing.
      pass

    else:
      desired_status["state"] = "ERROR"
      desired_status["message"] = "Unrecognized state: %r" % state

    return {"status": desired_status, "children": []}
```

## Dockerizing the service

Next, to prepare our controller logic for deployment in the cloud, we package it as a Docker image. Make sure that the docker daemon is running and that your user has the necessary privileges:

```shell
docker run --rm hello-world
```

If this command fails, make sure Docker is installed according to the [installation instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/).

In the same directory as `server.py`, create a `Dockerfile` with the following contents:

[embedmd]:# (examples/charge-service/Dockerfile dockerfile)
```dockerfile
FROM python:alpine

WORKDIR /data

COPY server.py ./

CMD [ "python", "-u", "./server.py" ]
```

(Note: the `-u` option disables line-buffering; Python's line-buffering can prevent output from appearing immediately in the Docker logs.)

To build the Docker image, run:

```shell
docker build -t charge-controller .
```

You should see `Successfully tagged charge-controller:latest`. You can run the container locally with:

```shell
docker run -ti --rm -p 8000:8000 charge-controller
```

Then, from another terminal on the same workstation, send a request with an empty `parent` object:

```shell
curl -X POST -d '{"parent": {}, "children": []}' http://localhost:8000/
```

You should see a response like:

```json
{"status": {"state": "IN_PROGRESS", "request_id": "2423e70c-9dc7-47ac-abcb-b2ef0cbc676c"}, "children": []}
```

The response indicates that the controller would set `"state": "IN_PROGRESS"` on a newly-created ChargeAction.

## Uploading the Docker image to the cloud

In order to be able to run the server as a container in our cloud cluster, we need to upload the Docker image to our GCP project's private [container registry](https://cloud.google.com/container-registry/docs/pushing-and-pulling).

Enable the Docker credential helper:

```shell
gcloud auth configure-docker
```

Tag the image and push it to the registry:

```shell
docker tag charge-controller gcr.io/$PROJECT_ID/charge-controller
docker push gcr.io/$PROJECT_ID/charge-controller
```

The image should now show up in the [Container Registry](https://console.cloud.google.com/gcr).

## Deploying the declarative API in the cloud

Create a file called `charge-crd.yaml` with the following contents:

[embedmd]:# (examples/charge-service/charge-crd.yaml yaml)
```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: chargeactions.example.com
  annotations:
    cr-syncer.cloudrobotics.com/spec-source: cloud
spec:
  group: example.com
  names:
    kind: ChargeAction
    plural: chargeactions
    singular: chargeaction
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      subresources:
        status: {}
      schema:
        openAPIV3Schema:
          type: object
          x-kubernetes-preserve-unknown-fields: true
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cloud-robotics:cr-syncer:chartaction
  labels:
    cr-syncer.cloudrobotics.com/aggregate-to-robot-service: "true"
rules:
- apiGroups:
  - example.com
  resources:
  - chargeactions
  verbs:
  - get
  - list
  - watch
  - update
```

This is a [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) definition (CRD) for a resource called ChargeAction.
This simple example just describes the name and version of the API, but CRDs can also define schemas for the resources.
The ClusterRole configures [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) to let the robot access the ChargeActions.
Don't worry about the `cr-syncer.cloudrobotics.com/spec-source` annotation for now, as it'll be explained later in the tutorial.

Next, create a file called `charge-controller.yaml` with the following contents, replacing `[PROJECT_ID]` with your GCP project ID:

[embedmd]:# (examples/charge-service/charge-controller.yaml yaml)
```yaml
apiVersion: metacontroller.k8s.io/v1alpha1
kind: CompositeController
metadata:
  name: charge-controller
spec:
  generateSelector: true
  parentResource:
    apiVersion: example.com/v1
    resource: chargeactions
  resyncPeriodSeconds: 1
  hooks:
    sync:
      webhook:
        url: http://charge-controller.default:8000/sync
---
apiVersion: v1
kind: Service
metadata:
  name: charge-controller
spec:
  selector:
    app: charge-controller
  ports:
  - port: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: charge-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      app: charge-controller
  template:
    metadata:
      labels:
        app: charge-controller
    spec:
      containers:
      - name: controller
        image: gcr.io/[PROJECT_ID]/charge-controller
        ports:
        - containerPort: 8000
```

This file contains the information needed by Kubernetes and metacontroller to handle ChargeAction resources.
In the following, we will go over it bit by bit assuming basic familiarity with the [YAML format](https://en.wikipedia.org/wiki/YAML).

We define three Kubernetes resources:

* The *CompositeController* tells metacontroller to send ChargeAction resources to the charge-controller Service.
* The *Service* defines how the HTTP server is exposed within the cluster.
* The *Deployment* describes the Docker container to run.

Metadata, labels, and selectors are used to tie the three resources together.

A detailed explanation of the Kubernetes resources is out of scope for this guide, check out the [Kubernetes docs](https://kubernetes.io/docs/home/) or [metacontroller User Guide](https://metacontroller.github.io/metacontroller/guide.html) to get started.
There are a few points worth mentioning, though:

* In the Deployment, don't forget to replace `[PROJECT_ID]` with your GCP project ID.
* The CompositeController sets `resyncPeriodSeconds: 1`.
  This tells metacontroller to check each ChargeAction every second.
  This allows `server.py` to update the progress every second while the action is in progress.
* The CompositeController sets `url: http://charge-controller.default:8000/sync`.
  This tells metacontroller that the ChargeAction resources are handled by a service called `charge-controller` in the `default` namespace.

Deploy these resources by applying the configuration:

```shell
kubectl apply -f charge-crd.yaml
kubectl apply -f charge-controller.yaml
```

You can explore the various resources that were created on your cluster as a result of this command in the [GKE Console](https://console.cloud.google.com/kubernetes/workload) or with `kubectl`, e.g.:

```shell
kubectl get pods
```

The resulting list should contain a running pod with a name like `charge-controller-xxxxxxxxxx-xxxxx`.

## Redeploying after a change

If you make a change to `server.py`, you need to rebuild and push the Docker image:

```shell
docker build -t charge-controller .
docker tag charge-controller gcr.io/$PROJECT_ID/charge-controller
docker push gcr.io/$PROJECT_ID/charge-controller
```

The easiest way to get Kubernetes to restart the workload with the latest version of the container is to delete the pod:

```shell
kubectl delete pod -l 'app=charge-controller'
```

Kubernetes will automatically pull the newest image and recreate the pod.

If you make a change to `charge-controller.yaml`, all you have to do is apply it again:

```shell
kubectl apply -f charge-controller.yaml
```

## Accessing the API

You can use `kubectl` to interact with the API.
Create a file called `charge-action.yaml` with the following contents:

[embedmd]:# (examples/charge-service/charge-action.yaml yaml)
```yaml
apiVersion: example.com/v1
kind: ChargeAction
metadata:
  name: my-charge-action
```

Run the following command to create a ChargeAction and observe how its status changes:

```shell
kubectl apply -f charge-action.yaml \
  && watch -n0 kubectl describe chargeaction my-charge-action
```

Over the next 10 seconds, you should see the "Charge Level Percent" increase to 100, and then the state should become "CHARGED".

> **Troubleshooting**:
> If the ChargeAction's status doesn't change, check that metacontroller is installed by running `kubectl --namespace metacontroller get pods`.
> You should see `metacontroller-0   1/1    Running`.
> You can also check the metacontroller logs with `kubectl --namespace metacontroller logs metacontroller-0`


## Deploying the declarative API on the robot.

So far, the Charge Service has been running in the cloud, but we need to run
code on the robot to get it to charge.
We can change this with the `cr-syncer`, a component of Cloud Robotics Core that allows declarative APIs to work between Kubernetes clusters.
In particular, we can run the charge-controller on the robot, while creating the ChargeAction in the cloud cluster.
The `cr-syncer` takes care of copying the ChargeAction to the robot when the
robot has network connectivity.

**Prerequisite**: you'll need a robot that has been successfully [connected to the cloud](connecting-robot.md).

First, remove the controller from the cloud cluster:

```shell
# Note: run this on the workstation
kubectl delete -f charge-controller.yaml
```

Then SSH into the robot, install metacontroller, and bring up the charge-controller there:

```shell
# Note: run this on the robot
kubectl create namespace metacontroller
kubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-rbac.yaml
kubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-crds-v1.yaml
kubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller.yaml

export PROJECT_ID=[YOUR_GCP_PROJECT_ID]
kubectl apply -f https://raw.githubusercontent.com/googlecloudrobotics/core/master/docs/how-to/examples/charge-service/charge-crd.yaml
curl https://raw.githubusercontent.com/googlecloudrobotics/core/master/docs/how-to/examples/charge-service/charge-controller.yaml \
  | sed "s/\[PROJECT_ID\]/$PROJECT_ID/g" | kubectl apply -f -
```

Now, check that these are running correctly:

```console
# Note: run this on the robot
> kubectl get pods --namespace metacontroller
NAME               READY   STATUS    RESTARTS   AGE
metacontroller-0   1/1     Running   0          1m
> kubectl get pods -l app=charge-controller
NAME                                 READY   STATUS    RESTARTS   AGE
charge-controller-57786849f8-xp5kf   1/1     Running   0          77s
```

Switch back to a terminal on your workstation.
As before, you can create a ChargeAction with `kubectl`, but this time it will be
handled by the controller on the robot.

```shell
# Note: run this on the workstation
kubectl delete -f charge-action.yaml
kubectl apply -f charge-action.yaml \
  && watch -n0 kubectl describe chargeaction my-charge-action
```

How does this work?

- The `cr-syncer` runs on the robot and watches custom resources in the cloud.
- It sees the `cr-syncer.cloudrobotics.com/spec-source: cloud` annotation on the
  CustomResourceDefinition, which tells it to copy the `spec` from
  `my-charge-action` in the cloud cluster into a copy of `my-charge-action` in
  the robot cluster.
- While the robot is charging, the robot's charge-controller updates the status
  in the robot's cluster.
- The `cr-syncer` copies the status back up to the original resource in the
  cloud cluster.

## Cleaning up

In order to stop the controller and remove the CRD you created, run:

```shell
kubectl delete -f charge-controller.yaml -f charge-crd.yaml
```

If you want to uninstall metacontroller too, run:

```shell
kubectl delete namespace metacontroller
```

If you installed on the robot, you'll need to run these commands there too.

<!--
TODO(rodrigoq): define "What's Next" for declarative APIs

## What's next

There are a few inconvenient steps in this guide, e.g.:

* manually replacing the project ID in all source files or
* remotely logging in to the robot to start a workload.

This is where the app management layer comes in; it provides, among other capabilities:

* ways of bundling and parameterizing Kubernetes resources and
* remote management of Kubernetes resources/workloads on the robot.

Also note that both the server and the client side can be implemented with similar ease in other programming languages, such as [Go](https://github.com/googleapis/google-api-go-client).
-->


================================================
FILE: docs/how-to/deploy-from-sources.md
================================================
# Deploy Cloud Robotics Core from sources

Estimated time: 30 min

This page describes how to set up a Google Cloud Platform (GCP) project
containing the Cloud Robotics Core components.
In particular, this creates a cluster with Google Kubernetes Engine and prepares
it to accept connections from robots, which enables those robots to securely
communicate with GCP.
The commands were tested on machines running Debian (Stretch) or Ubuntu (16.04
and 18.04) Linux.

1. In the GCP Console, go to the [Manage resources][resource-manager] page and
   select or create a project.
1. Make sure that [billing][modify-project] is enabled for your project.
1. [Install the Cloud SDK][cloud-sdk]. When prompted, choose the project that you created above.
1. After installing the Cloud SDK, install the `kubectl` command-line tool and the GKE auth plugin:

    ```shell
    gcloud components install kubectl
    gcloud components gke-gcloud-auth-plugin
    ```

    If you're using Debian or Ubuntu, you may need to use `apt install kubectl` instead.

1. [Install the Bazel build system][install-bazel].

1. Install additional build dependencies:

    ```shell
    sudo apt-get install default-jdk git python-dev unzip xz-utils
    ```

[resource-manager]: https://console.cloud.google.com/cloud-resource-manager
[modify-project]: https://cloud.google.com/billing/docs/how-to/modify-project
[cloud-sdk]: https://cloud.google.com/sdk/docs/
[install-bazel]: https://github.com/bazelbuild/bazel/blob/4.0.0/site/docs/install-ubuntu.md

## Build and deploy the project

1. Clone the source repo.

    ```shell
    git clone https://github.com/googlecloudrobotics/core
    cd core
    ```

1. Create application default credentials, which are used to deploy the cloud project and
   authorize access to the cloud docker registry.

    ```shell
    gcloud auth application-default login
    gcloud auth configure-docker
    ```

1. Create a Cloud Robotics config in your project:

    ```shell
    ./deploy.sh set_config [PROJECT_ID]
    ```

    You can keep the defaults for the other settings by hitting `ENTER`.

    This command creates a file `config.sh` containing your choices and stores
    in into a cloud-storage bucket named `[PROJECT_ID]-cloud-robotics-config`.
    You can verify the settings using:

    ```shell
    gcloud storage cat gs://[PROJECT_ID]-cloud-robotics-config/config.sh
    ```


1. Build the project. Depending on your computer and internet connection, it may take around 15 minutes.

    ```shell
    bazel build //...
    ```

1. Deploy the cloud project.

    ```shell
    ./deploy.sh create [PROJECT_ID]
    ```

> **Known issue:**
> Sometimes, this command fails with an error message like
> `Error 403: The caller does not have permission`,
> `Error 403: Service ... not found or permission denied',
> `Bad status during token exchange: 503`, or
> `Error enabling service`.
> In these cases, wait for a minute and try again.

`deploy.sh` created a Kubernetes cluster using Google Kubernetes Engine and used Helm to install the Cloud Robotics Core components.
You can browse these components on the [Workloads dashboard](https://console.cloud.google.com/kubernetes/workload).
Alternatively, you can list them from the console on your workstation:

```console
$ kubectl get pods

NAME                READY   STATUS             RESTARTS   AGE
cert-manager-xxx    1/1     Running            0          1m
nginx-ingress-xxx   1/1     Running            0          1m
oauth2-proxy-xxx    0/1     CrashLoopBackOff   4          1m
token-vendor-xxx    1/1     Running            0          1m
```

> **Note** Unless you already set up OAuth, the `oauth2-proxy` will show an error which we will ignore for now.


In addition to the cluster, `deploy.sh` also created:

* the [[PROJECT_ID]-robot Cloud Storage bucket](https://console.cloud.google.com/storage/browser), containing the scripts that connect robots to the cloud, and
* the [Identity & Access Management policies](https://console.cloud.google.com/iam-admin/iam) that authorize robots and humans to communicate with GCP.

With the project deployed, you're ready to [connect a robot to the cloud](connecting-robot.md).

## Update the project

To apply changes made in the source code, run:

```shell
./deploy.sh update [PROJECT_ID]
```

## Clean up

The following command will delete:

* the [cloud-robotics Kubernetes cluster](https://console.cloud.google.com/kubernetes/list)

This can be useful if the cluster is in a broken state.
Be careful with this invocation, since you'll have to redeploy the project and reconnect any robots afterwards.

```shell
./deploy.sh delete [PROJECT_ID]
```

If you want to completely shut down the project, see [the Resource Manager documentation](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects).

## What's next

* [Connecting a robot to the cloud](connecting-robot.md).
* [Setting up OAuth for web UIs](setting-up-oauth.md).


================================================
FILE: docs/how-to/deploying-grpc-service.md
================================================
# Deploying a gRPC service written in C++

Estimated time: 60 min

In this guide we will deploy a gRPC service written in C++ and deploy it to our Google Kubernetes Engine (GKE) cluster in the cloud in such a way that authentication is required for access. We will show how to access the service from the workstation and how to access it from code running in the robot's Kubernetes cluster.


## Prerequisites

* Completed the [Quickstart Guide](../quickstart.md), after which the GCP project is set up and `gcloud-sdk` and `kubectl` are installed and configured.
* `docker` is installed and configured on the workstation ([instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/)).
* `git` is installed on the workstation.
* For the last part of the guide: A robot that has been successfully [connected to the cloud](connecting-robot.md).

All files for this tutorial are located in
[docs/how-to/examples/greeter-service/](https://github.com/googlecloudrobotics/core/tree/master/docs/how-to/examples/greeter-service).

```shell
git clone https://github.com/googlecloudrobotics/core
cd core/docs/how-to/examples/greeter-service
```

Set your GCP project ID as an environment variable:

```shell
export PROJECT_ID=[YOUR_GCP_PROJECT_ID]
```


## Running gRPC server and client locally

We will use [gRPC's quickstart example](https://grpc.io/docs/quickstart/cpp.html) with small modifications.
If you like to learn more about gRPC in C++, follow their guide first.

The gRPC `helloworld.Greeter` service is defined in `proto/helloworld.proto`.
It accepts a `HelloRequest` containing a `name` and responds with a `HelloReply` containing a `message`.
The server is implemented in `server/server.cc` and the client is implemented `client/client.cc`. The client sends the request with `name: "world"` to the server which responds with `message: "Hello <name>"`.

In this tutorial, we build the server and client code inside Docker containers, so you don't need to install the gRPC library.
If you prefer, you can install the gRPC following [these instructions](https://github.com/grpc/grpc/blob/master/src/cpp/README.md) and build the server and client locally using the provided `Makefile`.

Make sure the Docker daemon is running and your user has the necessary privileges:

```shell
docker run --rm hello-world
```

If this command fails, make sure Docker is installed according to the [installation instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/).

The Docker image for the server is configured in `server/Dockerfile`:

[embedmd]:# (examples/greeter-service/server/Dockerfile dockerfile)
```dockerfile
FROM grpc/cxx:1.12.0

WORKDIR /data

COPY server/server.cc ./server/
COPY proto/helloworld.proto ./proto/
COPY Makefile ./

RUN make greeter-server && make clean

CMD ["./greeter-server"]
```

We use the [grpc/cxx](https://hub.docker.com/r/grpc/cxx) Docker image which contains all the build tools and libraries (`g++`, `make`, `protoc`, and `grpc`) we need to build the `greeter-server` binary.
The Docker image for the client is configured in `client/Dockerfile` which builds the `greeter-client` from `client/client.cc`.

To build the Docker images, run:

```shell
docker build -t greeter-server -f server/Dockerfile .
docker build -t greeter-client -f client/Dockerfile .
```

> **Note**
> The docker files are in the subfolders `greeter-server/server/` and `greeter-server/client`, but the docker command must be called from `greeter-server/` to include the files which are shared between the server and the client.

You should now have an image tagged `greeter-server` and one tagged `greeter-client` in your local registry:

```shell
docker images | grep greeter
```

To run the server locally, the container's port 50051, which specified as gRPC port in `server.cc`, has to be published to your machine with the flag `-p 50051:50051`:

```shell
docker run --rm -p 50051:50051 --name greeter-server greeter-server
```

In another console run the client container.
The flag `--network=host` tells the container to use your workstation's network stack which allows the client to connect to `localhost`.

```shell
docker run --rm --network=host greeter-client ./greeter-client localhost
```

You should see `Greeter received: Hello world` in the client's output and `Received request: name: "world"` in the server's output. You can also send your own name in the gRPC request to the server, try:

```shell
docker run --rm --network=host greeter-client \
  ./greeter-client localhost $USER
```

You can stop the server from another terminal by running:

```shell
docker stop greeter-server
```


## Uploading the Docker image to the cloud

In order to be able to run the server as a container in our cloud cluster, we need to upload the Docker image to our GCP project's private [container registry](https://cloud.google.com/container-registry/docs/pushing-and-pulling).

Enable the Docker credential helper:

```shell
gcloud auth configure-docker
```

Tag the image and push it to the registry:

```shell
docker tag greeter-server gcr.io/$PROJECT_ID/greeter-server
docker push gcr.io/$PROJECT_ID/greeter-server
```

The image should now show up in the [container registry](https://console.cloud.google.com/gcr).


## Deploying the service in the cloud using Kubernetes

Run the following command to create `greeter-server.yaml` using the provided template:

```shell
cat greeter-server.yaml.tmpl | envsubst >greeter-server.yaml
```

This file contains the information needed by Kubernetes to run the gRPC service in our cloud cluster.
The three resources, Ingress, Service, and Deployment, are explained in the [deploying a service tutorial](deploying-service.md).
In contrast to the other tutorial, the Ingress tells nginx to forward incoming requests to a gRPC backend.

[embedmd]:# (examples/greeter-service/greeter-server.yaml.tmpl yaml /^/ /---/)
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: greeter-server-ingress
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: GRPC
    nginx.ingress.kubernetes.io/auth-url: "http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true"
spec:
  ingressClassName: nginx
  rules:
  - host: "www.endpoints.${PROJECT_ID}.cloud.goog"
    http:
      paths:  # must match the namespace and service name in the proto
      - path: /helloworld.Greeter/
        pathType: Prefix
        backend:
          service:
            name: greeter-server-service
            # must match the port used in server.cc
            port:
              number: 50051
---
```

Make sure that `kubectl` points to the correct GCP project:

```shell
kubectl config get-contexts
```

If the correct cluster is not marked with an asterisk in the output, you can switch contexts with `kubectl config use-context [...]`.)
Then deploy by applying the configuration:

```shell
kubectl apply -f greeter-server.yaml
```

You can explore the various resources that were created on your cluster as a result of this command in the [GKE Console](https://console.cloud.google.com/kubernetes/workload) or with `kubectl`, e.g.:

```shell
kubectl get pods
```

The resulting list should contain a running pod with a name like `greeter-server-xxxxxxxxxx-xxxxx`.


## Redeploying after a change

For convenience, `deploy.sh` provides some commands to create, delete, and update the service.
If you make changes to `greeter-server.yaml.tmpl`, all you have to do is run:

```shell
./deploy.sh update_config
```

If you make changes to `server.cc`, you need to run:

```shell
./deploy.sh update_server
```

This builds, tags, and pushes the Docker image, and then forces a redeployment of the image by calling `kubectl delete pod -l 'app=greeter-server-app'`.
It also updates the resource definitions, so you don't have to run `./deploy.sh update_config` if you made changes to both files.


## Accessing the API

In `client/client.cc` we use `grpc::InsecureChannelCredentials()` when talking to `localhost` while we use `grpc::GoogleDefaultCredentials()` when talking to any other address.
SSL authentication with credentials from the user or robot are necessary when talking to the `greeter-server` in the Cloud Robotics project.

[embedmd]:# (examples/greeter-service/client/client.cc c++ /^ +if.*localhost/ /^ +}$/)
```c++
  if (grpc_endpoint.find("localhost:") == 0 ||
      grpc_endpoint.find("127.0.0.1:") == 0) {
    channel_creds = grpc::InsecureChannelCredentials();
  } else {
    channel_creds = grpc::GoogleDefaultCredentials();
  }
```

Let's try to access our server.
We have to connect to the nginx ingress which is hosted on `www.endpoints.$PROJECT_ID.cloud.goog:443`.
To ensure we have valid credentials to talk to nginx we have to mount our `~/.config` folder in the container.

```shell
docker run --rm -v ~/.config:/root/.config greeter-client \
  ./greeter-client www.endpoints.$PROJECT_ID.cloud.goog:443 workstation
```

Recall that when running `./greeter-server` on your workstation you were able to see the server's log output upon receiving a request.
This log output is also recorded when the server is running in the cloud cluster. To inspect it, run:

```shell
kubectl logs -l 'app=greeter-server-app'
```

Or go to the [GKE Console](https://console.cloud.google.com/kubernetes/workload), select the `greeter-server` workload and click on "Container logs".


## Accessing the API from the robot

In order to run `greeter-client` on the robot's Kubernetes cluster, we again package it as a Docker image and push it to our container registry, to which the robot also has access.
Our deploy script offers a command to build, tag, and push the image to the cloud registry, like we did with the server container:

```shell
./deploy.sh push_client
```

And finally, to execute the script, SSH into robot and run:

```shell
export PROJECT_ID=[YOUR_GCP_PROJECT_ID]
docker pull grpc/cxx:1.12.0  # This may take several minutes, depending on WiFi connection
kubectl run -ti --rm --restart=Never --image=gcr.io/$PROJECT_ID/greeter-client greeter-client \
  -- ./greeter-client www.endpoints.$PROJECT_ID.cloud.goog:443 robot
```

You should see the server's answer `Hello robot`.

Two things are noteworthy:

* The `greeter-client` Docker image was pulled from the container registry without the need for additional credentials. This worked because there is a periodical job running on the robot's Kubernetes cluster that refreshes the GCR credentials. Run `kubectl get pods` on the robot and you will see pod names that start with `gcr-credential-refresher`.
* `grpc::GoogleDefaultCredentials()` in the client's code automatically obtained credentials that allowed the robot to access the cloud cluster. This worked because the the local Metadata Server obtains access tokens for the robot in the background.


## Cleaning up

In order to stop the service in the cloud cluster and revert the configuration changes, run:

```shell
./deploy.sh delete
```


================================================
FILE: docs/how-to/deploying-service.md
================================================
# Deploying a service to the cloud cluster

Estimated time: 60 min

In this guide we will write a HTTP service in Python and deploy it to our Google Kubernetes Engine (GKE) cluster in the cloud in such a way that authentication is required for access. We will show how to access the service from the workstation and how to access it from code running in the robot's Kubernetes cluster.

## Prerequisites

* Completed the [Quickstart Guide](../quickstart.md), after which the GCP project is set up and `gcloud-sdk` and `kubectl` are installed and configured.
* `docker` is installed and configured on the workstation ([instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/)).
* `python3`, `python3-pip`, and `curl` are installed on the workstation.
* For the last part of the guide: A robot that has been successfully [connected to the cloud](connecting-robot.md).

Create a directory for the code examples of this guide, e.g.:

```shell
mkdir hello-service
cd hello-service
```

Set your GCP project ID as an environment variable:

```shell
export PROJECT_ID=[YOUR_GCP_PROJECT_ID]
```

All files created in this tutorial can be found in
[docs/how-to/examples/hello-service/](https://github.com/googlecloudrobotics/core/tree/master/docs/how-to/examples/hello-service).
If you download the files, you have to replace the placeholders `[PROJECT_ID]` with your GCP project ID:

```shell
sed -i "s/\[PROJECT_ID\]/$PROJECT_ID/g" client/client.py server/hello-server.yaml
```

## A simple HTTP server

Create a subdirectory for the server code:

```shell
mkdir server
cd server
```

Paste the following into a file called `server.py`:

[embedmd]:# (examples/hello-service/server/server.py python)
```python
from http import server
import signal
import sys


class MyRequestHandler(server.BaseHTTPRequestHandler):
  def do_GET(self):
    print('Received a request')
    self.send_response(200)
    self.send_header('Content-Type', 'text/plain')
    self.end_headers()
    self.wfile.write(b'Server says hello!\n')


def main():
  # Terminate process when Kubernetes sends SIGTERM.
  signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))

  server_address = ('', 8000)
  httpd = server.HTTPServer(server_address, MyRequestHandler)
  httpd.serve_forever()


if __name__ == '__main__':
  main()
```

This Python program implements a server that listens on port 8000 for incoming HTTP GET requests. When such a request is received, it prints a line to stdout and responds to the request with a short message.

You can try it out with:

```shell
python server.py
```

If you see `ImportError: No module named http`, you are most likely using Python 2.x; try `python3` instead of `python`.)

Then, from another terminal on the same workstation, run:

```shell
curl -i http://localhost:8000
```

You should see the headers indicating that the request was successful (`200 OK`) and the server's response message. You can also try entering `localhost:8000` in your browser's address bar.

## Dockerizing the service

Next, to prepare our Python program for deployment in the cloud, we package it as a Docker image.

Make sure that the docker daemon is running and that your user has the necessary privileges:

```shell
docker run --rm hello-world
```

If this command fails, make sure Docker is installed according to the [installation instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/).

In the same directory as `server.py`, create a `Dockerfile` with the following contents:

[embedmd]:# (examples/hello-service/server/Dockerfile dockerfile)
```dockerfile
FROM python:alpine

WORKDIR /data

COPY server.py ./

CMD [ "python", "-u", "./server.py" ]
```

(Note: the `-u` option disables line-buffering; Python's line-buffering can prevent output from appearing immediately in the Docker logs.)

To build the Docker image, run:

```shell
docker build -t hello-server .
```

You should now have an image tagged `hello-server` in your local registry:

```shell
docker images | grep hello-server
```

It can be run locally with:

```shell
docker run -ti --rm -p 8000:8000 hello-server
```

You should now be able to send requests to the server with `curl` as before.

## Uploading the Docker image to the cloud

In order to be able to run the server as a container in our cloud cluster, we need to upload the Docker image to our GCP project's private [container registry](https://cloud.google.com/container-registry/docs/pushing-and-pulling).

Enable the Docker credential helper:

```shell
gcloud auth configure-docker
```

Tag the image and push it to the registry:

```shell
docker tag hello-server gcr.io/$PROJECT_ID/hello-server
docker push gcr.io/$PROJECT_ID/hello-server
```

The image should now show up in the [Container Registry](https://console.cloud.google.com/gcr).

## Deploying the service in the cloud using Kubernetes

Create a file called `hello-server.yaml` with the following contents:

[embedmd]:# (examples/hello-service/server/hello-server.yaml yaml)
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-server-ingress
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true"
spec:
  ingressClassName: nginx
  rules:
  - host: www.endpoints.[PROJECT_ID].cloud.goog
    http:
      paths:
      - path: /apis/hello-server
        pathType: Prefix
        backend:
          service:
            name: hello-server-service
            port:
              number: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: hello-server-service
spec:
  ports:
  - name: hello-server-port
    port: 8000
  # the selector is used to link pods to services
  selector:
    app: hello-server-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
spec:
  # all pods matching this selector belong to this deployment
  selector:
    matchLabels:
      app: hello-server-app
  template:
    metadata:
      # the other side of the link between services and pods
      labels:
        app: hello-server-app
    spec:
      containers:
      - name: hello-server
        image: gcr.io/[PROJECT_ID]/hello-server:latest
        ports:
        # must match the port of the service
        - containerPort: 8000
```

This file contains the information needed by Kubernetes to run our HTTP service in our cloud cluster. In the following, we will go over it bit by bit assuming basic familiarity with the [YAML format](https://en.wikipedia.org/wiki/YAML).

We define three Kubernetes resources:

* The *Ingress* contains rules that tell our cluster's nginx (the HTTP server that handles all incoming traffic) which incoming requests to forward to our service.
* The *Service* defines how our service is exposed within the cluster.
* The *Deployment* describes the Docker container to run.

Metadata, labels, and selectors are used to tie the three resources together.

A detailed explanation of the Kubernetes resources is out of scope for this guide, check out the [Kubernetes docs](https://kubernetes.io/docs/home/) to get started. There are a few points worth mentioning, though:

* In the Ingress and Deployment, don't forget to replace `[PROJECT_ID]` with your GCP project ID.
* In the Ingress, there is an annotation with key `nginx.ingress.kubernetes.io/auth-url`. This tells our cluster's nginx to check the authorization of each request before forwarding it to the `hello-server`. The value `http://token-vendor...` is the cluster-internal DNS address of a token verifier service that is running in the cluster as part of the Cloud Robotics Core platform.
* The Ingress rules specify that our hello-server will be reachable at `https://www.endpoints.[PROJECT_ID].cloud.goog/apis/hello-server`.
* The Deployment contains the full reference of the Docker image that we pushed to the Container Registry in the previous section.

Make sure that `kubectl` points to the correct GCP project:

```shell
kubectl config get-contexts
```

If the correct cluster is not marked with an asterisk in the output, you can switch to it with `kubectl config use-context [...]`.)
Then deploy by applying the configuration:

```shell
kubectl apply -f hello-server.yaml
```

You can explore the various resources that were created on your cluster as a result of this command in the [GKE Console](https://console.cloud.google.com/kubernetes/workload) or with `kubectl`, e.g.:

```shell
kubectl get pods
```

The resulting list should contain a running pod with a name like `hello-server-xxxxxxxxxx-xxxxx`.

## Redeploying after a change

If you make a change to `server.py`, you need to rebuild and push the Docker image:

```shell
docker build -t hello-server .
docker tag hello-server gcr.io/$PROJECT_ID/hello-server
docker push gcr.io/$PROJECT_ID/hello-server
```

The easiest way to get Kubernetes to restart the workload with the latest version of the container is to delete the pod:

```shell
kubectl delete pod -l 'app=hello-server-app'
```

Kubernetes will automatically pull the newest image and recreate the pod.

If you make a change to `hello-server.yaml`, all you have to do is apply it again:

```shell
kubectl apply -f hello-server.yaml
```

## Accessing the API

Let's try to access our server as we did before:

```shell
curl -i https://www.endpoints.$PROJECT_ID.cloud.goog/apis/hello-server
```

This should result in a `401 Unauthorized` error because we did not supply any authorization information with the request.
(Note: If you comment out the `auth-url` annotation in the Ingress definition and reapply it, this request will succeed.)

We can, however, easily obtain credentials from `gcloud` and attach them to our request by means of an "Authorization" header:

```shell
token=$(gcloud auth application-default print-access-token)
curl -i -H "Authorization: Bearer $token" https://www.endpoints.$PROJECT_ID.cloud.goog/apis/hello-server
```

If this command fails because "Application Default Credentials are not available", you need to first run:

```shell
gcloud auth application-default login --project=$PROJECT_ID
```

And follow the instructions in your browser.

Recall that when running `server.py` on your workstation you were able to see the server's log output upon receiving a request. This log output is also recorded when the server is running in the cloud cluster. To inspect it, run:

```shell
kubectl logs -l 'app=hello-server-app'
```

Or go to the [GKE Console](https://console.cloud.google.com/kubernetes/workload), select the `hello-server` workload and click on "Container logs".

Next, let's access the API from some Python code. Eventually, we will build another Docker image from this code, so it needs to live in a separate directory:

```shell
cd ..
mkdir client
cd client
```

Get some dependencies:

```shell
pip3 install --upgrade google-auth requests
```

(Depending on your local installation, you might have to use `pip3`.)

Create `client.py` with the following contents:

[embedmd]:# (examples/hello-service/client/client.py python)
```python
import google.auth
import google.auth.transport.requests as requests

credentials, project_id = google.auth.default()

authed_session = requests.AuthorizedSession(credentials)

response = authed_session.request(
  "GET", "https://www.endpoints.[PROJECT_ID].cloud.goog/apis/hello-server")

print(response.status_code, response.reason, response.text)
```

Replace `[PROJECT_ID]` with your GCP project ID.

This script:

* uses [`google-auth`](https://google-auth.readthedocs.io/en/latest/user-guide.html) to obtain application default credentials (just as we previously did with the `gcloud` CLI),
* uses the [`requests`](http://docs.python-requests.org/en/stable/) library to perform an authenticated request to our API,
* prints the response to stdout.

Try it out:

```shell
python3 client.py
```

You will get a warning about using end user credentials. You can safely ignore this warning; we will eventually be using a robot's credentials.)

## Accessing the API from the robot

In order to run this script on the robot's Kubernetes cluster, we again package it as a Docker image and push it to our container registry, to which the robot also has access.

Create a `Dockerfile` containing:

[embedmd]:# (examples/hello-service/client/Dockerfile dockerfile)
```dockerfile
FROM python:alpine

RUN pip install --no-cache-dir google-auth requests

WORKDIR /data

COPY client.py ./

CMD [ "python", "-u", "./client.py" ]
```

Build, tag, and push the image:

```shell
docker build -t hello-client .
docker tag hello-client gcr.io/$PROJECT_ID/hello-client
docker push gcr.io/$PROJECT_ID/hello-client
```

And finally, to execute the script, SSH into robot and run:

```shell
kubectl run -ti --rm --restart=Never --image=gcr.io/$PROJECT_ID/hello-client hello-client
```

You should see the server's message.

Two things are noteworthy:

* The `hello-client` Docker image was pulled from the Container Registry without the need for additional credentials. This worked because there is a periodical job running on the robot's Kubernetes cluster that refreshes the GCR credentials. Run `kubectl get pods` on the robot and you will see pod names that start with `gcr-credential-refresher`.
* The `google.auth.default()` invocation in the Python code automatically obtained credentials that allowed the robot to access the cloud cluster. This worked because the `google-auth` library queried the local Metadata Server, which obtains access tokens for the robot in the background.

## Cleaning up

In order to stop the service in the cloud cluster and revert the configuration changes, change to the `server` directory and run:

```shell
kubectl delete -f hello-server.yaml
```

<!--
## What's next

There are a few inconvenient steps in this guide, e.g.:

* manually replacing the project ID in all source files or
* remotely logging in to the robot to start a workload.

This is where the app management layer comes in; it provides, among other capabilities:

* ways of bundling and parameterizing Kubernetes resources and
* remote management of Kubernetes resources/workloads on the robot.

Also note that both the server and the client side can be implemented with similar ease in other programming languages, such as [Go](https://github.com/googleapis/google-api-go-client).
-->


================================================
FILE: docs/how-to/examples/charge-service/Dockerfile
================================================
FROM python:alpine

WORKDIR /data

COPY server.py ./

CMD [ "python", "-u", "./server.py" ]


================================================
FILE: docs/how-to/examples/charge-service/charge-action.yaml
================================================
apiVersion: example.com/v1
kind: ChargeAction
metadata:
  name: my-charge-action


================================================
FILE: docs/how-to/examples/charge-service/charge-controller.yaml
================================================
apiVersion: metacontroller.k8s.io/v1alpha1
kind: CompositeController
metadata:
  name: charge-controller
spec:
  generateSelector: true
  parentResource:
    apiVersion: example.com/v1
    resource: chargeactions
  resyncPeriodSeconds: 1
  hooks:
    sync:
      webhook:
        url: http://charge-controller.default:8000/sync
---
apiVersion: v1
kind: Service
metadata:
  name: charge-controller
spec:
  selector:
    app: charge-controller
  ports:
  - port: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: charge-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      app: charge-controller
  template:
    metadata:
      labels:
        app: charge-controller
    spec:
      containers:
      - name: controller
        image: gcr.io/[PROJECT_ID]/charge-controller
        ports:
        - containerPort: 8000


================================================
FILE: docs/how-to/examples/charge-service/charge-crd.yaml
================================================
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: chargeactions.example.com
  annotations:
    cr-syncer.cloudrobotics.com/spec-source: cloud
spec:
  group: example.com
  names:
    kind: ChargeAction
    plural: chargeactions
    singular: chargeaction
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      subresources:
        status: {}
      schema:
        openAPIV3Schema:
          type: object
          x-kubernetes-preserve-unknown-fields: true
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cloud-robotics:cr-syncer:chartaction
  labels:
    cr-syncer.cloudrobotics.com/aggregate-to-robot-service: "true"
rules:
- apiGroups:
  - example.com
  resources:
  - chargeactions
  verbs:
  - get
  - list
  - watch
  - update


================================================
FILE: docs/how-to/examples/charge-service/server.py
================================================
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import signal
import sys
import time
import uuid


class Controller(BaseHTTPRequestHandler):
  def sync(self, parent, children):
    """Actuate a ChargeAction custom resource.

    The resource is actuated by using the Charge Service to send the robot to a
    charger. While charging is in progress, the status of the resource is
    updated to reflect the charge level.

    Because the ChargeAction is handled by an external service, this controller
    doesn't create any child resources, ie the `children` list is empty.

    Args:
      parent: The current ChargeAction resource.
      children: Unused.

    Returns:
      A dict containing the latest status of the action, and an empty list of
      children, eg:
      {
        "status": {
          "state": "OK",
        },
        "children": [],
      }
    """

    # Get current status and copy to start building next status.
    current_status = parent.get("status", None) or {}
    desired_status = dict(current_status)
    state = current_status.get("state", "CREATED")

    if state == "CREATED":
      # The ChargeAction has just been created. Use the external Charge Service
      # to start charging. Store the request ID in the status so we can use it
      # to check the state of the charge request.
      request_id = self.charge_service.start_charging()
      desired_status["state"] = "IN_PROGRESS"
      desired_status["request_id"] = request_id

    elif state == "IN_PROGRESS":
      try:
        # Get the progress of the charge request from the external service.
        progress = self.charge_service.get_progress(
            current_status["request_id"])
        desired_status["charge_level_percent"] = progress

        if progress == 100:
          # Charging has completed.
          desired_status["state"] = "OK"

      except ValueError as e:
        # The charge request was not found. This could be because the robot was
        # restarted during a charge, and the request was forgotten.
        desired_status["state"] = "ERROR"
        desired_status["message"] = str(e)

    elif state in ["OK", "CANCELLED", "ERROR"]:
      # Terminal state, do nothing.
      pass

    else:
      desired_status["state"] = "ERROR"
      desired_status["message"] = "Unrecognized state: %r" % state

    return {"status": desired_status, "children": []}

  def do_POST(self):
    """Serve the sync() function as a JSON webhook."""
    observed = json.loads(self.rfile.read(int(self.headers["content-length"])))
    desired = self.sync(observed["parent"], observed["children"])

    self.send_response(200)
    self.send_header("Content-type", "application/json")
    self.end_headers()
    self.wfile.write(json.dumps(desired).encode('utf-8'))


class ChargeService(object):
  """ChargeService wraps an external API that send the robot to a charger.

  For this example, it just fakes the charging process.
  """

  SECONDS_FOR_FULL_CHARGE = 10

  def __init__(self):
    self._requests = {}

  def start_charging(self):
    request_id = str(uuid.uuid4())
    self._requests[request_id] = time.time()
    return request_id

  def get_progress(self, request_id):
    if request_id not in self._requests:
      raise ValueError("invalid request ID")

    charge_time = time.time() - self._requests[request_id]
    if charge_time > self.SECONDS_FOR_FULL_CHARGE:
      return 100
    else:
      return int(100 * (charge_time / self.SECONDS_FOR_FULL_CHARGE))


# Terminate process when Kubernetes sends SIGTERM.
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))

Controller.charge_service = ChargeService()
HTTPServer(("", 8000), Controller).serve_forever()


================================================
FILE: docs/how-to/examples/greeter-service/Makefile
================================================
#
# Copyright 2019 The Cloud Robotics Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

HOST_SYSTEM = $(shell uname | cut -f 1 -d_)
SYSTEM ?= $(HOST_SYSTEM)
CXX = g++
CPPFLAGS += `pkg-config --cflags protobuf grpc`
CXXFLAGS += -std=c++11 -I .
ifeq ($(SYSTEM),Darwin)
LDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\
           -lgrpc++_reflection\
           -ldl
else
LDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\
           -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed\
           -ldl
endif
PROTOC = protoc
GRPC_CPP_PLUGIN = grpc_cpp_plugin
GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`

PROTOS_PATH = ./proto/

vpath %.proto $(PROTOS_PATH)

all: greeter-server greeter-client

greeter-server: helloworld.pb.o helloworld.grpc.pb.o server/server.o
	$(CXX) $^ $(LDFLAGS) -o $@

greeter-client: helloworld.pb.o helloworld.grpc.pb.o client/client.o
	$(CXX) $^ $(LDFLAGS) -o $@

.PRECIOUS: %.grpc.pb.cc
%.grpc.pb.cc: %.proto
	$(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<

.PRECIOUS: %.pb.cc
%.pb.cc: %.proto
	$(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $<

clean:
	rm -f *.o client/*.o server/*.o *.pb *.pb.cc *.pb.h


================================================
FILE: docs/how-to/examples/greeter-service/client/Dockerfile
================================================
FROM grpc/cxx:1.12.0

WORKDIR /data

COPY client/client.cc ./client/
COPY proto/helloworld.proto ./proto/
COPY Makefile ./

RUN make greeter-client && make clean

CMD ["./greeter-client"]


================================================
FILE: docs/how-to/examples/greeter-service/client/client.cc
================================================
/*
 *
 * Copyright 2019 The Cloud Robotics Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>

#include "helloworld.grpc.pb.h"

using grpc::Channel;
using grpc::ChannelCredentials;
using grpc::ClientContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;

class GreeterClient {
 public:
  GreeterClient(std::shared_ptr<Channel> channel)
      : stub_(Greeter::NewStub(channel)) {}

  // Assembles the client's payload, sends it and presents the response back
  // from the server.
  std::string SayHello(const std::string& user) {
    // Data we are sending to the server.
    HelloRequest request;
    request.set_name(user);

    // Container for the data we expect from the server.
    HelloReply reply;

    // Context for the client. It could be used to convey extra information to
    // the server and/or tweak certain RPC behaviors.
    ClientContext context;

    // The actual RPC.
    Status status = stub_->SayHello(&context, request, &reply);

    // Act upon its status.
    if (status.ok()) {
      return reply.message();
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<Greeter::Stub> stub_;
};

int main(int argc, char** argv) {
  if (argc < 2) {
    const std::string client_path(argv[0]);
    std::cout << "Usage:" << std::endl;
    std::cout << "  " << client_path << " <address[:port]> [<name>]"
              << std::endl;
    std::cout << "Example:" << std::endl;
    std::cout << "  " << client_path
              << " www.endpoints.${PROJECT_ID}.cloud.goog:443" << std::endl;
    return 0;
  }

  // The first parameter is the server's address, optionally containing the
  // port.
  std::string grpc_endpoint(argv[1]);
  if (grpc_endpoint.find(":") == std::string::npos) {
    // Set the default port of the server.
    grpc_endpoint += ":50051";
  }

  // The optional second parameter is the name to be sent to the server.
  std::string name("world");
  if (argc >= 3) {
    name = argv[2];
  }

  std::cout << "Sending request to " << grpc_endpoint << " ..." << std::endl;

  // We are communicating via SSL to the endpoint service using the credentials
  // of the user or robot running the client.
  // We don't use credentials when connecting to localhost for testing.
  std::shared_ptr<ChannelCredentials> channel_creds;
  if (grpc_endpoint.find("localhost:") == 0 ||
      grpc_endpoint.find("127.0.0.1:") == 0) {
    channel_creds = grpc::InsecureChannelCredentials();
  } else {
    channel_creds = grpc::GoogleDefaultCredentials();
  }

  GreeterClient greeter(grpc::CreateChannel(grpc_endpoint, channel_creds));
  std::string user(name);
  std::string reply = greeter.SayHello(user);
  std::cout << "Greeter received: " << reply << std::endl;

  return 0;
}


================================================
FILE: docs/how-to/examples/greeter-service/deploy.sh
================================================
#!/bin/bash
#
# Copyright 2019 The Cloud Robotics Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o pipefail -o errexit
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd ${DIR}

function die {
  echo "$1" >&2
  exit 1
}

function push_image {
  local target=$1

  docker build -f "${target}/Dockerfile" -t "greeter-${target}" .
  docker tag "greeter-${target}" "gcr.io/${PROJECT_ID}/greeter-${target}"
  docker push "gcr.io/${PROJECT_ID}/greeter-${target}"
}

function create_config {
  cat greeter-server.yaml.tmpl | envsubst >greeter-server.yaml
}

# public functions
function push_client {
  push_image client
}

function update_config {
  create_config
  kubectl apply -f greeter-server.yaml
}

function update_server {
  push_image server
  kubectl delete pod -l 'app=greeter-server-app'
  update_config
}

function create {
  push_image server
  push_client
  update_config
}

function delete {
  create_config
  kubectl delete -f greeter-server.yaml
}

# main
if [[ -z ${PROJECT_ID} ]]; then
  die "Set PROJECT_ID first: export PROJECT_ID=[GCP project id]"
fi

if [[ ! "$1" =~ ^(create|delete|update_config|update_server|push_client)$ ]]; then
  die "Usage: $0 {create|delete|update_config|update_server|push_client}"
fi

# call arguments verbatim:
"$@"


================================================
FILE: docs/how-to/examples/greeter-service/greeter-server.yaml.tmpl
================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: greeter-server-ingress
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: GRPC
    nginx.ingress.kubernetes.io/auth-url: "http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true"
spec:
  ingressClassName: nginx
  rules:
  - host: "www.endpoints.${PROJECT_ID}.cloud.goog"
    http:
      paths:  # must match the namespace and service name in the proto
      - path: /helloworld.Greeter/
        pathType: Prefix
        backend:
          service:
            name: greeter-server-service
            # must match the port used in server.cc
            port:
              number: 50051
---
apiVersion: v1
kind: Service
metadata:
  name: greeter-server-service
spec:
  ports:
    - # optional descriptive name for the service port
      name: grpc-port
      # must match the service port specified in ingress
      port: 50051
  # the selector is used to link pods to services
  selector:
    app: greeter-server-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: greeter-server
spec:
  replicas: 1
  # all pods matching this selector belong to this deployment
  selector:
    matchLabels:
      app: greeter-server-app
  template:
    metadata:
      # the other side of the link between services and pods
      labels:
        app: greeter-server-app
    spec:
      containers:
      - name: greeter-server
        image: "gcr.io/${PROJECT_ID}/greeter-server:latest"
        ports:
          # must match the port of the service
          - containerPort: 50051


================================================
FILE: docs/how-to/examples/greeter-service/proto/helloworld.proto
================================================
// Copyright 2019 The Cloud Robotics Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting.
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}


================================================
FILE: docs/how-to/examples/greeter-service/server/Dockerfile
================================================
FROM grpc/cxx:1.12.0

WORKDIR /data

COPY server/server.cc ./server/
COPY proto/helloworld.proto ./proto/
COPY Makefile ./

RUN make greeter-server && make clean

CMD ["./greeter-server"]


================================================
FILE: docs/how-to/examples/greeter-service/server/server.cc
================================================
/*
 *
 * Copyright 2019 The Cloud Robotics Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <csignal>
#include <future>
#include <iostream>
#include <memory>
#include <string>
#include <thread>

#include <grpcpp/grpcpp.h>

#include "helloworld.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;

// The gRPC server is defined globally so that SIGTERM handler can shut it
// down when Kubernetes stops the process.
std::unique_ptr<Server> server;

// Logic and data behind the server's behavior.
class GreeterServiceImpl final : public Greeter::Service {
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::cout << "Received request: " << request->ShortDebugString()
              << std::endl;
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
};

void RunServer() {
  std::string server_address("0.0.0.0:50051");
  GreeterServiceImpl service;

  ServerBuilder builder;
  // Listen on the given address without any authentication mechanism. Cloud
  // Robotics Core ensures that clients are authenticated.
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  // Register "service" as the instance through which we'll communicate with
  // clients. In this case it corresponds to a *synchronous* service.
  builder.RegisterService(&service);
  // Finally assemble the server.
  server = builder.BuildAndStart();
  std::cout << "Server listening on " << server_address << std::endl;

  std::signal(SIGTERM, [](int) {
    // When SIGTERM is received, shutdown the gRPC server.
    server->Shutdown();
  });

  // Wait for the server to shutdown.
  server->Wait();
}

int main(int argc, char** argv) {
  RunServer();

  return 0;
}


================================================
FILE: docs/how-to/examples/hello-service/client/Dockerfile
================================================
FROM python:alpine

RUN pip install --no-cache-dir google-auth requests

WORKDIR /data

COPY client.py ./

CMD [ "python", "-u", "./client.py" ]


================================================
FILE: docs/how-to/examples/hello-service/client/client.py
================================================
import google.auth
import google.auth.transport.requests as requests

credentials, project_id = google.auth.default()

authed_session = requests.AuthorizedSession(credentials)

response = authed_session.request(
  "GET", "https://www.endpoints.[PROJECT_ID].cloud.goog/apis/hello-server")

print(response.status_code, response.reason, response.text)


================================================
FILE: docs/how-to/examples/hello-service/server/Dockerfile
================================================
FROM python:alpine

WORKDIR /data

COPY server.py ./

CMD [ "python", "-u", "./server.py" ]


================================================
FILE: docs/how-to/examples/hello-service/server/hello-server.yaml
================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-server-ingress
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true"
spec:
  ingressClassName: nginx
  rules:
  - host: www.endpoints.[PROJECT_ID].cloud.goog
    http:
      paths:
      - path: /apis/hello-server
        pathType: Prefix
        backend:
          service:
            name: hello-server-service
            port:
              number: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: hello-server-service
spec:
  ports:
  - name: hello-server-port
    port: 8000
  # the selector is used to link pods to services
  selector:
    app: hello-server-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
spec:
  # all pods matching this selector belong to this deployment
  selector:
    matchLabels:
      app: hello-server-app
  template:
    metadata:
      # the other side of the link between services and pods
      labels:
        app: hello-server-app
    spec:
      containers:
      - name: hello-server
        image: gcr.io/[PROJECT_ID]/hello-server:latest
        ports:
        # must match the port of the service
        - containerPort: 8000


================================================
FILE: docs/how-to/examples/hello-service/server/server.py
================================================
from http import server
import signal
import sys


class MyRequestHandler(server.BaseHTTPRequestHandler):
  def do_GET(self):
    print('Received a request')
    self.send_response(200)
    self.send_header('Content-Type', 'text/plain')
    self.end_headers()
    self.wfile.write(b'Server says hello!\n')


def main():
  # Terminate process when Kubernetes sends SIGTERM.
  signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))

  server_address = ('', 8000)
  httpd = server.HTTPServer(server_address, MyRequestHandler)
  httpd.serve_forever()


if __name__ == '__main__':
  main()


================================================
FILE: docs/how-to/running-ros-node.md
================================================
# Running a ROS node as a Kubernetes deployment

Estimated time: 10 min

The following instructions describe how to setup a Kubernetes cluster on a robot
running Ubuntu 20.04 and run a ROS node on it.

The installation script installs and configures:

* Docker
* A single-node Kubernetes cluster (packages: kubectl, kubeadm, kubelet)

Once you've done this, you can use Kubernetes to:

* Reduce downtime during updates with Kubernetes deployments
* Apply CPU, disk or memory quotas to individual processes
* Add additional compute nodes to the cluster, such as an Nvidia Jetson
* Use a network plugin to apply network access control
* Manage project configuration or sensitive secrets such as account credentials

For more details, refer to the [Kubernetes documentation](https://kubernetes.io/docs/home/).

## Installing the cluster on the robot

See <https://github.com/googlecloudrobotics/core/blob/main/docs/how-to/connecting-robot.md#installing-the-cluster-on-the-robot>.

## Run a ROS node with Kubernetes

If you're already using ROS on your robot, you can run a ROS node inside Kubernetes that will communicate with other nodes on the robot. If not, you can follow the [ROS tutorials](http://wiki.ros.org/ROS/Tutorials) to get started.

First, make sure you're running `roscore`. In another terminal, please run:

```shell
roscore
```

> **Caution:** If you have a more complicated ROS setup, such as a ROS master running on another machine, you might need to change `ROS_MASTER_URI` or `ROS_IP` in rostopic-echo.yaml.

You can run a ROS node by creating a Kubernetes Deployment object, and you can describe a Deployment in a YAML file.
For example, this YAML file describes a Deployment that runs `rostopic echo`.
Create file called `rostopic-echo.yaml` with the following contents:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rostopic-echo
spec:
  selector:
    matchLabels:
      app: rostopic-echo
  template:
    metadata:
      labels:
        app: rostopic-echo
    spec:
      containers:
      - name: rostopic-echo
        image: ros:melodic-ros-core
        args:
        - rostopic
        - echo
        - chatter
        env:
        - name: ROS_MASTER_URI
          value: http://192.168.9.1:11311
        - name: ROS_IP
          value: 192.168.9.1
      hostNetwork: true
```

> **Note:** For simplicity, this example uses `hostNetwork: true` to disable network isolation. Advanced users can disable host networking to improve security. For more information, see the networking documentation for <a href="http://wiki.ros.org/ROS/NetworkSetup">Docker</a>, <a href="https://kubernetes.io/docs/concepts/cluster-administration/networking/">Kubernetes</a> and <a href="http://wiki.ros.org/ROS/NetworkSetup">ROS</a>.

After creating `rostopic-echo.yaml`, use `kubectl` to apply it to your cluster:

```shell
kubectl apply -f rostopic-echo.yaml
```

Depending on your internet connection, it will take a minute or so to download the Docker image. Wait until you see `Running`:

```console
$ watch kubectl get pods -l app=rostopic-echo
NAMESPACE     NAME                                        READY   STATUS      RESTARTS   AGE
default       rostopic-echo-576cbf47c7-dtlc6              1/1     Running     0          1m
```

Now, publish a ROS message and check that it was received inside Kubernetes:

```console
$ rostopic pub -1 chatter std_msgs/String "Hello, world"
$ kubectl logs -l app=rostopic-echo
data: "Hello, world"
---
```

Kubernetes will keep this node running until you delete the deployment:

```shell
kubectl delete -f rostopic-echo.yaml
```


================================================
FILE: docs/how-to/setting-up-oauth.md
================================================
# Setting up OAuth for web UIs

Estimated time: 5 min

When a user loads a web UI hosted in the cloud Kubernetes cluster, the server has to authenticate them before allowing them to use the service.
To enable this, you'll need to set up OAuth with the Cloud Console.
Once you've completed these steps, you'll be able to access services with web UIs, such as [Grafana](https://grafana.com/).


If you haven't already, complete the [Quickstart Guide](../quickstart.md) or [Deploy Cloud Robotics Core from sources](deploy-from-sources.md) to set up your GCP project.

## Create OAuth credentials

1. Open the [cloud console](https://console.cloud.google.com/) and ensure that
     your cloud project is selected in the project selector dropdown at the top.

1. Configure the OAuth consent screen: [APIs & Services → Credentials → OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent).
   * User Type: Internal
   * Application name: My Cloud Robotics Application
   * Support email: *your email address*
   * Add `[PROJECT_ID].cloud.goog` to Authorized domains (where `[PROJECT_ID]` is your GCP project ID).
   * Leave the other fields blank.

1. Create an OAuth client ID: [APIs & Services → Credentials → Create credentials → OAuth client ID](https://console.cloud.google.com/apis/credentials/oauthclient).
   * Application type: Web application
   * Restrictions → Authorized JavaScript origins:<br/>
   `https://www.endpoints.[PROJECT_ID].cloud.goog`
   * Restrictions → Authorized redirect URIs: <br/>
   `https://www.endpoints.[PROJECT_ID].cloud.goog/oauth2/callback`
   * Click "Create".

You'll see a dialog containing the client ID and secret which we will add to your `config.sh` next.

## Update your config and redeploy

1. update your `config.sh` in the Google Cloud Storage bucket:
    ```shell
    curl -fS "https://storage.googleapis.com/cloud-robotics-releases/run-install.sh" >run-install.sh
    bash ./run-install.sh $PROJECT_ID --set-oauth
    ```
    Enter the OAuth client ID and secret from the previous step when asked.
1. Update your cloud project:
    ```shell
    bash ./run-install.sh $PROJECT_ID
    ```

After the update has been deployed, OAuth is enabled in your cloud project.
Verify that `oauth2-proxy` is running now:
```console
$ kubectl get pods

NAME               READY   STATUS    RESTARTS   AGE
...
oauth2-proxy-xxx   1/1     Running   0          1m
```

## Try it out

Open a web browser and visit `https://www.endpoints.[PROJECT_ID].cloud.goog/grafana/dashboards`, replacing `[PROJECT_ID]` with your GCP project ID.
You'll be prompted to log in with your Google account, after which you'll see a list of dashboards.
Try selecting "Kubernetes Capacity Planning" to see the resource usage of the Kubernetes cluster.


================================================
FILE: docs/how-to/using-cloud-storage.md
================================================
# Using Cloud Storage from a robot

Estimated time: 20 minutes

This page describes a simple Cloud Storage transaction that demonstrates how Google Cloud APIs can be accessed without additional authentication configuration from within the robot's Kubernetes cluster.

Normally, to access a private Cloud Storage bucket from a robot, you'd need to manage a service account for the robot through Identity & Access Management (IAM). Cloud Robotics handles the robot's identity for you, so you can connect securely without additional configuration.

1. If you haven't already, complete the [Connecting a robot to the cloud](connecting-robot.md) steps.

1. Choose a name for the Cloud Storage bucket.

    In the course of this guide, the robot will upload a file into a private bucket. The bucket namespace is global, so we must take care to choose a bucket name that is not in use yet by any other user of GCP. See also the [bucket naming requirements](https://cloud.google.com/storage/docs/naming), and [best practices](https://cloud.google.com/storage/docs/best-practices#naming).

    For this guide we will assume a bucket name like `robot-hello-world-dc1bb474`, where the part after the last dash is a random hexadecimal number. You can generate your own unique bucket name with the command

    ```shell
    echo robot-hello-world-$(tr -dc 'a-f0-9' < /dev/urandom | head -c8)
    ```

    Note: If the bucket name is already in use, creating the bucket in the next step will fail. In this case, choose a different bucket name.

1. Create the Cloud Storage bucket.

    On your workstation, run:

    ```shell
    gcloud storage buckets create gs://[BUCKET_NAME]
    ```

    Replace `[BUCKET_NAME]` with the name of the bucket you created, e.g., `robot-hello-world-dc1bb474`.
    `gcloud storage` contains the sub-commands for accessing Cloud Storage, it is part of the `gcloud-sdk` package.

    Note that the bucket is not publicly writable, as can be verified in the [Cloud Storage browser](https://console.cloud.google.com/storage/browser).

1. Drop a file into the bucket from the robot.

    On the robot, run:

    ```console
    docker pull python:alpine
    kubectl run python --restart=Never --rm -ti --image=python:alpine -- /bin/sh
    # apk add gcc musl-dev libffi-dev
    # pip3 install google-cloud-storage
    # python3
    >>> from google.cloud import storage
    >>> client = storage.Client()
    >>> bucket = client.bucket("[BUCKET_NAME]")
    >>> buck
Download .txt
gitextract_mwr70acn/

├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .dockerignore
├── .editorconfig
├── .github/
│   ├── ci/
│   │   ├── .bazelrc
│   │   ├── Dockerfile.integration-test-image
│   │   ├── common.sh
│   │   ├── deploy_navtest.sh
│   │   ├── deploy_navtest_cloudbuild.yaml
│   │   ├── deployments/
│   │   │   ├── robco-integration-test/
│   │   │   │   ├── config.sh
│   │   │   │   └── kubernetes/
│   │   │   │       └── k8s-relay-rollout.yaml
│   │   │   └── robco-navtest/
│   │   │       └── config.sh
│   │   ├── integration_test.sh
│   │   ├── integration_test_cloudbuild.yaml
│   │   ├── integration_test_image_builder.sh
│   │   ├── presubmit.sh
│   │   └── release_binary.sh
│   ├── dependabot.yml
│   └── workflows/
│       ├── check-bazel.yml
│       ├── postsubmit.yml
│       ├── presubmit.yml
│       └── release.yml
├── .gitignore
├── .pep8
├── BUILD.bazel
├── CONTRIBUTING.md
├── LICENSE
├── METADATA
├── MODULE.bazel
├── README.md
├── bazel/
│   ├── BUILD.bazel
│   ├── BUILD.sysroot
│   ├── app.bzl
│   ├── app_chart.bzl
│   ├── build_rules/
│   │   ├── app_chart/
│   │   │   ├── BUILD.bazel
│   │   │   ├── Chart.yaml.template
│   │   │   ├── cache_gcr_credentials.bzl
│   │   │   ├── cache_gcr_credentials.sh.tpl
│   │   │   ├── push_all.bzl
│   │   │   ├── push_all.sh.tpl
│   │   │   ├── run_parallel.bzl
│   │   │   ├── run_parallel.sh.tpl
│   │   │   ├── values-cloud.yaml
│   │   │   └── values-robot.yaml
│   │   ├── copy.bzl
│   │   ├── helm_chart.bzl
│   │   └── helm_template.bzl
│   ├── container_push.bzl
│   └── debug_repository.bzl
├── config.sh.tmpl
├── current_versions.txt
├── deploy.sh
├── docs/
│   ├── .gitignore
│   ├── _config.yml
│   ├── concepts/
│   │   ├── app-management.md
│   │   ├── config.md
│   │   ├── device_identity.md
│   │   └── federation.md
│   ├── developers/
│   │   └── debug-auth.md
│   ├── how-to/
│   │   ├── connecting-robot.md
│   │   ├── creating-declarative-api.md
│   │   ├── deploy-from-sources.md
│   │   ├── deploying-grpc-service.md
│   │   ├── deploying-service.md
│   │   ├── examples/
│   │   │   ├── charge-service/
│   │   │   │   ├── Dockerfile
│   │   │   │   ├── charge-action.yaml
│   │   │   │   ├── charge-controller.yaml
│   │   │   │   ├── charge-crd.yaml
│   │   │   │   └── server.py
│   │   │   ├── greeter-service/
│   │   │   │   ├── Makefile
│   │   │   │   ├── client/
│   │   │   │   │   ├── Dockerfile
│   │   │   │   │   └── client.cc
│   │   │   │   ├── deploy.sh
│   │   │   │   ├── greeter-server.yaml.tmpl
│   │   │   │   ├── proto/
│   │   │   │   │   └── helloworld.proto
│   │   │   │   └── server/
│   │   │   │       ├── Dockerfile
│   │   │   │       └── server.cc
│   │   │   └── hello-service/
│   │   │       ├── client/
│   │   │       │   ├── Dockerfile
│   │   │       │   └── client.py
│   │   │       └── server/
│   │   │           ├── Dockerfile
│   │   │           ├── hello-server.yaml
│   │   │           └── server.py
│   │   ├── running-ros-node.md
│   │   ├── setting-up-oauth.md
│   │   └── using-cloud-storage.md
│   ├── index.md
│   ├── overview.md
│   └── quickstart.md
├── new_versions.txt
├── non_module_deps.bzl
├── nvchecker.toml
├── scripts/
│   ├── BUILD.bazel
│   ├── backup_robots.sh
│   ├── check-images.sh
│   ├── common.sh
│   ├── config.sh
│   ├── include-config.sh
│   ├── migrate.sh
│   ├── pre-commit
│   ├── robot-sim.sh
│   └── set-config.sh
├── src/
│   ├── .gitignore
│   ├── BUILD.bazel
│   ├── README.md
│   ├── app_charts/
│   │   ├── BUILD.bazel
│   │   ├── README.md
│   │   ├── akri/
│   │   │   ├── BUILD.bazel
│   │   │   ├── akri-robot.values.yaml
│   │   │   ├── robot/
│   │   │   │   └── akri.yaml
│   │   │   └── values-robot.yaml
│   │   ├── base/
│   │   │   ├── BUILD.bazel
│   │   │   ├── README.md
│   │   │   ├── app_management_test.sh
│   │   │   ├── cert-manager-cloud.values.yaml
│   │   │   ├── cert-manager-google-cas-issuer-cloud.values.yaml
│   │   │   ├── cert-manager-robot.values.yaml
│   │   │   ├── cloud/
│   │   │   │   ├── app-management-policy.yaml
│   │   │   │   ├── app-management.yaml
│   │   │   │   ├── apps-crd.yaml
│   │   │   │   ├── cert-ingress.yaml
│   │   │   │   ├── cert-manager-certificates.yaml
│   │   │   │   ├── cert-manager-google-cas-issuer.yaml
│   │   │   │   ├── cert-manager-issuers.yaml
│   │   │   │   ├── cert-manager.yaml
│   │   │   │   ├── cr-syncer-auth-webhook.yaml
│   │   │   │   ├── cr-syncer-policy.yaml
│   │   │   │   ├── domain-redirect.yaml
│   │   │   │   ├── fluentd-metrics.yaml
│   │   │   │   ├── kubernetes-api.yaml
│   │   │   │   ├── namespace.yaml
│   │   │   │   ├── nginx-ingress-controller-policy.yaml
│   │   │   │   ├── nginx-ingress-controller.yaml
│   │   │   │   ├── oauth2-proxy.yaml
│   │   │   │   ├── registry-crd.yaml
│   │   │   │   ├── registry-policy.yaml
│   │   │   │   ├── relay-dashboards.yaml
│   │   │   │   ├── token-vendor-app-fwd.yaml
│   │   │   │   └── token-vendor-rollout.yaml
│   │   │   ├── fluent-bit-helm.sh
│   │   │   ├── fluent-bit-values.yaml
│   │   │   ├── relay-dashboard.json
│   │   │   ├── robot/
│   │   │   │   ├── app-management.yaml
│   │   │   │   ├── cert-manager-certificates.yaml
│   │   │   │   ├── cert-manager-issuers.yaml
│   │   │   │   ├── cert-manager.yaml
│   │   │   │   ├── cr-syncer.yaml
│   │   │   │   ├── fluent-bit.yaml
│   │   │   │   ├── fluentd-gcp-addon.yaml
│   │   │   │   ├── fluentd-metrics.yaml
│   │   │   │   ├── gcr-credential-refresher.yaml
│   │   │   │   └── metadata-server.yaml
│   │   │   ├── values-cloud.yaml
│   │   │   └── values-robot.yaml
│   │   ├── k8s-relay/
│   │   │   ├── BUILD.bazel
│   │   │   ├── cloud/
│   │   │   │   ├── ingress.yaml
│   │   │   │   ├── kubernetes-relay-server.yaml
│   │   │   │   ├── service-monitor.yaml
│   │   │   │   └── service.yaml
│   │   │   ├── robot/
│   │   │   │   └── kubernetes-relay-client.yaml
│   │   │   ├── values-cloud.yaml
│   │   │   └── values-robot.yaml
│   │   ├── mission-crd/
│   │   │   ├── BUILD.bazel
│   │   │   ├── mission_crd.yaml
│   │   │   └── values.yaml
│   │   ├── platform-apps/
│   │   │   ├── BUILD.bazel
│   │   │   └── values.yaml
│   │   ├── prometheus/
│   │   │   ├── BUILD.bazel
│   │   │   ├── README.md
│   │   │   ├── cloud/
│   │   │   │   ├── app.yaml
│   │   │   │   ├── base-alerts.yaml
│   │   │   │   ├── federation-service-monitor.yaml
│   │   │   │   ├── grafana-ingress.yaml
│   │   │   │   ├── prometheus-ingress.yaml
│   │   │   │   ├── prometheus-operator.yaml
│   │   │   │   ├── prometheus-relay.yaml
│   │   │   │   └── storage-class.yaml
│   │   │   ├── prometheus-cloud.values.yaml
│   │   │   ├── prometheus-robot.values.yaml
│   │   │   ├── robot/
│   │   │   │   ├── hw-exporter.yaml
│   │   │   │   ├── prometheus-adapter.yaml
│   │   │   │   ├── prometheus-operator.yaml
│   │   │   │   ├── prometheus-relay-client.yaml
│   │   │   │   └── smartctl-exporter.yaml
│   │   │   ├── update_prometheus_adapter.sh
│   │   │   └── values-cloud.yaml
│   │   └── token-vendor/
│   │       ├── BUILD.bazel
│   │       ├── cloud/
│   │       │   ├── dashboard.yaml
│   │       │   ├── ingress.yaml
│   │       │   ├── service-monitor.yaml
│   │       │   ├── service.yaml
│   │       │   ├── token-vendor-policy.yaml
│   │       │   └── token-vendor.yaml
│   │       └── dashboard.json
│   ├── bootstrap/
│   │   ├── cloud/
│   │   │   ├── BUILD.bazel
│   │   │   ├── INSTALL_FROM_BINARY
│   │   │   ├── run-install.sh
│   │   │   └── terraform/
│   │   │       ├── .gitignore
│   │   │       ├── BUILD.bazel
│   │   │       ├── README.md
│   │   │       ├── address.tf
│   │   │       ├── certificate-authority.tf
│   │   │       ├── cluster.tf
│   │   │       ├── dns.tf
│   │   │       ├── endpoints.tf
│   │   │       ├── gcs.tf
│   │   │       ├── input.tf
│   │   │       ├── logging.tf
│   │   │       ├── multi-cluster-ingress.tf
│   │   │       ├── output.tf
│   │   │       ├── project.tf
│   │   │       ├── provider.tf
│   │   │       ├── registry.tf
│   │   │       ├── service-account.tf
│   │   │       ├── versions.tf
│   │   │       ├── workload-identity.tf
│   │   │       └── www.yaml
│   │   └── robot/
│   │       ├── BUILD.bazel
│   │       └── setup_robot.sh
│   ├── go/
│   │   ├── cmd/
│   │   │   ├── app-rollout-controller/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── main.go
│   │   │   ├── chart-assignment-controller/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── main.go
│   │   │   ├── cr-syncer/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── health.go
│   │   │   │   ├── health_test.go
│   │   │   │   ├── main.go
│   │   │   │   ├── main_test.go
│   │   │   │   ├── syncer.go
│   │   │   │   └── syncer_test.go
│   │   │   ├── cr-syncer-auth-webhook/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   ├── request.go
│   │   │   │   └── request_test.go
│   │   │   ├── gcr-credential-refresher/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── main.go
│   │   │   ├── http-relay-client/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── client/
│   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   ├── client.go
│   │   │   │   │   └── client_test.go
│   │   │   │   └── main.go
│   │   │   ├── http-relay-server/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── README.md
│   │   │   │   ├── main.go
│   │   │   │   └── server/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── broker.go
│   │   │   │       ├── broker_test.go
│   │   │   │       ├── server.go
│   │   │   │       └── server_test.go
│   │   │   ├── hw-exporter/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   └── main_test.go
│   │   │   ├── metadata-server/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── coredns.go
│   │   │   │   ├── coredns_test.go
│   │   │   │   ├── main.go
│   │   │   │   ├── main_test.go
│   │   │   │   ├── metadata.go
│   │   │   │   ├── metadata_test.go
│   │   │   │   └── nftables.go
│   │   │   ├── setup-dev/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   └── setup-dev.md
│   │   │   ├── setup-robot/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── main.go
│   │   │   │   └── main_test.go
│   │   │   ├── synk/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── README.md
│   │   │   │   └── synk.go
│   │   │   └── token-vendor/
│   │   │       ├── BUILD.bazel
│   │   │       ├── README.md
│   │   │       ├── api/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── api.go
│   │   │       │   └── v1/
│   │   │       │       ├── BUILD.bazel
│   │   │       │       ├── testdata/
│   │   │       │       │   ├── README.md
│   │   │       │       │   ├── cloudiot/
│   │   │       │       │   │   ├── describe_device.json
│   │   │       │       │   │   └── describe_device_expired_key.json
│   │   │       │       │   ├── rsa_cert.pem
│   │   │       │       │   └── rsa_private.pem
│   │   │       │       ├── v1.go
│   │   │       │       └── v1_test.go
│   │   │       ├── app/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── tokenvendor.go
│   │   │       │   └── tokenvendor_test.go
│   │   │       ├── main.go
│   │   │       ├── oauth/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── cache.go
│   │   │       │   ├── cache_test.go
│   │   │       │   ├── jwt/
│   │   │       │   │   ├── BUILD.bazel
│   │   │       │   │   ├── jwt.go
│   │   │       │   │   └── jwt_test.go
│   │   │       │   └── verifier.go
│   │   │       ├── repository/
│   │   │       │   ├── BUILD.bazel
│   │   │       │   ├── k8s/
│   │   │       │   │   ├── BUILD.bazel
│   │   │       │   │   ├── k8s.go
│   │   │       │   │   └── k8s_test.go
│   │   │       │   ├── memory/
│   │   │       │   │   ├── BUILD.bazel
│   │   │       │   │   ├── memory.go
│   │   │       │   │   └── memory_test.go
│   │   │       │   └── repository.go
│   │   │       ├── testdata/
│   │   │       │   ├── describe_device_a.json
│   │   │       │   ├── describe_device_b.json
│   │   │       │   ├── describe_device_b_blocked.json
│   │   │       │   └── list_devices.json
│   │   │       └── tokensource/
│   │   │           ├── BUILD.bazel
│   │   │           ├── gcp.go
│   │   │           └── gcp_test.go
│   │   ├── generate.sh
│   │   ├── pkg/
│   │   │   ├── apis/
│   │   │   │   ├── apps/
│   │   │   │   │   └── v1alpha1/
│   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │       ├── doc.go
│   │   │   │   │       ├── register.go
│   │   │   │   │       ├── types.go
│   │   │   │   │       └── zz_generated.deepcopy.go
│   │   │   │   └── registry/
│   │   │   │       └── v1alpha1/
│   │   │   │           ├── BUILD.bazel
│   │   │   │           ├── doc.go
│   │   │   │           ├── register.go
│   │   │   │           ├── types.go
│   │   │   │           └── zz_generated.deepcopy.go
│   │   │   ├── client/
│   │   │   │   ├── informers/
│   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   ├── apps/
│   │   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   │   ├── interface.go
│   │   │   │   │   │   └── v1alpha1/
│   │   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │   │       ├── app.go
│   │   │   │   │   │       ├── approllout.go
│   │   │   │   │   │       ├── chartassignment.go
│   │   │   │   │   │       ├── interface.go
│   │   │   │   │   │       └── resourceset.go
│   │   │   │   │   ├── factory.go
│   │   │   │   │   ├── generic.go
│   │   │   │   │   ├── internalinterfaces/
│   │   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   │   └── factory_interfaces.go
│   │   │   │   │   └── registry/
│   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │       ├── interface.go
│   │   │   │   │       └── v1alpha1/
│   │   │   │   │           ├── BUILD.bazel
│   │   │   │   │           ├── interface.go
│   │   │   │   │           └── robot.go
│   │   │   │   ├── listers/
│   │   │   │   │   ├── apps/
│   │   │   │   │   │   └── v1alpha1/
│   │   │   │   │   │       ├── BUILD.bazel
│   │   │   │   │   │       ├── app.go
│   │   │   │   │   │       ├── approllout.go
│   │   │   │   │   │       ├── chartassignment.go
│   │   │   │   │   │       ├── expansion_generated.go
│   │   │   │   │   │       └── resourceset.go
│   │   │   │   │   └── registry/
│   │   │   │   │       └── v1alpha1/
│   │   │   │   │           ├── BUILD.bazel
│   │   │   │   │           ├── expansion_generated.go
│   │   │   │   │           └── robot.go
│   │   │   │   └── versioned/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── clientset.go
│   │   │   │       ├── doc.go
│   │   │   │       ├── fake/
│   │   │   │       │   ├── BUILD.bazel
│   │   │   │       │   ├── clientset_generated.go
│   │   │   │       │   ├── doc.go
│   │   │   │       │   └── register.go
│   │   │   │       ├── scheme/
│   │   │   │       │   ├── BUILD.bazel
│   │   │   │       │   ├── doc.go
│   │   │   │       │   └── register.go
│   │   │   │       └── typed/
│   │   │   │           ├── apps/
│   │   │   │           │   └── v1alpha1/
│   │   │   │           │       ├── BUILD.bazel
│   │   │   │           │       ├── app.go
│   │   │   │           │       ├── approllout.go
│   │   │   │           │       ├── apps_client.go
│   │   │   │           │       ├── chartassignment.go
│   │   │   │           │       ├── doc.go
│   │   │   │           │       ├── fake/
│   │   │   │           │       │   ├── BUILD.bazel
│   │   │   │           │       │   ├── doc.go
│   │   │   │           │       │   ├── fake_app.go
│   │   │   │           │       │   ├── fake_approllout.go
│   │   │   │           │       │   ├── fake_apps_client.go
│   │   │   │           │       │   ├── fake_chartassignment.go
│   │   │   │           │       │   └── fake_resourceset.go
│   │   │   │           │       ├── generated_expansion.go
│   │   │   │           │       └── resourceset.go
│   │   │   │           └── registry/
│   │   │   │               └── v1alpha1/
│   │   │   │                   ├── BUILD.bazel
│   │   │   │                   ├── doc.go
│   │   │   │                   ├── fake/
│   │   │   │                   │   ├── BUILD.bazel
│   │   │   │                   │   ├── doc.go
│   │   │   │                   │   ├── fake_registry_client.go
│   │   │   │                   │   └── fake_robot.go
│   │   │   │                   ├── generated_expansion.go
│   │   │   │                   ├── registry_client.go
│   │   │   │                   └── robot.go
│   │   │   ├── configutil/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── config_reader.go
│   │   │   │   └── config_reader_test.go
│   │   │   ├── controller/
│   │   │   │   ├── approllout/
│   │   │   │   │   ├── BUILD.bazel
│   │   │   │   │   ├── controller.go
│   │   │   │   │   └── controller_test.go
│   │   │   │   └── chartassignment/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── controller.go
│   │   │   │       ├── release.go
│   │   │   │       ├── release_test.go
│   │   │   │       ├── validator.go
│   │   │   │       └── validator_test.go
│   │   │   ├── gcr/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── update_gcr_credential_test.go
│   │   │   │   └── update_gcr_credentials.go
│   │   │   ├── kubetest/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── kubetest.go
│   │   │   ├── kubeutils/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── kubeutils.go
│   │   │   ├── robotauth/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── robotauth.go
│   │   │   │   └── robotauth_test.go
│   │   │   ├── setup/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   ├── setupcommon.go
│   │   │   │   ├── setupcommon_test.go
│   │   │   │   └── util/
│   │   │   │       ├── BUILD.bazel
│   │   │   │       ├── factory.go
│   │   │   │       └── fake.go
│   │   │   └── synk/
│   │   │       ├── BUILD.bazel
│   │   │       ├── interface.go
│   │   │       ├── sort.go
│   │   │       ├── sort_test.go
│   │   │       ├── synk.go
│   │   │       └── synk_test.go
│   │   └── tests/
│   │       ├── BUILD.bazel
│   │       ├── apps/
│   │       │   ├── BUILD.bazel
│   │       │   ├── apps_test.go
│   │       │   └── run.sh
│   │       ├── k8s_integration_test.go
│   │       ├── k8s_integration_test_auth_helper.go
│   │       ├── relay/
│   │       │   ├── BUILD.bazel
│   │       │   ├── in_process_relay_test.go
│   │       │   └── nok8s_relay_test.go
│   │       ├── relay-bench.sh
│   │       └── relay_test.sh
│   ├── go.mod
│   ├── go.sum
│   ├── gomod.sh
│   └── proto/
│       └── http-relay/
│           ├── BUILD.bazel
│           ├── http_over_rpc.proto
│           └── unused.go
└── third_party/
    ├── BUILD
    ├── BUILD.bazel
    ├── README.md
    ├── akri/
    │   ├── BUILD.bazel
    │   ├── akri-0.12.9.tgz
    │   ├── akri-configuration-crd.yaml
    │   ├── akri-instance-crd.yaml
    │   └── update-akri.sh
    ├── app_crd.BUILD
    ├── cert-manager/
    │   ├── BUILD.bazel
    │   └── cert-manager-v1.16.3.tgz
    ├── cert-manager-google-cas-issuer/
    │   ├── BUILD.bazel
    │   └── cert-manager-google-cas-issuer-v0.6.2.tgz
    ├── fluentd_gcp_addon/
    │   ├── BUILD.bazel
    │   ├── fluentd-gcp-configmap.yaml
    │   └── fluentd-gcp-ds.yaml
    ├── helm2/
    │   └── BUILD.bazel
    ├── helm3/
    │   └── BUILD.bazel
    ├── ingress-nginx.BUILD
    ├── kube-prometheus-stack/
    │   ├── 00-crds.yaml
    │   ├── 01-crds.yaml
    │   ├── BUILD.bazel
    │   ├── kube-prometheus-stack-72.9.1.tgz
    │   └── update_crd.sh
    ├── kubernetes_proto/
    │   ├── meta/
    │   │   ├── BUILD.bazel
    │   │   ├── README.md
    │   │   └── generated.proto
    │   ├── runtime/
    │   │   ├── BUILD.bazel
    │   │   └── generated.proto
    │   └── schema/
    │       ├── BUILD.bazel
    │       └── generated.proto
    └── terraform.BUILD
Download .txt
SYMBOL INDEX (1320 symbols across 129 files)

FILE: docs/how-to/examples/charge-service/server.py
  class Controller (line 9) | class Controller(BaseHTTPRequestHandler):
    method sync (line 10) | def sync(self, parent, children):
    method do_POST (line 75) | def do_POST(self):
  class ChargeService (line 86) | class ChargeService(object):
    method __init__ (line 94) | def __init__(self):
    method start_charging (line 97) | def start_charging(self):
    method get_progress (line 102) | def get_progress(self, request_id):

FILE: docs/how-to/examples/greeter-service/client/client.cc
  class GreeterClient (line 35) | class GreeterClient {
    method GreeterClient (line 37) | GreeterClient(std::shared_ptr<Channel> channel)
    method SayHello (line 42) | std::string SayHello(const std::string& user) {
  function main (line 71) | int main(int argc, char** argv) {

FILE: docs/how-to/examples/greeter-service/server/server.cc
  class GreeterServiceImpl (line 43) | class GreeterServiceImpl final : public Greeter::Service {
    method Status (line 44) | Status SayHello(ServerContext* context, const HelloRequest* request,
  function RunServer (line 54) | void RunServer() {
  function main (line 78) | int main(int argc, char** argv) {

FILE: docs/how-to/examples/hello-service/server/server.py
  class MyRequestHandler (line 6) | class MyRequestHandler(server.BaseHTTPRequestHandler):
    method do_GET (line 7) | def do_GET(self):
  function main (line 15) | def main():

FILE: src/go/cmd/app-rollout-controller/main.go
  function main (line 53) | func main() {
  function runController (line 78) | func runController(ctx context.Context, cfg *rest.Config, params map[str...

FILE: src/go/cmd/chart-assignment-controller/main.go
  function main (line 56) | func main() {
  function runController (line 104) | func runController(ctx context.Context, cfg *rest.Config, cluster string...

FILE: src/go/cmd/cr-syncer-auth-webhook/main.go
  constant verifyJWTEndpoint (line 53) | verifyJWTEndpoint = "/apis/core.token-vendor/v1/jwt.verify"
  constant legacyTokenPrefix (line 55) | legacyTokenPrefix = "ya29."
  type handlers (line 58) | type handlers struct
    method health (line 68) | func (h *handlers) health(w http.ResponseWriter, r *http.Request) {
    method verifyJWT (line 74) | func (h *handlers) verifyJWT(encodedJWT string) error {
    method resourceIsFiltered (line 103) | func (h *handlers) resourceIsFiltered(groupKind string) bool {
    method validateRequest (line 111) | func (h *handlers) validateRequest(r *http.Request, robotName string) ...
    method auth (line 146) | func (h *handlers) auth(w http.ResponseWriter, r *http.Request) {
  function newHandlers (line 62) | func newHandlers() handlers {
  function main (line 186) | func main() {

FILE: src/go/cmd/cr-syncer-auth-webhook/request.go
  constant robotNameSelectorPrefix (line 21) | robotNameSelectorPrefix = "cloudrobotics.com/robot-name="
  type incomingRequest (line 24) | type incomingRequest struct
  function parseURL (line 37) | func parseURL(urlString string) (*incomingRequest, error) {

FILE: src/go/cmd/cr-syncer-auth-webhook/request_test.go
  function TestParseURL (line 9) | func TestParseURL(t *testing.T) {
  function TestParseURLErrors (line 76) | func TestParseURLErrors(t *testing.T) {

FILE: src/go/cmd/cr-syncer/health.go
  type handler (line 27) | type handler struct
    method ServeHTTP (line 36) | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  function newHealthHandler (line 32) | func newHealthHandler(ctx context.Context, client dynamic.Interface) htt...

FILE: src/go/cmd/cr-syncer/health_test.go
  function TestHealthy (line 16) | func TestHealthy(t *testing.T) {
  function TestHealthyForBadRequest (line 35) | func TestHealthyForBadRequest(t *testing.T) {
  function TestUnhealthy (line 58) | func TestUnhealthy(t *testing.T) {

FILE: src/go/cmd/cr-syncer/main.go
  constant resyncPeriod (line 82) | resyncPeriod = 5 * time.Minute
  function init (line 101) | func init() {
  type PrefixingRoundtripper (line 140) | type PrefixingRoundtripper struct
    method RoundTrip (line 145) | func (pr *PrefixingRoundtripper) RoundTrip(r *http.Request) (*http.Res...
  type ctxRoundTripper (line 157) | type ctxRoundTripper struct
    method RoundTrip (line 162) | func (r *ctxRoundTripper) RoundTrip(req *http.Request) (*http.Response...
  function restConfigForRemote (line 167) | func restConfigForRemote(ctx context.Context) (*rest.Config, error) {
  type CrdChange (line 207) | type CrdChange struct
  function streamCrds (line 212) | func streamCrds(done <-chan struct{}, clientset crdclientset.Interface, ...
  function main (line 238) | func main() {
  function mustNewTagKey (line 336) | func mustNewTagKey(s string) tag.Key {

FILE: src/go/cmd/cr-syncer/main_test.go
  function TestStreamCrdsSeesPreexistingObject (line 31) | func TestStreamCrdsSeesPreexistingObject(t *testing.T) {
  function TestStreamCrdsSeesAdditionAndDeletion (line 64) | func TestStreamCrdsSeesAdditionAndDeletion(t *testing.T) {
  function TestStreamCrdsSeesUpdate (line 102) | func TestStreamCrdsSeesUpdate(t *testing.T) {

FILE: src/go/cmd/cr-syncer/syncer.go
  constant annotationStatusSubtree (line 45) | annotationStatusSubtree     = "cr-syncer.cloudrobotics.com/status-subtree"
  constant annotationFilterByRobotName (line 46) | annotationFilterByRobotName = "cr-syncer.cloudrobotics.com/filter-by-rob...
  constant annotationSpecSource (line 47) | annotationSpecSource        = "cr-syncer.cloudrobotics.com/spec-source"
  constant labelRobotName (line 50) | labelRobotName = "cloudrobotics.com/robot-name"
  constant annotationResourceVersion (line 56) | annotationResourceVersion = "cr-syncer.cloudrobotics.com/remote-resource...
  constant cloudClusterName (line 58) | cloudClusterName = "cloud"
  function init (line 80) | func init() {
  function removeFinalizer (line 104) | func removeFinalizer(ctx context.Context, client dynamic.ResourceInterfa...
  type crSyncer (line 131) | type crSyncer struct
    method newInformer (line 237) | func (s *crSyncer) newInformer(client dynamic.ResourceInterface) cache...
    method startInformers (line 257) | func (s *crSyncer) startInformers() error {
    method stopInformers (line 278) | func (s *crSyncer) stopInformers() {
    method restartInformers (line 285) | func (s *crSyncer) restartInformers() error {
    method setupInformerHandlers (line 292) | func (s *crSyncer) setupInformerHandlers(
    method processNextWorkItem (line 322) | func (s *crSyncer) processNextWorkItem(
    method run (line 373) | func (s *crSyncer) run() {
    method stop (line 405) | func (s *crSyncer) stop() {
    method syncDownstream (line 413) | func (s *crSyncer) syncDownstream(key string) error {
    method syncUpstream (line 521) | func (s *crSyncer) syncUpstream(key string) error {
  function getStorageVersionIndex (line 156) | func getStorageVersionIndex(crd crdtypes.CustomResourceDefinition) (int,...
  function newCRSyncer (line 165) | func newCRSyncer(
  function isNotFoundError (line 617) | func isNotFoundError(err error) bool {
  type apiError (line 622) | type apiError struct
    method Error (line 627) | func (e apiError) Error() string {
  function newAPIErrorf (line 631) | func newAPIErrorf(o *unstructured.Unstructured, format string, args ...i...
  function keyFunc (line 637) | func keyFunc(obj interface{}) (string, bool) {
  function setAnnotation (line 646) | func setAnnotation(o *unstructured.Unstructured, key, value string) {
  function deleteAnnotation (line 655) | func deleteAnnotation(o *unstructured.Unstructured, key string) {
  function copyStatus (line 668) | func copyStatus(dst, src *unstructured.Unstructured) {

FILE: src/go/cmd/cr-syncer/syncer_test.go
  function filterReadActions (line 38) | func filterReadActions(actions []k8stest.Action) (ret []k8stest.Action) {
  type fixture (line 48) | type fixture struct
    method newCRSyncer (line 66) | func (f *fixture) newCRSyncer(crd crdtypes.CustomResourceDefinition, r...
    method addLocalObjects (line 107) | func (f *fixture) addLocalObjects(objs ...runtime.Object) {
    method addRemoteObjects (line 111) | func (f *fixture) addRemoteObjects(objs ...runtime.Object) {
    method expectLocalActions (line 115) | func (f *fixture) expectLocalActions(as ...k8stest.Action) {
    method expectRemoteActions (line 119) | func (f *fixture) expectRemoteActions(as ...k8stest.Action) {
    method verifyWriteActions (line 123) | func (f *fixture) verifyWriteActions() {
  function newFixture (line 62) | func newFixture(t *testing.T) *fixture {
  function testCRD (line 139) | func testCRD(scope crdtypes.ResourceScope) crdtypes.CustomResourceDefini...
  function newTestCR (line 166) | func newTestCR(name string, spec, status interface{}) *unstructured.Unst...
  function newClusterScopedTestCR (line 181) | func newClusterScopedTestCR(name string, spec, status interface{}) *unst...
  function TestSyncUpstream_createSpec (line 194) | func TestSyncUpstream_createSpec(t *testing.T) {
  function TestSyncClusterScopedCRUpstream_createSpec (line 218) | func TestSyncClusterScopedCRUpstream_createSpec(t *testing.T) {
  function TestSyncUpstream_updateSpec (line 242) | func TestSyncUpstream_updateSpec(t *testing.T) {
  function TestSyncUpstream_propagateDelete (line 269) | func TestSyncUpstream_propagateDelete(t *testing.T) {
  function TestSyncDownstream_deleteOrphan (line 297) | func TestSyncDownstream_deleteOrphan(t *testing.T) {
  function TestSyncDownstream_statusFull (line 321) | func TestSyncDownstream_statusFull(t *testing.T) {
  function TestSyncDownstream_statusWithObservedGeneration (line 351) | func TestSyncDownstream_statusWithObservedGeneration(t *testing.T) {
  function TestSyncDownstream_statusSubtree (line 390) | func TestSyncDownstream_statusSubtree(t *testing.T) {
  function TestSyncDownstream_downstreamNotFound (line 430) | func TestSyncDownstream_downstreamNotFound(t *testing.T) {
  function TestCRSyncer_populateWorkqueue (line 476) | func TestCRSyncer_populateWorkqueue(t *testing.T) {
  function TestCRSyncer_populateWorkqueueWithFilter (line 517) | func TestCRSyncer_populateWorkqueueWithFilter(t *testing.T) {
  function channelFromQueue (line 555) | func channelFromQueue(t *testing.T, queue workqueue.Interface, inf cache...

FILE: src/go/cmd/gcr-credential-refresher/main.go
  constant updateInterval (line 34) | updateInterval = 10 * time.Minute
  function updateCredentials (line 37) | func updateCredentials(ctx context.Context) error {
  function main (line 64) | func main() {

FILE: src/go/cmd/http-relay-client/client/client.go
  type ClientConfig (line 66) | type ClientConfig struct
  type RelayServerError (line 97) | type RelayServerError struct
    method Error (line 105) | func (e *RelayServerError) Error() string {
  function NewRelayServerError (line 101) | func NewRelayServerError(msg string) error {
  function DefaultClientConfig (line 109) | func DefaultClientConfig() ClientConfig {
  type Client (line 148) | type Client struct
    method Start (line 158) | func (c *Client) Start() {
    method getRequest (line 268) | func (c *Client) getRequest(remote *http.Client, relayURL string) (*pb...
    method createBackendRequest (line 317) | func (c *Client) createBackendRequest(breq *pb.HttpRequest) (*http.Req...
    method postResponse (line 402) | func (c *Client) postResponse(remote *http.Client, br *pb.HttpResponse...
    method streamBytes (line 437) | func (c *Client) streamBytes(id string, in io.ReadCloser, out chan<- [...
    method buildResponses (line 470) | func (c *Client) buildResponses(in <-chan []byte, resp *pb.HttpRespons...
    method postErrorResponse (line 517) | func (c *Client) postErrorResponse(remote *http.Client, id string, mes...
    method streamToBackend (line 539) | func (c *Client) streamToBackend(remote *http.Client, id string, backe...
    method handleRequest (line 594) | func (c *Client) handleRequest(remote *http.Client, local *http.Client...
    method localProxy (line 720) | func (c *Client) localProxy(remote, local *http.Client) error {
    method localProxyWorker (line 763) | func (c *Client) localProxyWorker(remote, local *http.Client) {
    method buildRelayURL (line 774) | func (c *Client) buildRelayURL() string {
  function NewClient (line 152) | func NewClient(config ClientConfig) *Client {
  function addServiceName (line 263) | func addServiceName(span *trace.Span) {
  function marshalHeader (line 301) | func marshalHeader(h *http.Header) []*pb.HttpHeader {
  function extractRequestHeader (line 311) | func extractRequestHeader(breq *pb.HttpRequest, header *http.Header) {
  function makeBackendRequest (line 362) | func makeBackendRequest(ctx context.Context, local *http.Client, req *ht...

FILE: src/go/cmd/http-relay-client/client/client_test.go
  function assertMocksDoneWithin (line 30) | func assertMocksDoneWithin(t *testing.T, d time.Duration) {
  function TestAssertMocksDoneWithin_SucceedsWhenMocksAreDone (line 42) | func TestAssertMocksDoneWithin_SucceedsWhenMocksAreDone(t *testing.T) {
  function TestAssertMocksDoneWithin_FailsWhenMocksNotDone (line 46) | func TestAssertMocksDoneWithin_FailsWhenMocksNotDone(t *testing.T) {
  function TestLocalProxy (line 56) | func TestLocalProxy(t *testing.T) {
  function TestBackendError (line 114) | func TestBackendError(t *testing.T) {
  function TestServerTimeout (line 183) | func TestServerTimeout(t *testing.T) {
  function TestBuildResponsesTimesOut (line 214) | func TestBuildResponsesTimesOut(t *testing.T) {

FILE: src/go/cmd/http-relay-client/main.go
  function init (line 45) | func init() {
  function main (line 97) | func main() {

FILE: src/go/cmd/http-relay-server/main.go
  function main (line 48) | func main() {

FILE: src/go/cmd/http-relay-server/server/broker.go
  function init (line 69) | func init() {
  type pendingResponse (line 77) | type pendingResponse struct
  type RelayClientUnavailableError (line 100) | type RelayClientUnavailableError struct
    method Error (line 104) | func (e *RelayClientUnavailableError) Error() string {
  type broker (line 114) | type broker struct
    method Healthy (line 129) | func (r *broker) Healthy() error {
    method RelayRequest (line 137) | func (r *broker) RelayRequest(server string, request *pb.HttpRequest) ...
    method StopRelayRequest (line 182) | func (r *broker) StopRelayRequest(requestId string) {
    method GetRequest (line 190) | func (r *broker) GetRequest(ctx context.Context, server, path string) ...
    method GetRequestStream (line 217) | func (r *broker) GetRequestStream(id string) ([]byte, bool) {
    method PutRequestStream (line 237) | func (r *broker) PutRequestStream(id string, data []byte) bool {
    method SendResponse (line 260) | func (r *broker) SendResponse(resp *pb.HttpResponse) error {
    method ReapInactiveRequests (line 317) | func (r *broker) ReapInactiveRequests(threshold time.Time) {
  function newBroker (line 120) | func newBroker() *broker {

FILE: src/go/cmd/http-relay-server/server/broker_test.go
  constant idOne (line 32) | idOne     = "idOne"
  constant idTwo (line 33) | idTwo     = "idTwo"
  constant idThree (line 34) | idThree   = "idThree"
  constant unknownID (line 35) | unknownID = "unknownID"
  type brokerConn (line 43) | type brokerConn struct
    method runSender (line 53) | func (bt *brokerConn) runSender(t *testing.T, b *broker, s string, m s...
    method bakeRequest (line 70) | func (bt *brokerConn) bakeRequest(b *broker, s string) {
    method runReceiver (line 80) | func (bt *brokerConn) runReceiver(t *testing.T, b *broker, s string, w...
    method runSenderStream (line 94) | func (bt *brokerConn) runSenderStream(t *testing.T, b *broker, s strin...
    method runReceiverStream (line 117) | func (bt *brokerConn) runReceiverStream(t *testing.T, b *broker, s str...
  function newBrokerConn (line 47) | func newBrokerConn() brokerConn {
  function TestNormalCase (line 137) | func TestNormalCase(t *testing.T) {
  function TestResponseStream (line 155) | func TestResponseStream(t *testing.T) {
  function TestMissingId (line 167) | func TestMissingId(t *testing.T) {
  function TestDuplicateId (line 175) | func TestDuplicateId(t *testing.T) {
  function TestRequestStream (line 190) | func TestRequestStream(t *testing.T) {
  function TestRequestStreamUnknownID (line 220) | func TestRequestStreamUnknownID(t *testing.T) {
  function TestTimeout (line 230) | func TestTimeout(t *testing.T) {
  function TestReapWhileSendingResponse (line 259) | func TestReapWhileSendingResponse(t *testing.T) {
  function TestReapWhileSendingRequest (line 307) | func TestReapWhileSendingRequest(t *testing.T) {

FILE: src/go/cmd/http-relay-server/server/server.go
  constant clientPrefix (line 48) | clientPrefix = "/client/"
  constant cleanShutdownTimeout (line 52) | cleanShutdownTimeout = 20 * time.Second
  constant debugLogs (line 54) | debugLogs = false
  constant DefaultPort (line 57) | DefaultPort = 80
  constant DefaultBlockSize (line 59) | DefaultBlockSize = 10 * 1024
  constant DefaultInactiveRequestTimeout (line 61) | DefaultInactiveRequestTimeout = 60 * time.Second
  type Config (line 64) | type Config struct
  type Server (line 73) | type Server struct
    method responseFilter (line 164) | func (s *Server) responseFilter(backendCtx backendContext, in <-chan *...
    method health (line 223) | func (s *Server) health(w http.ResponseWriter, r *http.Request) {
    method bidirectionalStream (line 241) | func (s *Server) bidirectionalStream(backendCtx backendContext, w http...
    method readRequestBody (line 299) | func (s *Server) readRequestBody(ctx context.Context, r *http.Request)...
    method createBackendRequest (line 306) | func (s *Server) createBackendRequest(backendCtx backendContext, r *ht...
    method relayRequest (line 327) | func (s *Server) relayRequest(ctx context.Context, backendCtx backendC...
    method waitForFirstResponseAndHandleSwitching (line 339) | func (s *Server) waitForFirstResponseAndHandleSwitching(ctx context.Co...
    method userClientRequest (line 366) | func (s *Server) userClientRequest(w http.ResponseWriter, r *http.Requ...
    method serverRequest (line 463) | func (s *Server) serverRequest(w http.ResponseWriter, r *http.Request) {
    method serverRequestStream (line 492) | func (s *Server) serverRequestStream(w http.ResponseWriter, r *http.Re...
    method serverResponse (line 515) | func (s *Server) serverResponse(w http.ResponseWriter, r *http.Request) {
    method Start (line 542) | func (s *Server) Start() {
  function NewServer (line 78) | func NewServer(conf Config) *Server {
  function createId (line 100) | func createId() string {
  function marshalHeader (line 107) | func marshalHeader(h *http.Header) []*pb.HttpHeader {
  function unmarshalHeader (line 117) | func unmarshalHeader(w http.ResponseWriter, protoHeader []*pb.HttpHeader) {
  function addServiceName (line 123) | func addServiceName(span *trace.Span) {
  function extractBackendNameAndPath (line 128) | func extractBackendNameAndPath(r *http.Request) (backendName string, pat...
  type responseChunk (line 155) | type responseChunk struct
  type backendContext (line 205) | type backendContext struct
  function newBackendContext (line 211) | func newBackendContext(r *http.Request) (*backendContext, error) {

FILE: src/go/cmd/http-relay-server/server/server_test.go
  function checkResponse (line 34) | func checkResponse(t *testing.T, resp *http.Response, wantStatus int, wa...
  function TestClientHandler (line 48) | func TestClientHandler(t *testing.T) {
  function TestClientHandlerWithChunkedResponse (line 116) | func TestClientHandlerWithChunkedResponse(t *testing.T) {
  function TestClientBadRequest (line 189) | func TestClientBadRequest(t *testing.T) {
  function nonRepeatingByteArray (line 236) | func nonRepeatingByteArray(n int) []byte {
  function TestRequestStreamHandler (line 246) | func TestRequestStreamHandler(t *testing.T) {
  function TestServerRequestResponseHandler (line 304) | func TestServerRequestResponseHandler(t *testing.T) {
  function TestServerResponseHandlerWithInvalidRequestID (line 376) | func TestServerResponseHandlerWithInvalidRequestID(t *testing.T) {
  function TestRequestToUnknownBackendResponse503 (line 404) | func TestRequestToUnknownBackendResponse503(t *testing.T) {

FILE: src/go/cmd/hw-exporter/main.go
  type pciCollector (line 30) | type pciCollector struct
    method Describe (line 46) | func (c *pciCollector) Describe(ch chan<- *prometheus.Desc) {
    method Collect (line 60) | func (c *pciCollector) Collect(ch chan<- prometheus.Metric) {
  function newPciCollector (line 34) | func newPciCollector() *pciCollector {
  function getNameOrID (line 51) | func getNameOrID(name, id string) string {
  function main (line 81) | func main() {

FILE: src/go/cmd/hw-exporter/main_test.go
  function TestPciCollector_Collect (line 42) | func TestPciCollector_Collect(t *testing.T) {

FILE: src/go/cmd/metadata-server/coredns.go
  constant configMapName (line 29) | configMapName      = "coredns"
  constant configMapNamespace (line 30) | configMapNamespace = "kube-system"
  constant corefileName (line 31) | corefileName       = "Corefile"
  constant zoneStart (line 32) | zoneStart          = ".:53 {\n"
  constant zoneStartPatched (line 33) | zoneStartPatched   = `.:53 {
  constant hostsStart (line 39) | hostsStart        = "hosts {\n"
  constant hostsStartPatched (line 40) | hostsStartPatched = `hosts hosts metadata.google.internal host.minikube....
  function getCorefile (line 50) | func getCorefile(ctx context.Context, k8s kubernetes.Interface) (*v1.Con...
  function writeCorefile (line 64) | func writeCorefile(ctx context.Context, k8s kubernetes.Interface, cm *v1...
  function PatchCorefile (line 71) | func PatchCorefile(ctx context.Context, k8s kubernetes.Interface) error {
  function RevertCorefile (line 93) | func RevertCorefile(ctx context.Context, k8s kubernetes.Interface) error {

FILE: src/go/cmd/metadata-server/coredns_test.go
  constant defaultCorefileBeforeMinikube121 (line 30) | defaultCorefileBeforeMinikube121 = `.:53 {
  constant modifiedCorefileBeforeMinikube121 (line 34) | modifiedCorefileBeforeMinikube121 = `.:53 {
  constant defaultCorefileAfterMinikube121 (line 42) | defaultCorefileAfterMinikube121 = `.:53 {
  constant modifiedCorefileAfterMinikube121 (line 50) | modifiedCorefileAfterMinikube121 = `.:53 {
  constant defaultCorefileUnexpected (line 59) | defaultCorefileUnexpected = `.:53 {
  constant differentIp (line 67) | differentIp = "1.2.3.456"
  function createCorefile (line 70) | func createCorefile(t *testing.T, k8s kubernetes.Interface, corefileData...
  function readCorefile (line 86) | func readCorefile(t *testing.T, k8s kubernetes.Interface) string {
  function TestPatchCorefile (line 100) | func TestPatchCorefile(t *testing.T) {
  function TestPatchCorefileUnexpected (line 163) | func TestPatchCorefileUnexpected(t *testing.T) {

FILE: src/go/cmd/metadata-server/main.go
  function detectChangesToFile (line 54) | func detectChangesToFile(filename string) <-chan struct{} {
  function main (line 88) | func main() {

FILE: src/go/cmd/metadata-server/main_test.go
  constant writeTimeout (line 24) | writeTimeout = 100 * time.Millisecond
  function TestDetectsDeletionOfFile (line 27) | func TestDetectsDeletionOfFile(t *testing.T) {
  function TestNoChangeDetectedWhenFileUnchanged (line 47) | func TestNoChangeDetectedWhenFileUnchanged(t *testing.T) {

FILE: src/go/cmd/metadata-server/metadata.go
  constant getPodByIPRetries (line 43) | getPodByIPRetries = 10
  constant getPodByIPWait (line 45) | getPodByIPWait = 500 * time.Millisecond
  type rateLimitTokenSource (line 51) | type rateLimitTokenSource struct
    method Token (line 70) | func (s *rateLimitTokenSource) Token() (*oauth2.Token, error) {
    method resetBackoff (line 85) | func (s *rateLimitTokenSource) resetBackoff() {
    method updateBackoff (line 90) | func (s *rateLimitTokenSource) updateBackoff(err error) {
  function newRateLimitTokenSource (line 60) | func newRateLimitTokenSource(ts oauth2.TokenSource) *rateLimitTokenSource {
  type ConstHandler (line 99) | type ConstHandler struct
    method ServeHTTP (line 103) | func (ch ConstHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques...
  type jwtSource (line 110) | type jwtSource interface
  type IdentityHandler (line 115) | type IdentityHandler struct
    method ServeHTTP (line 153) | func (h *IdentityHandler) ServeHTTP(w http.ResponseWriter, r *http.Req...
  function NewIdentityHandler (line 120) | func NewIdentityHandler(ctx context.Context) (*IdentityHandler, error) {
  function fromAcceptedIP (line 137) | func fromAcceptedIP(w http.ResponseWriter, r *http.Request, allowedSourc...
  type TokenHandler (line 175) | type TokenHandler struct
    method updateRobotAuth (line 227) | func (th *TokenHandler) updateRobotAuth() error {
    method updateRobotTokenSource (line 236) | func (th *TokenHandler) updateRobotTokenSource(ctx context.Context) {
    method NewMetadataHandler (line 247) | func (th *TokenHandler) NewMetadataHandler(ctx context.Context) *Metad...
    method ServeHTTP (line 281) | func (th *TokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque...
    method getPodNameByIP (line 332) | func (th *TokenHandler) getPodNameByIP(ctx context.Context, ip string)...
  type auth (line 184) | type auth interface
  type rAuth (line 190) | type rAuth struct
    method projectID (line 194) | func (a rAuth) projectID() string {
    method robotName (line 198) | func (a rAuth) robotName() string {
  type TokenResponse (line 202) | type TokenResponse struct
  function NewTokenHandler (line 208) | func NewTokenHandler(ctx context.Context, k8s *kubernetes.Clientset, saN...
  type ServiceAccountHandler (line 403) | type ServiceAccountHandler struct
    method ServeHTTP (line 412) | func (sh ServiceAccountHandler) ServeHTTP(w http.ResponseWriter, r *ht...
  type ServiceAccountResponse (line 406) | type ServiceAccountResponse struct
  type MetadataHandler (line 434) | type MetadataHandler struct
    method ServeHTTP (line 443) | func (mh MetadataHandler) ServeHTTP(w http.ResponseWriter, r *http.Req...
  function getProjectNumber (line 479) | func getProjectNumber(client *http.Client, projectId string) (int64, err...

FILE: src/go/cmd/metadata-server/metadata_test.go
  function bodyOrDie (line 33) | func bodyOrDie(r *http.Response) string {
  function TestConstHandler (line 42) | func TestConstHandler(t *testing.T) {
  type fakeJWTSource (line 56) | type fakeJWTSource struct
    method CreateJWT (line 61) | func (s *fakeJWTSource) CreateJWT(_ context.Context, d time.Duration) ...
  function TestIdentityHandlerServeHTTP (line 66) | func TestIdentityHandlerServeHTTP(t *testing.T) {
  function TestTokenHandlerServesToken (line 100) | func TestTokenHandlerServesToken(t *testing.T) {
  type fakeRobotAuth (line 123) | type fakeRobotAuth struct
    method CreateRobotTokenSource (line 129) | func (a *fakeRobotAuth) CreateRobotTokenSource(context.Context, ...str...
    method projectID (line 133) | func (a *fakeRobotAuth) projectID() string {
    method robotName (line 137) | func (a *fakeRobotAuth) robotName() string {
  function TestTokenHandlerServesLastingToken (line 141) | func TestTokenHandlerServesLastingToken(t *testing.T) {
  function TestTokenHandlerDeniesWrongAddress (line 167) | func TestTokenHandlerDeniesWrongAddress(t *testing.T) {
  function TestServiceAccountHandlerReturnsMinimalJSON (line 182) | func TestServiceAccountHandlerReturnsMinimalJSON(t *testing.T) {
  function TestMetadataHandlerReturnsZone (line 197) | func TestMetadataHandlerReturnsZone(t *testing.T) {
  type fakeTokenSource (line 221) | type fakeTokenSource struct
    method Token (line 226) | func (s *fakeTokenSource) Token() (*oauth2.Token, error) {
  function TestRateLimitTokenSource (line 234) | func TestRateLimitTokenSource(t *testing.T) {

FILE: src/go/cmd/metadata-server/nftables.go
  function addNATRule (line 30) | func addNATRule(listenIP string, listenPort int) error {
  function removeNATRule (line 145) | func removeNATRule() {

FILE: src/go/cmd/setup-dev/main.go
  constant robotPrefix (line 46) | robotPrefix = "dev-"
  function parseFlags (line 53) | func parseFlags() {
  function main (line 62) | func main() {
  function createKubeRelayEntry (line 117) | func createKubeRelayEntry(projectID string, domain string, robotName str...
  function setupDevCredentials (line 151) | func setupDevCredentials(client *http.Client, domain string, robotName s...
  function makeIdentifier (line 177) | func makeIdentifier(base string) string {
  function containerExists (line 182) | func containerExists(container string) (bool, error) {
  function stopContainerIfNeeded (line 195) | func stopContainerIfNeeded(container string) error {

FILE: src/go/cmd/setup-robot/main.go
  constant filesDir (line 75) | filesDir          = "/setup-robot-files"
  constant helmPath (line 76) | helmPath          = filesDir + "/helm"
  constant synkPath (line 77) | synkPath          = filesDir + "/synk"
  constant numDNSRetries (line 78) | numDNSRetries     = 6
  constant numServiceRetries (line 79) | numServiceRetries = 6
  constant commaSentinel (line 81) | commaSentinel = "_COMMA_SENTINEL_"
  constant baseNamespace (line 82) | baseNamespace = "default"
  function parseFlags (line 85) | func parseFlags() {
  function parseKeyValues (line 127) | func parseKeyValues(s string) (map[string]string, error) {
  function checkRobotName (line 151) | func checkRobotName(ctx context.Context, client dynamic.Interface) error {
  function main (line 172) | func main() {
  function helmValuesStringFromMap (line 322) | func helmValuesStringFromMap(varMap map[string]string) string {
  function installChartOrDie (line 331) | func installChartOrDie(ctx context.Context, cs *kubernetes.Clientset, do...
  function createOrUpdateRobot (line 398) | func createOrUpdateRobot(ctx context.Context, k8sDynamicClient dynamic.I...

FILE: src/go/cmd/setup-robot/main_test.go
  function TestParseKeyValues_ReturnsEmptyMapOnEmptyInput (line 34) | func TestParseKeyValues_ReturnsEmptyMapOnEmptyInput(t *testing.T) {
  function TestParseKeyValues_HandlesSingleEntry (line 41) | func TestParseKeyValues_HandlesSingleEntry(t *testing.T) {
  function TestParseKeyValues_HandlesMultipleEntries (line 55) | func TestParseKeyValues_HandlesMultipleEntries(t *testing.T) {
  function TestParseKeyValues_HandlesEscapedCommas (line 76) | func TestParseKeyValues_HandlesEscapedCommas(t *testing.T) {
  function TestParseKeyValues_HandlesSpaces (line 97) | func TestParseKeyValues_HandlesSpaces(t *testing.T) {
  function TestCheckRobotName_SucceedsWhenCRDNotFound (line 111) | func TestCheckRobotName_SucceedsWhenCRDNotFound(t *testing.T) {
  function TestCheckRobotName (line 137) | func TestCheckRobotName(t *testing.T) {
  function TestCreateOrUpdateRobot_Succeeds (line 200) | func TestCreateOrUpdateRobot_Succeeds(t *testing.T) {

FILE: src/go/cmd/synk/synk.go
  constant retryBackoff (line 39) | retryBackoff = 5 * time.Second
  function main (line 70) | func main() {
  function newSynk (line 90) | func newSynk() (*synk.Synk, error) {
  function runInit (line 114) | func runInit(cmd *cobra.Command, args []string) {
  function runDelete (line 127) | func runDelete(cmd *cobra.Command, args []string) {
  function runApply (line 144) | func runApply(cmd *cobra.Command, args []string) {
  function apply (line 155) | func apply(name string) error {
  function logAction (line 210) | func logAction(r *unstructured.Unstructured, action apps.ResourceAction,...

FILE: src/go/cmd/token-vendor/api/api.go
  constant httpTimeoutRead (line 27) | httpTimeoutRead    = 10 * time.Second
  constant httpTimeoutWrite (line 28) | httpTimeoutWrite   = 10 * time.Second
  constant httpTimeoutHandler (line 29) | httpTimeoutHandler = 10 * time.Second
  type constHandler (line 32) | type constHandler
    method ServeHTTP (line 34) | func (ch constHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques...
  function Register (line 42) | func Register() error {
  function SetupAndServe (line 49) | func SetupAndServe(addr string) error {
  function LoggingMiddleware (line 61) | func LoggingMiddleware(handler http.Handler) http.Handler {
  function ErrResponse (line 75) | func ErrResponse(ctx context.Context, w http.ResponseWriter, statusCode ...

FILE: src/go/cmd/token-vendor/api/v1/v1.go
  constant paramDeviceID (line 39) | paramDeviceID = "device-id"
  constant contentType (line 40) | contentType   = "content-type"
  constant pemFile (line 41) | pemFile       = "application/x-pem-file"
  type HandlerContext (line 44) | type HandlerContext struct
    method publicKeyConfigureHandler (line 75) | func (h *HandlerContext) publicKeyConfigureHandler(w http.ResponseWrit...
    method publicKeyReadHandler (line 119) | func (h *HandlerContext) publicKeyReadHandler(w http.ResponseWriter, r...
    method publicKeyPublishHandler (line 157) | func (h *HandlerContext) publicKeyPublishHandler(w http.ResponseWriter...
    method tokenOAuth2Handler (line 262) | func (h *HandlerContext) tokenOAuth2Handler(w http.ResponseWriter, r *...
    method verifyJWTHandler (line 320) | func (h *HandlerContext) verifyJWTHandler(w http.ResponseWriter, r *ht...
    method verifyTokenHandler (line 364) | func (h *HandlerContext) verifyTokenHandler(w http.ResponseWriter, r *...
  function NewHandlerContext (line 48) | func NewHandlerContext(tv *app.TokenVendor) *HandlerContext {
  function getQueryParam (line 55) | func getQueryParam(u *url.URL, param string) (string, error) {
  function isValidPublicKey (line 194) | func isValidPublicKey(pk []byte) (bool, error) {
  function testForRobotACL (line 385) | func testForRobotACL(u *url.URL) bool {
  function tokenFromRequest (line 400) | func tokenFromRequest(u *url.URL, h *http.Header) (string, error) {
  constant tokenRegex (line 433) | tokenRegex = `^ya29\.[a-zA-Z0-9\.\-_]+$`
  function isValidToken (line 440) | func isValidToken(token string) (bool, error) {
  constant jwtRegex (line 454) | jwtRegex = `^[a-zA-Z0-9\.\-_]+\.[a-zA-Z0-9\.\-_]+\.[a-zA-Z0-9\.\-_]+$`
  function isValidJWT (line 461) | func isValidJWT(jwt string) (bool, error) {
  function Register (line 474) | func Register(tv *app.TokenVendor, prefix string) error {

FILE: src/go/cmd/token-vendor/api/v1/v1_test.go
  constant testPubKey (line 28) | testPubKey    = "testdata/rsa_cert.pem"
  constant jwtBodyPrefix (line 29) | jwtBodyPrefix = "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&...
  constant saName (line 30) | saName        = "robot-service@testproject.iam.gserviceaccount.com"
  constant jwtCorrect (line 32) | jwtCorrect  = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVk...
  constant jwtWrongSig (line 33) | jwtWrongSig = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVk...
  constant jwtWrongAud (line 34) | jwtWrongAud = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhYmMiLCJp...
  constant jwtExpired (line 35) | jwtExpired  = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVk...
  type RoundTripFunc (line 38) | type RoundTripFunc
    method RoundTrip (line 40) | func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, e...
  function NewTestHTTPClient (line 44) | func NewTestHTTPClient(fn RoundTripFunc) *http.Client {
  type publicKeyConfigureHandlerK8sTest (line 48) | type publicKeyConfigureHandlerK8sTest struct
  function TestPublicKeyConfigureHandlerWithK8s (line 57) | func TestPublicKeyConfigureHandlerWithK8s(t *testing.T) {
  function runPublicKeyConfigureHandlerWithK8sCase (line 106) | func runPublicKeyConfigureHandlerWithK8sCase(t *testing.T, test *publicK...
  type publicKeyReadHandlerK8sTest (line 138) | type publicKeyReadHandlerK8sTest struct
  function TestPublicKeyReadHandlerWithK8s (line 146) | func TestPublicKeyReadHandlerWithK8s(t *testing.T) {
  function populateK8sEnv (line 203) | func populateK8sEnv(env kubernetes.Interface, ns string, maps []*corev1....
  function runPublicKeyReadHandlerWithK8sCase (line 212) | func runPublicKeyReadHandlerWithK8sCase(t *testing.T, test *publicKeyRea...
  function mustRespBodyFromFile (line 252) | func mustRespBodyFromFile(t *testing.T, file string) io.ReadCloser {
  function mustNewRequest (line 260) | func mustNewRequest(t *testing.T, method, url string, body io.Reader) *h...
  function mustFileOpen (line 268) | func mustFileOpen(t *testing.T, name string) io.Reader {
  function mustFileToString (line 276) | func mustFileToString(t *testing.T, name string) string {
  type publicKeyPublishHandlerK8sTest (line 285) | type publicKeyPublishHandlerK8sTest struct
  function TestPublicKeyPublishHandlerWithK8s (line 296) | func TestPublicKeyPublishHandlerWithK8s(t *testing.T) {
  function runPublicKeyPublishHandlerWithK8sCase (line 335) | func runPublicKeyPublishHandlerWithK8sCase(t *testing.T, test *publicKey...
  type isValidPublicKeyTest (line 406) | type isValidPublicKeyTest struct
  function TestIsValidPublicKey (line 412) | func TestIsValidPublicKey(t *testing.T) {
  type isValidTokenTest (line 430) | type isValidTokenTest struct
  function TestIsValidToken (line 436) | func TestIsValidToken(t *testing.T) {
  type isValidJWTTest (line 462) | type isValidJWTTest struct
  function TestIsValidJWT (line 468) | func TestIsValidJWT(t *testing.T) {
  type tokenFromRequestTest (line 494) | type tokenFromRequestTest struct
  function TestTokenFromRequest (line 502) | func TestTokenFromRequest(t *testing.T) {
  type VerifyTokenHandlerTest (line 567) | type VerifyTokenHandlerTest struct
  function TestVerifyTokenHandler (line 580) | func TestVerifyTokenHandler(t *testing.T) {
  function runVerifyTokenHandlerTest (line 600) | func runVerifyTokenHandlerTest(t *testing.T, test *VerifyTokenHandlerTes...
  type TokenOAuth2HandlerTest (line 661) | type TokenOAuth2HandlerTest struct
  function TestTokenOAuth2HandlerHapyPath (line 688) | func TestTokenOAuth2HandlerHapyPath(t *testing.T) {
  function TestTokenOAuth2HandlerDifferentPrivateKey (line 694) | func TestTokenOAuth2HandlerDifferentPrivateKey(t *testing.T) {
  function TestTokenOAuth2HandlerWrongAud (line 706) | func TestTokenOAuth2HandlerWrongAud(t *testing.T) {
  function TestTokenOAuth2HandlerExpired (line 717) | func TestTokenOAuth2HandlerExpired(t *testing.T) {
  function runTokenOAuth2HandlerTestWithK8s (line 729) | func runTokenOAuth2HandlerTestWithK8s(t *testing.T, test TokenOAuth2Hand...
  function Test_verifyJWTHandler (line 813) | func Test_verifyJWTHandler(t *testing.T) {

FILE: src/go/cmd/token-vendor/app/tokenvendor.go
  type TokenVendor (line 37) | type TokenVendor struct
    method PublishPublicKey (line 52) | func (tv *TokenVendor) PublishPublicKey(ctx context.Context, deviceID,...
    method ReadPublicKey (line 57) | func (tv *TokenVendor) ReadPublicKey(ctx context.Context, deviceID str...
    method ConfigurePublicKey (line 66) | func (tv *TokenVendor) ConfigurePublicKey(ctx context.Context, deviceI...
    method GetOAuth2Token (line 112) | func (tv *TokenVendor) GetOAuth2Token(ctx context.Context, jwtk string...
    method ValidateJWT (line 141) | func (tv *TokenVendor) ValidateJWT(ctx context.Context, jwtk string) (...
    method getOAuth2Token (line 184) | func (tv *TokenVendor) getOAuth2Token(ctx context.Context, jwtk string...
    method VerifyToken (line 273) | func (tv *TokenVendor) VerifyToken(ctx context.Context, token oauth.To...
  function NewTokenVendor (line 45) | func NewTokenVendor(ctx context.Context, repo repository.PubKeyRepositor...
  function validateKeyOptions (line 75) | func validateKeyOptions(opts repository.KeyOptions) error {
  function validateEmail (line 90) | func validateEmail(e string) error {
  type DeviceAuth (line 128) | type DeviceAuth struct
  function acceptedAudience (line 224) | func acceptedAudience(aud, accAud string) error {
  function contains (line 233) | func contains(s []string, str string) bool {
  function init (line 259) | func init() {
  function IsValidDeviceID (line 304) | func IsValidDeviceID(ID string) bool {

FILE: src/go/cmd/token-vendor/app/tokenvendor_test.go
  type isValidDeviceIDTest (line 19) | type isValidDeviceIDTest struct
  function TestValidateDeviceId (line 24) | func TestValidateDeviceId(t *testing.T) {
  type acceptedAudienceTest (line 44) | type acceptedAudienceTest struct
  function TestAcceptedAudience (line 51) | func TestAcceptedAudience(t *testing.T) {
  type serviceAccountNameTest (line 73) | type serviceAccountNameTest struct
  type keyOptionsTest (line 81) | type keyOptionsTest struct
  function TestKeyOptions (line 87) | func TestKeyOptions(t *testing.T) {
  function TestTokenVendor_ValidateJWT (line 127) | func TestTokenVendor_ValidateJWT(t *testing.T) {
  function getInMemoryRepo (line 263) | func getInMemoryRepo(deviceId, key string) repository.PubKeyRepository {
  function createFakeJWTWithSubject (line 270) | func createFakeJWTWithSubject(t *testing.T, deviceId, subject string) st...
  function createSAFromName (line 291) | func createSAFromName(name string) string {

FILE: src/go/cmd/token-vendor/main.go
  type scopeFlags (line 42) | type scopeFlags
    method String (line 44) | func (i *scopeFlags) String() string {
    method Set (line 48) | func (i *scopeFlags) Set(value string) error {
  type KeyStoreOpt (line 53) | type KeyStoreOpt
  constant Kubernetes (line 56) | Kubernetes = "KUBERNETES"
  constant Memory (line 57) | Memory     = "IN_MEMORY"
  function main (line 95) | func main() {

FILE: src/go/cmd/token-vendor/oauth/cache.go
  type entry (line 10) | type entry struct
  type tokenCache (line 19) | type tokenCache struct
    method add (line 50) | func (c *tokenCache) add(token Token, sa string, actAs bool) {
    method evictExpired (line 76) | func (c *tokenCache) evictExpired() {
    method evictOldest (line 88) | func (c *tokenCache) evictOldest() {
    method actAs (line 102) | func (c *tokenCache) actAs(token Token, sa string) (bool, bool) {
  function newTokenCache (line 36) | func newTokenCache(size int, expire time.Duration) (*tokenCache, error) {

FILE: src/go/cmd/token-vendor/oauth/cache_test.go
  type tokenProp (line 11) | type tokenProp struct
  function randToken (line 22) | func randToken() *tokenProp {
  function TestTokenCacheOneTokenMultipleAcl (line 31) | func TestTokenCacheOneTokenMultipleAcl(t *testing.T) {
  function TestTokenCacheExpire (line 48) | func TestTokenCacheExpire(t *testing.T) {
  function TestEvictExpired (line 62) | func TestEvictExpired(t *testing.T) {
  function TestEvictOldest (line 78) | func TestEvictOldest(t *testing.T) {
  function TestEvictionGeneral (line 98) | func TestEvictionGeneral(t *testing.T) {
  function TestParallelAccessNoEviction (line 116) | func TestParallelAccessNoEviction(t *testing.T) {
  function accessWorker (line 144) | func accessWorker(tc *tokenCache, adds int, failIfNotFound bool) error {

FILE: src/go/cmd/token-vendor/oauth/jwt/jwt.go
  type payload (line 12) | type payload struct
  function PayloadUnsafe (line 25) | func PayloadUnsafe(jwtk string) (*payload, error) {
  function VerifySignature (line 43) | func VerifySignature(jwtk string, pubKey string) error {

FILE: src/go/cmd/token-vendor/oauth/jwt/jwt_test.go
  constant testJWT (line 39) | testJWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwi...
  constant testPubKey (line 41) | testPubKey = `
  function TestVerifySignature (line 85) | func TestVerifySignature(t *testing.T) {
  function TestPayloadUnsafe (line 92) | func TestPayloadUnsafe(t *testing.T) {
  function TestPayloadUnsafeInvalidPayload (line 102) | func TestPayloadUnsafeInvalidPayload(t *testing.T) {
  function TestVerifySignatureInvalidSig (line 111) | func TestVerifySignatureInvalidSig(t *testing.T) {
  constant testJWTInvalidSigAlg (line 125) | testJWTInvalidSigAlg = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ...
  function TestVerifySignatureInvalidSigAlg (line 127) | func TestVerifySignatureInvalidSigAlg(t *testing.T) {

FILE: src/go/cmd/token-vendor/oauth/verifier.go
  type TokenVerifier (line 14) | type TokenVerifier struct
    method Verify (line 42) | func (v *TokenVerifier) Verify(ctx context.Context, token Token, sa st...
  type Token (line 21) | type Token
  constant cacheSize (line 24) | cacheSize   = 1000
  constant cacheExpire (line 25) | cacheExpire = 5 * time.Minute
  function NewTokenVerifier (line 29) | func NewTokenVerifier(ctx context.Context, c *http.Client, project strin...
  constant timeout (line 71) | timeout       = time.Second * 5
  constant retryInterval (line 72) | retryInterval = time.Second * 1
  constant retries (line 73) | retries       = 2
  function doTestIamPermissions (line 76) | func doTestIamPermissions(ctx context.Context, s *iam.Service, token, re...
  function contains (line 107) | func contains(s []string, str string) bool {

FILE: src/go/cmd/token-vendor/repository/k8s/k8s.go
  constant resyncPeriod (line 40) | resyncPeriod = 1 * time.Hour
  type K8sRepository (line 43) | type K8sRepository struct
    method ListAllDeviceIDs (line 88) | func (k *K8sRepository) ListAllDeviceIDs(ctx context.Context) ([]strin...
    method LookupKey (line 104) | func (k *K8sRepository) LookupKey(ctx context.Context, deviceID string...
    method PublishKey (line 131) | func (k *K8sRepository) PublishKey(ctx context.Context, deviceID, publ...
    method ConfigureKey (line 161) | func (k *K8sRepository) ConfigureKey(ctx context.Context, deviceID str...
  function NewK8sRepository (line 55) | func NewK8sRepository(ctx context.Context, kcl kubernetes.Interface, ns ...
  constant pubKey (line 80) | pubKey = "pubKey"
  constant serviceAccountAnnotation (line 82) | serviceAccountAnnotation = "cloudrobotics.com/gcp-service-account"
  constant serviceAccountDelegateAnnotation (line 84) | serviceAccountDelegateAnnotation = "cloudrobotics.com/gcp-service-accoun...
  function createPubKeyDeviceConfig (line 188) | func createPubKeyDeviceConfig(name, namespace, pk string) (*corev1.Confi...
  function mapSetOrDelete (line 205) | func mapSetOrDelete(m map[string]string, k, v string) {

FILE: src/go/cmd/token-vendor/repository/k8s/k8s_test.go
  function TestPublishListLookup (line 28) | func TestPublishListLookup(t *testing.T) {
  function TestPublishKeyUpdate (line 53) | func TestPublishKeyUpdate(t *testing.T) {
  function TestLookupDoesNotExist (line 77) | func TestLookupDoesNotExist(t *testing.T) {
  function TestConfigure (line 93) | func TestConfigure(t *testing.T) {
  function TestReConfigure (line 118) | func TestReConfigure(t *testing.T) {

FILE: src/go/cmd/token-vendor/repository/memory/memory.go
  type MemoryRepository (line 26) | type MemoryRepository struct
    method PublishKey (line 38) | func (m *MemoryRepository) PublishKey(ctx context.Context, deviceID, p...
    method LookupKey (line 44) | func (m *MemoryRepository) LookupKey(ctx context.Context, deviceID str...
    method ConfigureKey (line 58) | func (m *MemoryRepository) ConfigureKey(ctx context.Context, deviceID ...
  function NewMemoryRepository (line 31) | func NewMemoryRepository(ctx context.Context) (*MemoryRepository, error) {

FILE: src/go/cmd/token-vendor/repository/memory/memory_test.go
  function TestPublishAndLookup (line 26) | func TestPublishAndLookup(t *testing.T) {
  function TestLookupNotFound (line 54) | func TestLookupNotFound(t *testing.T) {
  function TestConfigure (line 69) | func TestConfigure(t *testing.T) {

FILE: src/go/cmd/token-vendor/repository/repository.go
  type Key (line 29) | type Key struct
  type KeyOptions (line 39) | type KeyOptions struct
  type PubKeyRepository (line 45) | type PubKeyRepository interface

FILE: src/go/cmd/token-vendor/tokensource/gcp.go
  type GCPTokenSource (line 19) | type GCPTokenSource struct
    method Token (line 53) | func (g *GCPTokenSource) Token(ctx context.Context, saName, saDelegate...
  type TokenResponse (line 24) | type TokenResponse struct
  constant saPrefix (line 32) | saPrefix = "projects/-/serviceAccounts/"
  function NewGCPTokenSource (line 42) | func NewGCPTokenSource(ctx context.Context, client *http.Client, scopes ...
  function tokenResponse (line 87) | func tokenResponse(r *iam.GenerateAccessTokenResponse, scopes []string, ...
  function getWorkloadServiceAccount (line 107) | func getWorkloadServiceAccount(ctx context.Context) (string, error) {

FILE: src/go/cmd/token-vendor/tokensource/gcp_test.go
  type TokenResponseTest (line 11) | type TokenResponseTest struct
  function TestTokenResponse (line 20) | func TestTokenResponse(t *testing.T) {

FILE: src/go/pkg/apis/apps/v1alpha1/register.go
  function init (line 33) | func init() {
  function Resource (line 41) | func Resource(resource string) schema.GroupResource {
  function addKnownTypes (line 46) | func addKnownTypes(scheme *runtime.Scheme) error {

FILE: src/go/pkg/apis/apps/v1alpha1/types.go
  type ResourceSet (line 27) | type ResourceSet struct
  type ResourceSetList (line 37) | type ResourceSetList struct
  type ResourceSetSpec (line 44) | type ResourceSetSpec struct
  type ResourceSetStatus (line 48) | type ResourceSetStatus struct
  type ResourceSetSpecGroup (line 56) | type ResourceSetSpecGroup struct
  type ResourceSetStatusGroup (line 63) | type ResourceSetStatusGroup struct
  type ResourceRef (line 70) | type ResourceRef struct
  type ResourceStatus (line 75) | type ResourceStatus struct
  type ResourceSetPhase (line 84) | type ResourceSetPhase
  constant ResourceSetPhasePending (line 87) | ResourceSetPhasePending ResourceSetPhase = "Pending"
  constant ResourceSetPhaseFailed (line 88) | ResourceSetPhaseFailed  ResourceSetPhase = "Failed"
  constant ResourceSetPhaseSettled (line 89) | ResourceSetPhaseSettled ResourceSetPhase = "Settled"
  type ResourceAction (line 92) | type ResourceAction
  constant ResourceActionNone (line 95) | ResourceActionNone    ResourceAction = "None"
  constant ResourceActionCreate (line 96) | ResourceActionCreate  ResourceAction = "Create"
  constant ResourceActionUpdate (line 97) | ResourceActionUpdate  ResourceAction = "Update"
  constant ResourceActionReplace (line 98) | ResourceActionReplace ResourceAction = "Replace"
  type App (line 105) | type App struct
  type AppList (line 114) | type AppList struct
  type AppSpec (line 121) | type AppSpec struct
  type AppComponents (line 127) | type AppComponents struct
  type AppComponent (line 132) | type AppComponent struct
  type AppRollout (line 141) | type AppRollout struct
  type AppRolloutList (line 151) | type AppRolloutList struct
  type AppRolloutSpec (line 158) | type AppRolloutSpec struct
  type AppRolloutSpecCloud (line 164) | type AppRolloutSpecCloud struct
  type AppRolloutSpecRobot (line 168) | type AppRolloutSpecRobot struct
  type RobotSelector (line 175) | type RobotSelector struct
  type AppRolloutStatus (line 181) | type AppRolloutStatus struct
  type AppRolloutCondition (line 190) | type AppRolloutCondition struct
  type AppRolloutConditionType (line 198) | type AppRolloutConditionType
  constant AppRolloutConditionSettled (line 201) | AppRolloutConditionSettled AppRolloutConditionType = "Settled"
  constant AppRolloutConditionReady (line 202) | AppRolloutConditionReady   AppRolloutConditionType = "Ready"
  type ChartAssignment (line 209) | type ChartAssignment struct
  type ChartAssignmentList (line 219) | type ChartAssignmentList struct
  type ChartAssignmentSpec (line 226) | type ChartAssignmentSpec struct
  type AssignedChart (line 232) | type AssignedChart struct
  type ConfigValues (line 240) | type ConfigValues
    method DeepCopy (line 244) | func (in ConfigValues) DeepCopy() ConfigValues {
  type ChartAssignmentStatus (line 248) | type ChartAssignmentStatus struct
  type ChartAssignmentPhase (line 254) | type ChartAssignmentPhase
  constant ChartAssignmentPhaseAccepted (line 259) | ChartAssignmentPhaseAccepted ChartAssignmentPhase = "Accepted"
  constant ChartAssignmentPhaseLoadingChart (line 261) | ChartAssignmentPhaseLoadingChart ChartAssignmentPhase = "LoadingChart"
  constant ChartAssignmentPhaseInstalling (line 263) | ChartAssignmentPhaseInstalling ChartAssignmentPhase = "Installing"
  constant ChartAssignmentPhaseUpdating (line 265) | ChartAssignmentPhaseUpdating ChartAssignmentPhase = "Updating"
  constant ChartAssignmentPhaseDeleting (line 268) | ChartAssignmentPhaseDeleting ChartAssignmentPhase = "Deleting"
  constant ChartAssignmentPhaseSettled (line 270) | ChartAssignmentPhaseSettled ChartAssignmentPhase = "Settled"
  constant ChartAssignmentPhaseDeleted (line 272) | ChartAssignmentPhaseDeleted ChartAssignmentPhase = "Deleted"
  constant ChartAssignmentPhaseFailed (line 276) | ChartAssignmentPhaseFailed ChartAssignmentPhase = "Failed"
  constant ChartAssignmentPhaseReady (line 279) | ChartAssignmentPhaseReady ChartAssignmentPhase = "Ready"
  type ChartAssignmentCondition (line 282) | type ChartAssignmentCondition struct
  type ChartAssignmentConditionType (line 290) | type ChartAssignmentConditionType
  constant ChartAssignmentConditionSettled (line 293) | ChartAssignmentConditionSettled ChartAssignmentConditionType = "Settled"
  constant ChartAssignmentConditionReady (line 294) | ChartAssignmentConditionReady   ChartAssignmentConditionType = "Ready"

FILE: src/go/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
  method DeepCopyInto (line 28) | func (in *App) DeepCopyInto(out *App) {
  method DeepCopy (line 37) | func (in *App) DeepCopy() *App {
  method DeepCopyObject (line 47) | func (in *App) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 55) | func (in *AppComponent) DeepCopyInto(out *AppComponent) {
  method DeepCopy (line 61) | func (in *AppComponent) DeepCopy() *AppComponent {
  method DeepCopyInto (line 71) | func (in *AppComponents) DeepCopyInto(out *AppComponents) {
  method DeepCopy (line 79) | func (in *AppComponents) DeepCopy() *AppComponents {
  method DeepCopyInto (line 89) | func (in *AppList) DeepCopyInto(out *AppList) {
  method DeepCopy (line 104) | func (in *AppList) DeepCopy() *AppList {
  method DeepCopyObject (line 114) | func (in *AppList) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 122) | func (in *AppRollout) DeepCopyInto(out *AppRollout) {
  method DeepCopy (line 132) | func (in *AppRollout) DeepCopy() *AppRollout {
  method DeepCopyObject (line 142) | func (in *AppRollout) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 150) | func (in *AppRolloutCondition) DeepCopyInto(out *AppRolloutCondition) {
  method DeepCopy (line 158) | func (in *AppRolloutCondition) DeepCopy() *AppRolloutCondition {
  method DeepCopyInto (line 168) | func (in *AppRolloutList) DeepCopyInto(out *AppRolloutList) {
  method DeepCopy (line 183) | func (in *AppRolloutList) DeepCopy() *AppRolloutList {
  method DeepCopyObject (line 193) | func (in *AppRolloutList) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 201) | func (in *AppRolloutSpec) DeepCopyInto(out *AppRolloutSpec) {
  method DeepCopy (line 215) | func (in *AppRolloutSpec) DeepCopy() *AppRolloutSpec {
  method DeepCopyInto (line 225) | func (in *AppRolloutSpecCloud) DeepCopyInto(out *AppRolloutSpecCloud) {
  method DeepCopy (line 232) | func (in *AppRolloutSpecCloud) DeepCopy() *AppRolloutSpecCloud {
  method DeepCopyInto (line 242) | func (in *AppRolloutSpecRobot) DeepCopyInto(out *AppRolloutSpecRobot) {
  method DeepCopy (line 254) | func (in *AppRolloutSpecRobot) DeepCopy() *AppRolloutSpecRobot {
  method DeepCopyInto (line 264) | func (in *AppRolloutStatus) DeepCopyInto(out *AppRolloutStatus) {
  method DeepCopy (line 277) | func (in *AppRolloutStatus) DeepCopy() *AppRolloutStatus {
  method DeepCopyInto (line 287) | func (in *AppSpec) DeepCopyInto(out *AppSpec) {
  method DeepCopy (line 294) | func (in *AppSpec) DeepCopy() *AppSpec {
  method DeepCopyInto (line 304) | func (in *AssignedChart) DeepCopyInto(out *AssignedChart) {
  method DeepCopy (line 311) | func (in *AssignedChart) DeepCopy() *AssignedChart {
  method DeepCopyInto (line 321) | func (in *ChartAssignment) DeepCopyInto(out *ChartAssignment) {
  method DeepCopy (line 331) | func (in *ChartAssignment) DeepCopy() *ChartAssignment {
  method DeepCopyObject (line 341) | func (in *ChartAssignment) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 349) | func (in *ChartAssignmentCondition) DeepCopyInto(out *ChartAssignmentCon...
  method DeepCopy (line 357) | func (in *ChartAssignmentCondition) DeepCopy() *ChartAssignmentCondition {
  method DeepCopyInto (line 367) | func (in *ChartAssignmentList) DeepCopyInto(out *ChartAssignmentList) {
  method DeepCopy (line 382) | func (in *ChartAssignmentList) DeepCopy() *ChartAssignmentList {
  method DeepCopyObject (line 392) | func (in *ChartAssignmentList) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 400) | func (in *ChartAssignmentSpec) DeepCopyInto(out *ChartAssignmentSpec) {
  method DeepCopy (line 407) | func (in *ChartAssignmentSpec) DeepCopy() *ChartAssignmentSpec {
  method DeepCopyInto (line 417) | func (in *ChartAssignmentStatus) DeepCopyInto(out *ChartAssignmentStatus) {
  method DeepCopy (line 430) | func (in *ChartAssignmentStatus) DeepCopy() *ChartAssignmentStatus {
  method DeepCopyInto (line 440) | func (in ConfigValues) DeepCopyInto(out *ConfigValues) {
  method DeepCopyInto (line 449) | func (in *ResourceRef) DeepCopyInto(out *ResourceRef) {
  method DeepCopy (line 455) | func (in *ResourceRef) DeepCopy() *ResourceRef {
  method DeepCopyInto (line 465) | func (in *ResourceSet) DeepCopyInto(out *ResourceSet) {
  method DeepCopy (line 475) | func (in *ResourceSet) DeepCopy() *ResourceSet {
  method DeepCopyObject (line 485) | func (in *ResourceSet) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 493) | func (in *ResourceSetList) DeepCopyInto(out *ResourceSetList) {
  method DeepCopy (line 508) | func (in *ResourceSetList) DeepCopy() *ResourceSetList {
  method DeepCopyObject (line 518) | func (in *ResourceSetList) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 526) | func (in *ResourceSetSpec) DeepCopyInto(out *ResourceSetSpec) {
  method DeepCopy (line 539) | func (in *ResourceSetSpec) DeepCopy() *ResourceSetSpec {
  method DeepCopyInto (line 549) | func (in *ResourceSetSpecGroup) DeepCopyInto(out *ResourceSetSpecGroup) {
  method DeepCopy (line 560) | func (in *ResourceSetSpecGroup) DeepCopy() *ResourceSetSpecGroup {
  method DeepCopyInto (line 570) | func (in *ResourceSetStatus) DeepCopyInto(out *ResourceSetStatus) {
  method DeepCopy (line 592) | func (in *ResourceSetStatus) DeepCopy() *ResourceSetStatus {
  method DeepCopyInto (line 602) | func (in *ResourceSetStatusGroup) DeepCopyInto(out *ResourceSetStatusGro...
  method DeepCopy (line 613) | func (in *ResourceSetStatusGroup) DeepCopy() *ResourceSetStatusGroup {
  method DeepCopyInto (line 623) | func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) {
  method DeepCopy (line 629) | func (in *ResourceStatus) DeepCopy() *ResourceStatus {
  method DeepCopyInto (line 639) | func (in *RobotSelector) DeepCopyInto(out *RobotSelector) {
  method DeepCopy (line 655) | func (in *RobotSelector) DeepCopy() *RobotSelector {

FILE: src/go/pkg/apis/registry/v1alpha1/register.go
  function init (line 31) | func init() {
  function Resource (line 36) | func Resource(resource string) schema.GroupResource {
  function addKnownTypes (line 41) | func addKnownTypes(scheme *runtime.Scheme) error {

FILE: src/go/pkg/apis/registry/v1alpha1/types.go
  type Robot (line 24) | type Robot struct
  type RobotList (line 34) | type RobotList struct
  type RobotSpec (line 41) | type RobotSpec struct
  type RobotStatus (line 46) | type RobotStatus struct
  type RobotStatusCloud (line 52) | type RobotStatusCloud struct
  type RobotStatusRobot (line 55) | type RobotStatusRobot struct
  type RobotConfiguration (line 65) | type RobotConfiguration struct
  type RobotState (line 69) | type RobotState
  constant RobotStateUndefined (line 72) | RobotStateUndefined     RobotState = "UNDEFINED"
  constant RobotStateUnavailable (line 73) | RobotStateUnavailable   RobotState = "UNAVAILABLE"
  constant RobotStateAvailable (line 74) | RobotStateAvailable     RobotState = "AVAILABLE"
  constant RobotStateEmergencyStop (line 75) | RobotStateEmergencyStop RobotState = "EMERGENCY_STOP"
  constant RobotStateError (line 76) | RobotStateError         RobotState = "ERROR"

FILE: src/go/pkg/apis/registry/v1alpha1/zz_generated.deepcopy.go
  method DeepCopyInto (line 27) | func (in *Robot) DeepCopyInto(out *Robot) {
  method DeepCopy (line 37) | func (in *Robot) DeepCopy() *Robot {
  method DeepCopyObject (line 47) | func (in *Robot) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 55) | func (in *RobotConfiguration) DeepCopyInto(out *RobotConfiguration) {
  method DeepCopy (line 61) | func (in *RobotConfiguration) DeepCopy() *RobotConfiguration {
  method DeepCopyInto (line 71) | func (in *RobotList) DeepCopyInto(out *RobotList) {
  method DeepCopy (line 86) | func (in *RobotList) DeepCopy() *RobotList {
  method DeepCopyObject (line 96) | func (in *RobotList) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 104) | func (in *RobotSpec) DeepCopyInto(out *RobotSpec) {
  method DeepCopy (line 110) | func (in *RobotSpec) DeepCopy() *RobotSpec {
  method DeepCopyInto (line 120) | func (in *RobotStatus) DeepCopyInto(out *RobotStatus) {
  method DeepCopy (line 129) | func (in *RobotStatus) DeepCopy() *RobotStatus {
  method DeepCopyInto (line 139) | func (in *RobotStatusCloud) DeepCopyInto(out *RobotStatusCloud) {
  method DeepCopy (line 145) | func (in *RobotStatusCloud) DeepCopy() *RobotStatusCloud {
  method DeepCopyInto (line 155) | func (in *RobotStatusRobot) DeepCopyInto(out *RobotStatusRobot) {
  method DeepCopy (line 170) | func (in *RobotStatusRobot) DeepCopy() *RobotStatusRobot {

FILE: src/go/pkg/client/informers/apps/interface.go
  type Interface (line 25) | type Interface interface
  type group (line 30) | type group struct
    method V1alpha1 (line 42) | func (g *group) V1alpha1() v1alpha1.Interface {
  function New (line 37) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...

FILE: src/go/pkg/client/informers/apps/v1alpha1/app.go
  type AppInformer (line 35) | type AppInformer interface
  type appInformer (line 40) | type appInformer struct
    method defaultInformer (line 77) | func (f *appInformer) defaultInformer(client versioned.Interface, resy...
    method Informer (line 81) | func (f *appInformer) Informer() cache.SharedIndexInformer {
    method Lister (line 85) | func (f *appInformer) Lister() v1alpha1.AppLister {
  function NewAppInformer (line 48) | func NewAppInformer(client versioned.Interface, resyncPeriod time.Durati...
  function NewFilteredAppInformer (line 55) | func NewFilteredAppInformer(client versioned.Interface, resyncPeriod tim...

FILE: src/go/pkg/client/informers/apps/v1alpha1/approllout.go
  type AppRolloutInformer (line 35) | type AppRolloutInformer interface
  type appRolloutInformer (line 40) | type appRolloutInformer struct
    method defaultInformer (line 77) | func (f *appRolloutInformer) defaultInformer(client versioned.Interfac...
    method Informer (line 81) | func (f *appRolloutInformer) Informer() cache.SharedIndexInformer {
    method Lister (line 85) | func (f *appRolloutInformer) Lister() v1alpha1.AppRolloutLister {
  function NewAppRolloutInformer (line 48) | func NewAppRolloutInformer(client versioned.Interface, resyncPeriod time...
  function NewFilteredAppRolloutInformer (line 55) | func NewFilteredAppRolloutInformer(client versioned.Interface, resyncPer...

FILE: src/go/pkg/client/informers/apps/v1alpha1/chartassignment.go
  type ChartAssignmentInformer (line 35) | type ChartAssignmentInformer interface
  type chartAssignmentInformer (line 40) | type chartAssignmentInformer struct
    method defaultInformer (line 77) | func (f *chartAssignmentInformer) defaultInformer(client versioned.Int...
    method Informer (line 81) | func (f *chartAssignmentInformer) Informer() cache.SharedIndexInformer {
    method Lister (line 85) | func (f *chartAssignmentInformer) Lister() v1alpha1.ChartAssignmentLis...
  function NewChartAssignmentInformer (line 48) | func NewChartAssignmentInformer(client versioned.Interface, resyncPeriod...
  function NewFilteredChartAssignmentInformer (line 55) | func NewFilteredChartAssignmentInformer(client versioned.Interface, resy...

FILE: src/go/pkg/client/informers/apps/v1alpha1/interface.go
  type Interface (line 24) | type Interface interface
  type version (line 35) | type version struct
    method Apps (line 47) | func (v *version) Apps() AppInformer {
    method AppRollouts (line 52) | func (v *version) AppRollouts() AppRolloutInformer {
    method ChartAssignments (line 57) | func (v *version) ChartAssignments() ChartAssignmentInformer {
    method ResourceSets (line 62) | func (v *version) ResourceSets() ResourceSetInformer {
  function New (line 42) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...

FILE: src/go/pkg/client/informers/apps/v1alpha1/resourceset.go
  type ResourceSetInformer (line 35) | type ResourceSetInformer interface
  type resourceSetInformer (line 40) | type resourceSetInformer struct
    method defaultInformer (line 77) | func (f *resourceSetInformer) defaultInformer(client versioned.Interfa...
    method Informer (line 81) | func (f *resourceSetInformer) Informer() cache.SharedIndexInformer {
    method Lister (line 85) | func (f *resourceSetInformer) Lister() v1alpha1.ResourceSetLister {
  function NewResourceSetInformer (line 48) | func NewResourceSetInformer(client versioned.Interface, resyncPeriod tim...
  function NewFilteredResourceSetInformer (line 55) | func NewFilteredResourceSetInformer(client versioned.Interface, resyncPe...

FILE: src/go/pkg/client/informers/factory.go
  type SharedInformerOption (line 35) | type SharedInformerOption
  type sharedInformerFactory (line 37) | type sharedInformerFactory struct
    method Start (line 110) | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
    method WaitForCacheSync (line 123) | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{...
    method InformerFor (line 146) | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFun...
    method Apps (line 178) | func (f *sharedInformerFactory) Apps() apps.Interface {
    method Registry (line 182) | func (f *sharedInformerFactory) Registry() registry.Interface {
  function WithCustomResyncConfig (line 52) | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) Sh...
  function WithTweakListOptions (line 62) | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListO...
  function WithNamespace (line 70) | func WithNamespace(namespace string) SharedInformerOption {
  function NewSharedInformerFactory (line 78) | func NewSharedInformerFactory(client versioned.Interface, defaultResync ...
  function NewFilteredSharedInformerFactory (line 86) | func NewFilteredSharedInformerFactory(client versioned.Interface, defaul...
  function NewSharedInformerFactoryWithOptions (line 91) | func NewSharedInformerFactoryWithOptions(client versioned.Interface, def...
  type SharedInformerFactory (line 169) | type SharedInformerFactory interface

FILE: src/go/pkg/client/informers/generic.go
  type GenericInformer (line 30) | type GenericInformer interface
  type genericInformer (line 35) | type genericInformer struct
    method Informer (line 41) | func (f *genericInformer) Informer() cache.SharedIndexInformer {
    method Lister (line 46) | func (f *genericInformer) Lister() cache.GenericLister {
  method ForResource (line 52) | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersion...

FILE: src/go/pkg/client/informers/internalinterfaces/factory_interfaces.go
  type NewInformerFunc (line 29) | type NewInformerFunc
  type SharedInformerFactory (line 32) | type SharedInformerFactory interface
  type TweakListOptionsFunc (line 38) | type TweakListOptionsFunc

FILE: src/go/pkg/client/informers/registry/interface.go
  type Interface (line 25) | type Interface interface
  type group (line 30) | type group struct
    method V1alpha1 (line 42) | func (g *group) V1alpha1() v1alpha1.Interface {
  function New (line 37) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...

FILE: src/go/pkg/client/informers/registry/v1alpha1/interface.go
  type Interface (line 24) | type Interface interface
  type version (line 29) | type version struct
    method Robots (line 41) | func (v *version) Robots() RobotInformer {
  function New (line 36) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...

FILE: src/go/pkg/client/informers/registry/v1alpha1/robot.go
  type RobotInformer (line 35) | type RobotInformer interface
  type robotInformer (line 40) | type robotInformer struct
    method defaultInformer (line 78) | func (f *robotInformer) defaultInformer(client versioned.Interface, re...
    method Informer (line 82) | func (f *robotInformer) Informer() cache.SharedIndexInformer {
    method Lister (line 86) | func (f *robotInformer) Lister() v1alpha1.RobotLister {
  function NewRobotInformer (line 49) | func NewRobotInformer(client versioned.Interface, namespace string, resy...
  function NewFilteredRobotInformer (line 56) | func NewFilteredRobotInformer(client versioned.Interface, namespace stri...

FILE: src/go/pkg/client/listers/apps/v1alpha1/app.go
  type AppLister (line 28) | type AppLister interface
  type appLister (line 39) | type appLister struct
    method List (line 49) | func (s *appLister) List(selector labels.Selector) (ret []*v1alpha1.Ap...
    method Get (line 57) | func (s *appLister) Get(name string) (*v1alpha1.App, error) {
  function NewAppLister (line 44) | func NewAppLister(indexer cache.Indexer) AppLister {

FILE: src/go/pkg/client/listers/apps/v1alpha1/approllout.go
  type AppRolloutLister (line 28) | type AppRolloutLister interface
  type appRolloutLister (line 39) | type appRolloutLister struct
    method List (line 49) | func (s *appRolloutLister) List(selector labels.Selector) (ret []*v1al...
    method Get (line 57) | func (s *appRolloutLister) Get(name string) (*v1alpha1.AppRollout, err...
  function NewAppRolloutLister (line 44) | func NewAppRolloutLister(indexer cache.Indexer) AppRolloutLister {

FILE: src/go/pkg/client/listers/apps/v1alpha1/chartassignment.go
  type ChartAssignmentLister (line 28) | type ChartAssignmentLister interface
  type chartAssignmentLister (line 39) | type chartAssignmentLister struct
    method List (line 49) | func (s *chartAssignmentLister) List(selector labels.Selector) (ret []...
    method Get (line 57) | func (s *chartAssignmentLister) Get(name string) (*v1alpha1.ChartAssig...
  function NewChartAssignmentLister (line 44) | func NewChartAssignmentLister(indexer cache.Indexer) ChartAssignmentList...

FILE: src/go/pkg/client/listers/apps/v1alpha1/expansion_generated.go
  type AppListerExpansion (line 21) | type AppListerExpansion interface
  type AppRolloutListerExpansion (line 25) | type AppRolloutListerExpansion interface
  type ChartAssignmentListerExpansion (line 29) | type ChartAssignmentListerExpansion interface
  type ResourceSetListerExpansion (line 33) | type ResourceSetListerExpansion interface

FILE: src/go/pkg/client/listers/apps/v1alpha1/resourceset.go
  type ResourceSetLister (line 28) | type ResourceSetLister interface
  type resourceSetLister (line 39) | type resourceSetLister struct
    method List (line 49) | func (s *resourceSetLister) List(selector labels.Selector) (ret []*v1a...
    method Get (line 57) | func (s *resourceSetLister) Get(name string) (*v1alpha1.ResourceSet, e...
  function NewResourceSetLister (line 44) | func NewResourceSetLister(indexer cache.Indexer) ResourceSetLister {

FILE: src/go/pkg/client/listers/registry/v1alpha1/expansion_generated.go
  type RobotListerExpansion (line 21) | type RobotListerExpansion interface
  type RobotNamespaceListerExpansion (line 25) | type RobotNamespaceListerExpansion interface

FILE: src/go/pkg/client/listers/registry/v1alpha1/robot.go
  type RobotLister (line 28) | type RobotLister interface
  type robotLister (line 38) | type robotLister struct
    method List (line 48) | func (s *robotLister) List(selector labels.Selector) (ret []*v1alpha1....
    method Robots (line 56) | func (s *robotLister) Robots(namespace string) RobotNamespaceLister {
  function NewRobotLister (line 43) | func NewRobotLister(indexer cache.Indexer) RobotLister {
  type RobotNamespaceLister (line 62) | type RobotNamespaceLister interface
  type robotNamespaceLister (line 74) | type robotNamespaceLister struct
    method List (line 80) | func (s robotNamespaceLister) List(selector labels.Selector) (ret []*v...
    method Get (line 88) | func (s robotNamespaceLister) Get(name string) (*v1alpha1.Robot, error) {

FILE: src/go/pkg/client/versioned/clientset.go
  type Interface (line 29) | type Interface interface
  type Clientset (line 37) | type Clientset struct
    method AppsV1alpha1 (line 44) | func (c *Clientset) AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface {
    method RegistryV1alpha1 (line 49) | func (c *Clientset) RegistryV1alpha1() registryv1alpha1.RegistryV1alph...
    method Discovery (line 54) | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
  function NewForConfig (line 64) | func NewForConfig(c *rest.Config) (*Clientset, error) {
  function NewForConfigOrDie (line 92) | func NewForConfigOrDie(c *rest.Config) *Clientset {
  function New (line 102) | func New(c rest.Interface) *Clientset {

FILE: src/go/pkg/client/versioned/fake/clientset_generated.go
  function NewSimpleClientset (line 36) | func NewSimpleClientset(objects ...runtime.Object) *Clientset {
  type Clientset (line 63) | type Clientset struct
    method Discovery (line 69) | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
    method Tracker (line 73) | func (c *Clientset) Tracker() testing.ObjectTracker {
    method AppsV1alpha1 (line 83) | func (c *Clientset) AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface {
    method RegistryV1alpha1 (line 88) | func (c *Clientset) RegistryV1alpha1() registryv1alpha1.RegistryV1alph...

FILE: src/go/pkg/client/versioned/fake/register.go
  function init (line 53) | func init() {

FILE: src/go/pkg/client/versioned/scheme/register.go
  function init (line 53) | func init() {

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/app.go
  type AppsGetter (line 33) | type AppsGetter interface
  type AppInterface (line 38) | type AppInterface interface
  type apps (line 51) | type apps struct
    method Get (line 63) | func (c *apps) Get(ctx context.Context, name string, options v1.GetOpt...
    method List (line 75) | func (c *apps) List(ctx context.Context, opts v1.ListOptions) (result ...
    method Watch (line 91) | func (c *apps) Watch(ctx context.Context, opts v1.ListOptions) (watch....
    method Create (line 105) | func (c *apps) Create(ctx context.Context, app *v1alpha1.App, opts v1....
    method Update (line 117) | func (c *apps) Update(ctx context.Context, app *v1alpha1.App, opts v1....
    method Delete (line 130) | func (c *apps) Delete(ctx context.Context, name string, opts v1.Delete...
    method DeleteCollection (line 140) | func (c *apps) DeleteCollection(ctx context.Context, opts v1.DeleteOpt...
    method Patch (line 155) | func (c *apps) Patch(ctx context.Context, name string, pt types.PatchT...
  function newApps (line 56) | func newApps(c *AppsV1alpha1Client) *apps {

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/approllout.go
  type AppRolloutsGetter (line 33) | type AppRolloutsGetter interface
  type AppRolloutInterface (line 38) | type AppRolloutInterface interface
  type appRollouts (line 52) | type appRollouts struct
    method Get (line 64) | func (c *appRollouts) Get(ctx context.Context, name string, options v1...
    method List (line 76) | func (c *appRollouts) List(ctx context.Context, opts v1.ListOptions) (...
    method Watch (line 92) | func (c *appRollouts) Watch(ctx context.Context, opts v1.ListOptions) ...
    method Create (line 106) | func (c *appRollouts) Create(ctx context.Context, appRollout *v1alpha1...
    method Update (line 118) | func (c *appRollouts) Update(ctx context.Context, appRollout *v1alpha1...
    method UpdateStatus (line 132) | func (c *appRollouts) UpdateStatus(ctx context.Context, appRollout *v1...
    method Delete (line 146) | func (c *appRollouts) Delete(ctx context.Context, name string, opts v1...
    method DeleteCollection (line 156) | func (c *appRollouts) DeleteCollection(ctx context.Context, opts v1.De...
    method Patch (line 171) | func (c *appRollouts) Patch(ctx context.Context, name string, pt types...
  function newAppRollouts (line 57) | func newAppRollouts(c *AppsV1alpha1Client) *appRollouts {

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/apps_client.go
  type AppsV1alpha1Interface (line 25) | type AppsV1alpha1Interface interface
  type AppsV1alpha1Client (line 34) | type AppsV1alpha1Client struct
    method Apps (line 38) | func (c *AppsV1alpha1Client) Apps() AppInterface {
    method AppRollouts (line 42) | func (c *AppsV1alpha1Client) AppRollouts() AppRolloutInterface {
    method ChartAssignments (line 46) | func (c *AppsV1alpha1Client) ChartAssignments() ChartAssignmentInterfa...
    method ResourceSets (line 50) | func (c *AppsV1alpha1Client) ResourceSets() ResourceSetInterface {
    method RESTClient (line 97) | func (c *AppsV1alpha1Client) RESTClient() rest.Interface {
  function NewForConfig (line 55) | func NewForConfig(c *rest.Config) (*AppsV1alpha1Client, error) {
  function NewForConfigOrDie (line 69) | func NewForConfigOrDie(c *rest.Config) *AppsV1alpha1Client {
  function New (line 78) | func New(c rest.Interface) *AppsV1alpha1Client {
  function setConfigDefaults (line 82) | func setConfigDefaults(config *rest.Config) error {

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/chartassignment.go
  type ChartAssignmentsGetter (line 33) | type ChartAssignmentsGetter interface
  type ChartAssignmentInterface (line 38) | type ChartAssignmentInterface interface
  type chartAssignments (line 52) | type chartAssignments struct
    method Get (line 64) | func (c *chartAssignments) Get(ctx context.Context, name string, optio...
    method List (line 76) | func (c *chartAssignments) List(ctx context.Context, opts v1.ListOptio...
    method Watch (line 92) | func (c *chartAssignments) Watch(ctx context.Context, opts v1.ListOpti...
    method Create (line 106) | func (c *chartAssignments) Create(ctx context.Context, chartAssignment...
    method Update (line 118) | func (c *chartAssignments) Update(ctx context.Context, chartAssignment...
    method UpdateStatus (line 132) | func (c *chartAssignments) UpdateStatus(ctx context.Context, chartAssi...
    method Delete (line 146) | func (c *chartAssignments) Delete(ctx context.Context, name string, op...
    method DeleteCollection (line 156) | func (c *chartAssignments) DeleteCollection(ctx context.Context, opts ...
    method Patch (line 171) | func (c *chartAssignments) Patch(ctx context.Context, name string, pt ...
  function newChartAssignments (line 57) | func newChartAssignments(c *AppsV1alpha1Client) *chartAssignments {

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_app.go
  type FakeApps (line 32) | type FakeApps struct
    method Get (line 41) | func (c *FakeApps) Get(ctx context.Context, name string, options v1.Ge...
    method List (line 51) | func (c *FakeApps) List(ctx context.Context, opts v1.ListOptions) (res...
    method Watch (line 72) | func (c *FakeApps) Watch(ctx context.Context, opts v1.ListOptions) (wa...
    method Create (line 78) | func (c *FakeApps) Create(ctx context.Context, app *v1alpha1.App, opts...
    method Update (line 88) | func (c *FakeApps) Update(ctx context.Context, app *v1alpha1.App, opts...
    method Delete (line 98) | func (c *FakeApps) Delete(ctx context.Context, name string, opts v1.De...
    method DeleteCollection (line 105) | func (c *FakeApps) DeleteCollection(ctx context.Context, opts v1.Delet...
    method Patch (line 113) | func (c *FakeApps) Patch(ctx context.Context, name string, pt types.Pa...

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_approllout.go
  type FakeAppRollouts (line 32) | type FakeAppRollouts struct
    method Get (line 41) | func (c *FakeAppRollouts) Get(ctx context.Context, name string, option...
    method List (line 51) | func (c *FakeAppRollouts) List(ctx context.Context, opts v1.ListOption...
    method Watch (line 72) | func (c *FakeAppRollouts) Watch(ctx context.Context, opts v1.ListOptio...
    method Create (line 78) | func (c *FakeAppRollouts) Create(ctx context.Context, appRollout *v1al...
    method Update (line 88) | func (c *FakeAppRollouts) Update(ctx context.Context, appRollout *v1al...
    method UpdateStatus (line 99) | func (c *FakeAppRollouts) UpdateStatus(ctx context.Context, appRollout...
    method Delete (line 109) | func (c *FakeAppRollouts) Delete(ctx context.Context, name string, opt...
    method DeleteCollection (line 116) | func (c *FakeAppRollouts) DeleteCollection(ctx context.Context, opts v...
    method Patch (line 124) | func (c *FakeAppRollouts) Patch(ctx context.Context, name string, pt t...

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_apps_client.go
  type FakeAppsV1alpha1 (line 25) | type FakeAppsV1alpha1 struct
    method Apps (line 29) | func (c *FakeAppsV1alpha1) Apps() v1alpha1.AppInterface {
    method AppRollouts (line 33) | func (c *FakeAppsV1alpha1) AppRollouts() v1alpha1.AppRolloutInterface {
    method ChartAssignments (line 37) | func (c *FakeAppsV1alpha1) ChartAssignments() v1alpha1.ChartAssignment...
    method ResourceSets (line 41) | func (c *FakeAppsV1alpha1) ResourceSets() v1alpha1.ResourceSetInterface {
    method RESTClient (line 47) | func (c *FakeAppsV1alpha1) RESTClient() rest.Interface {

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_chartassignment.go
  type FakeChartAssignments (line 32) | type FakeChartAssignments struct
    method Get (line 41) | func (c *FakeChartAssignments) Get(ctx context.Context, name string, o...
    method List (line 51) | func (c *FakeChartAssignments) List(ctx context.Context, opts v1.ListO...
    method Watch (line 72) | func (c *FakeChartAssignments) Watch(ctx context.Context, opts v1.List...
    method Create (line 78) | func (c *FakeChartAssignments) Create(ctx context.Context, chartAssign...
    method Update (line 88) | func (c *FakeChartAssignments) Update(ctx context.Context, chartAssign...
    method UpdateStatus (line 99) | func (c *FakeChartAssignments) UpdateStatus(ctx context.Context, chart...
    method Delete (line 109) | func (c *FakeChartAssignments) Delete(ctx context.Context, name string...
    method DeleteCollection (line 116) | func (c *FakeChartAssignments) DeleteCollection(ctx context.Context, o...
    method Patch (line 124) | func (c *FakeChartAssignments) Patch(ctx context.Context, name string,...

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_resourceset.go
  type FakeResourceSets (line 32) | type FakeResourceSets struct
    method Get (line 41) | func (c *FakeResourceSets) Get(ctx context.Context, name string, optio...
    method List (line 51) | func (c *FakeResourceSets) List(ctx context.Context, opts v1.ListOptio...
    method Watch (line 72) | func (c *FakeResourceSets) Watch(ctx context.Context, opts v1.ListOpti...
    method Create (line 78) | func (c *FakeResourceSets) Create(ctx context.Context, resourceSet *v1...
    method Update (line 88) | func (c *FakeResourceSets) Update(ctx context.Context, resourceSet *v1...
    method UpdateStatus (line 99) | func (c *FakeResourceSets) UpdateStatus(ctx context.Context, resourceS...
    method Delete (line 109) | func (c *FakeResourceSets) Delete(ctx context.Context, name string, op...
    method DeleteCollection (line 116) | func (c *FakeResourceSets) DeleteCollection(ctx context.Context, opts ...
    method Patch (line 124) | func (c *FakeResourceSets) Patch(ctx context.Context, name string, pt ...

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/generated_expansion.go
  type AppExpansion (line 19) | type AppExpansion interface
  type AppRolloutExpansion (line 21) | type AppRolloutExpansion interface
  type ChartAssignmentExpansion (line 23) | type ChartAssignmentExpansion interface
  type ResourceSetExpansion (line 25) | type ResourceSetExpansion interface

FILE: src/go/pkg/client/versioned/typed/apps/v1alpha1/resourceset.go
  type ResourceSetsGetter (line 33) | type ResourceSetsGetter interface
  type ResourceSetInterface (line 38) | type ResourceSetInterface interface
  type resourceSets (line 52) | type resourceSets struct
    method Get (line 64) | func (c *resourceSets) Get(ctx context.Context, name string, options v...
    method List (line 76) | func (c *resourceSets) List(ctx context.Context, opts v1.ListOptions) ...
    method Watch (line 92) | func (c *resourceSets) Watch(ctx context.Context, opts v1.ListOptions)...
    method Create (line 106) | func (c *resourceSets) Create(ctx context.Context, resourceSet *v1alph...
    method Update (line 118) | func (c *resourceSets) Update(ctx context.Context, resourceSet *v1alph...
    method UpdateStatus (line 132) | func (c *resourceSets) UpdateStatus(ctx context.Context, resourceSet *...
    method Delete (line 146) | func (c *resourceSets) Delete(ctx context.Context, name string, opts v...
    method DeleteCollection (line 156) | func (c *resourceSets) DeleteCollection(ctx context.Context, opts v1.D...
    method Patch (line 171) | func (c *resourceSets) Patch(ctx context.Context, name string, pt type...
  function newResourceSets (line 57) | func newResourceSets(c *AppsV1alpha1Client) *resourceSets {

FILE: src/go/pkg/client/versioned/typed/registry/v1alpha1/fake/fake_registry_client.go
  type FakeRegistryV1alpha1 (line 25) | type FakeRegistryV1alpha1 struct
    method Robots (line 29) | func (c *FakeRegistryV1alpha1) Robots(namespace string) v1alpha1.Robot...
    method RESTClient (line 35) | func (c *FakeRegistryV1alpha1) RESTClient() rest.Interface {

FILE: src/go/pkg/client/versioned/typed/registry/v1alpha1/fake/fake_robot.go
  type FakeRobots (line 32) | type FakeRobots struct
    method Get (line 42) | func (c *FakeRobots) Get(ctx context.Context, name string, options v1....
    method List (line 53) | func (c *FakeRobots) List(ctx context.Context, opts v1.ListOptions) (r...
    method Watch (line 75) | func (c *FakeRobots) Watch(ctx context.Context, opts v1.ListOptions) (...
    method Create (line 82) | func (c *FakeRobots) Create(ctx context.Context, robot *v1alpha1.Robot...
    method Update (line 93) | func (c *FakeRobots) Update(ctx context.Context, robot *v1alpha1.Robot...
    method UpdateStatus (line 105) | func (c *FakeRobots) UpdateStatus(ctx context.Context, robot *v1alpha1...
    method Delete (line 116) | func (c *FakeRobots) Delete(ctx context.Context, name string, opts v1....
    method DeleteCollection (line 124) | func (c *FakeRobots) DeleteCollection(ctx context.Context, opts v1.Del...
    method Patch (line 132) | func (c *FakeRobots) Patch(ctx context.Context, name string, pt types....

FILE: src/go/pkg/client/versioned/typed/registry/v1alpha1/generated_expansion.go
  type RobotExpansion (line 19) | type RobotExpansion interface

FILE: src/go/pkg/client/versioned/typed/registry/v1alpha1/registry_client.go
  type RegistryV1alpha1Interface (line 25) | type RegistryV1alpha1Interface interface
  type RegistryV1alpha1Client (line 31) | type RegistryV1alpha1Client struct
    method Robots (line 35) | func (c *RegistryV1alpha1Client) Robots(namespace string) RobotInterfa...
    method RESTClient (line 82) | func (c *RegistryV1alpha1Client) RESTClient() rest.Interface {
  function NewForConfig (line 40) | func NewForConfig(c *rest.Config) (*RegistryV1alpha1Client, error) {
  function NewForConfigOrDie (line 54) | func NewForConfigOrDie(c *rest.Config) *RegistryV1alpha1Client {
  function New (line 63) | func New(c rest.Interface) *RegistryV1alpha1Client {
  function setConfigDefaults (line 67) | func setConfigDefaults(config *rest.Config) error {

FILE: src/go/pkg/client/versioned/typed/registry/v1alpha1/robot.go
  type RobotsGetter (line 33) | type RobotsGetter interface
  type RobotInterface (line 38) | type RobotInterface interface
  type robots (line 52) | type robots struct
    method Get (line 66) | func (c *robots) Get(ctx context.Context, name string, options v1.GetO...
    method List (line 79) | func (c *robots) List(ctx context.Context, opts v1.ListOptions) (resul...
    method Watch (line 96) | func (c *robots) Watch(ctx context.Context, opts v1.ListOptions) (watc...
    method Create (line 111) | func (c *robots) Create(ctx context.Context, robot *v1alpha1.Robot, op...
    method Update (line 124) | func (c *robots) Update(ctx context.Context, robot *v1alpha1.Robot, op...
    method UpdateStatus (line 139) | func (c *robots) UpdateStatus(ctx context.Context, robot *v1alpha1.Rob...
    method Delete (line 154) | func (c *robots) Delete(ctx context.Context, name string, opts v1.Dele...
    method DeleteCollection (line 165) | func (c *robots) DeleteCollection(ctx context.Context, opts v1.DeleteO...
    method Patch (line 181) | func (c *robots) Patch(ctx context.Context, name string, pt types.Patc...
  function newRobots (line 58) | func newRobots(c *RegistryV1alpha1Client, namespace string) *robots {

FILE: src/go/pkg/configutil/config_reader.go
  function bashUnescape (line 36) | func bashUnescape(s string) string {
  function getConfigFromReader (line 53) | func getConfigFromReader(reader io.Reader) (map[string]string, error) {
  function setDefaultVars (line 70) | func setDefaultVars(vars map[string]string) {
  function ReadConfig (line 82) | func ReadConfig(project string, opts ...option.ClientOption) (map[string...
  function GetBoolean (line 106) | func GetBoolean(vars map[string]string, key string, def bool) bool {

FILE: src/go/pkg/configutil/config_reader_test.go
  function TestBashUnescape (line 23) | func TestBashUnescape(t *testing.T) {
  function TestGetConfigFromReader (line 53) | func TestGetConfigFromReader(t *testing.T) {
  function TestSetDefaultVars (line 77) | func TestSetDefaultVars(t *testing.T) {
  function TestGetBoolean (line 111) | func TestGetBoolean(t *testing.T) {

FILE: src/go/pkg/controller/approllout/controller.go
  constant fieldIndexOwners (line 54) | fieldIndexOwners  = "metadata.ownerReferences.uid"
  constant fieldIndexAppName (line 55) | fieldIndexAppName = "spec.appName"
  constant labelRobotName (line 56) | labelRobotName    = "cloudrobotics.com/robot-name"
  function Add (line 61) | func Add(ctx context.Context, mgr manager.Manager, baseValues chartutil....
  type Reconciler (line 209) | type Reconciler struct
    method enqueueForApp (line 166) | func (r *Reconciler) enqueueForApp(ctx context.Context, m metav1.Objec...
    method enqueueForOwner (line 182) | func (r *Reconciler) enqueueForOwner(m metav1.Object, q workqueue.Rate...
    method enqueueAll (line 193) | func (r *Reconciler) enqueueAll(ctx context.Context, q workqueue.RateL...
    method Reconcile (line 214) | func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Requ...
    method reconcile (line 227) | func (r *Reconciler) reconcile(ctx context.Context, ar *apps.AppRollou...
    method updateErrorStatus (line 357) | func (r *Reconciler) updateErrorStatus(ctx context.Context, ar *apps.A...
  function setStatus (line 365) | func setStatus(ar *apps.AppRollout, numWantCAs int, curCAs []apps.ChartA...
  function setCondition (line 395) | func setCondition(ar *apps.AppRollout, t apps.AppRolloutConditionType, s...
  function chartAssignmentChanged (line 425) | func chartAssignmentChanged(prev, cur *apps.ChartAssignment) (bool, erro...
  type errRobotSelectorOverlap (line 443) | type errRobotSelectorOverlap
    method Error (line 445) | func (r errRobotSelectorOverlap) Error() string {
  function generateChartAssignments (line 451) | func generateChartAssignments(
  function newCloudChartAssignment (line 542) | func newCloudChartAssignment(
  function newRobotChartAssignment (line 572) | func newRobotChartAssignment(
  function newBaseChartAssignment (line 601) | func newBaseChartAssignment(app *apps.App, rollout *apps.AppRollout, com...
  function matchingRobots (line 630) | func matchingRobots(robots []registry.Robot, sel *apps.RobotSelector) ([...
  function appNamespaceName (line 650) | func appNamespaceName(rollout string) string {
  type componentType (line 654) | type componentType
  constant compTypeRobot (line 657) | compTypeRobot componentType = "robot"
  constant compTypeCloud (line 658) | compTypeCloud               = "cloud"
  function chartAssignmentName (line 661) | func chartAssignmentName(rollout string, typ componentType, robot string...
  type robotValues (line 670) | type robotValues struct
  function setLabel (line 674) | func setLabel(o *metav1.ObjectMeta, k, v string) {
  function setAnnotation (line 681) | func setAnnotation(o *metav1.ObjectMeta, k, v string) {
  function setOwnerReference (line 690) | func setOwnerReference(om *metav1.ObjectMeta, ref metav1.OwnerReference) {
  function indexOwnerReferences (line 701) | func indexOwnerReferences(o kclient.Object) (vs []string) {
  function indexAppName (line 709) | func indexAppName(o kclient.Object) []string {
  constant labelAppName (line 716) | labelAppName = "cloudrobotics.com/app-name"
  constant labelAppVersion (line 719) | labelAppVersion = "cloudrobotics.com/app-version"
  function NewAppValidationWebhook (line 736) | func NewAppValidationWebhook(mgr manager.Manager) *admission.Webhook {
  type appValidator (line 741) | type appValidator struct
    method Handle (line 751) | func (v *appValidator) Handle(_ context.Context, req admission.Request...
  function newAppValidator (line 745) | func newAppValidator(sc *runtime.Scheme) *appValidator {
  function appValidate (line 762) | func appValidate(cur *apps.App) error {
  function NewAppRolloutValidationWebhook (line 785) | func NewAppRolloutValidationWebhook(mgr manager.Manager) *admission.Webh...
  type appRolloutValidator (line 790) | type appRolloutValidator struct
    method Handle (line 800) | func (v *appRolloutValidator) Handle(_ context.Context, req admission....
  function newAppRolloutValidator (line 794) | func newAppRolloutValidator(sc *runtime.Scheme) *appRolloutValidator {
  function appRolloutValidate (line 812) | func appRolloutValidate(cur *apps.AppRollout) error {

FILE: src/go/pkg/controller/approllout/controller_test.go
  function marshalYAML (line 29) | func marshalYAML(t *testing.T, v interface{}) string {
  function unmarshalYAML (line 38) | func unmarshalYAML(t *testing.T, v interface{}, s string) {
  function verifyChartAssignment (line 45) | func verifyChartAssignment(t *testing.T, want, got *apps.ChartAssignment) {
  function TestNewRobotChartAssignment (line 57) | func TestNewRobotChartAssignment(t *testing.T) {
  function TestNewCloudChartAssignment (line 128) | func TestNewCloudChartAssignment(t *testing.T) {
  function TestGenerateChartAssignments (line 201) | func TestGenerateChartAssignments(t *testing.T) {
  function generateApp (line 325) | func generateApp(name, version, robotPayload, cloudPayload string) apps....
  function generateRobot (line 350) | func generateRobot(name string, labels map[string]string) registry.Robot {
  function generateRollout (line 360) | func generateRollout(name, appName string) apps.AppRollout {
  function addRobotToRollout (line 372) | func addRobotToRollout(ar *apps.AppRollout, matchLabel, matchValue, vers...
  function TestGenerateChartAssignments_vApps (line 388) | func TestGenerateChartAssignments_vApps(t *testing.T) {
  function TestGenerateChartAssignments_vAppMissing (line 422) | func TestGenerateChartAssignments_vAppMissing(t *testing.T) {
  function TestGenerateChartAssignments_vAppCloud (line 447) | func TestGenerateChartAssignments_vAppCloud(t *testing.T) {
  function TestGenerateChartAssignments_vAppCloudMissing (line 483) | func TestGenerateChartAssignments_vAppCloudMissing(t *testing.T) {
  function TestGenerateChartAssignments_cloudPerRobot (line 508) | func TestGenerateChartAssignments_cloudPerRobot(t *testing.T) {
  function TestGenerateChartAssignments_selectorOverlap (line 575) | func TestGenerateChartAssignments_selectorOverlap(t *testing.T) {
  function TestSetStatus (line 619) | func TestSetStatus(t *testing.T) {
  function TestValidateAppRollout (line 665) | func TestValidateAppRollout(t *testing.T) {
  function TestValidateApp (line 780) | func TestValidateApp(t *testing.T) {

FILE: src/go/pkg/controller/chartassignment/controller.go
  constant defaultServiceAccountDeadline (line 45) | defaultServiceAccountDeadline = time.Minute
  constant fieldIndexNamespace (line 46) | fieldIndexNamespace           = "spec.namespaceName"
  constant statusCheckingOptOutLabel (line 47) | statusCheckingOptOutLabel     = "cloudrobotics.com/opt-out-error-checking"
  function Add (line 53) | func Add(ctx context.Context, mgr manager.Manager, cloud bool) error {
  type Reconciler (line 122) | type Reconciler struct
    method enqueueForPod (line 106) | func (r *Reconciler) enqueueForPod(ctx context.Context, m meta.Object,...
    method Reconcile (line 133) | func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Requ...
    method ensureNamespace (line 187) | func (r *Reconciler) ensureNamespace(ctx context.Context, as *apps.Cha...
    method ensureSecrets (line 225) | func (r *Reconciler) ensureSecrets(ctx context.Context, as *apps.Chart...
    method ensureServiceAccount (line 253) | func (r *Reconciler) ensureServiceAccount(ctx context.Context, ns *cor...
    method reconcile (line 294) | func (r *Reconciler) reconcile(ctx context.Context, as *apps.ChartAssi...
    method setStatus (line 366) | func (r *Reconciler) setStatus(ctx context.Context, as *apps.ChartAssi...
    method ensureDeleted (line 433) | func (r *Reconciler) ensureDeleted(ctx context.Context, as *apps.Chart...
  constant finalizer (line 160) | finalizer = "helm.apps.cloudrobotics.com"
  constant requeueFast (line 162) | requeueFast = 3 * time.Second
  constant requeueSlow (line 164) | requeueSlow = 3 * time.Minute
  type namespaceDeletionError (line 172) | type namespaceDeletionError struct
    method Error (line 176) | func (e *namespaceDeletionError) Error() string { return e.msg }
  type missingServiceAccountError (line 181) | type missingServiceAccountError struct
    method Error (line 185) | func (e *missingServiceAccountError) Error() string { return e.msg }
  function condition (line 359) | func condition(b bool) core.ConditionStatus {
  function stringsContain (line 454) | func stringsContain(list []string, s string) bool {
  function stringsDelete (line 463) | func stringsDelete(list []string, s string) (res []string) {
  function setOwnerReference (line 474) | func setOwnerReference(om *meta.ObjectMeta, ref meta.OwnerReference) bool {
  function inCondition (line 487) | func inCondition(as *apps.ChartAssignment, c apps.ChartAssignmentConditi...
  function setCondition (line 498) | func setCondition(as *apps.ChartAssignment, t apps.ChartAssignmentCondit...

FILE: src/go/pkg/controller/chartassignment/release.go
  type releases (line 50) | type releases struct
    method status (line 90) | func (rs *releases) status(name string) (releaseStatus, bool) {
    method add (line 103) | func (rs *releases) add(name string) *release {
    method ensureUpdated (line 127) | func (rs *releases) ensureUpdated(as *apps.ChartAssignment) bool {
    method ensureDeleted (line 152) | func (rs *releases) ensureDeleted(as *apps.ChartAssignment) bool {
  function newReleases (line 58) | func newReleases(cfg *rest.Config, rec record.EventRecorder) (*releases,...
  type release (line 71) | type release struct
    method run (line 159) | func (r *release) run() {
    method start (line 167) | func (r *release) start(f func()) bool {
    method setPhase (line 176) | func (r *release) setPhase(p apps.ChartAssignmentPhase) {
    method generation (line 184) | func (r *release) generation() int64 {
    method setGeneration (line 190) | func (r *release) setGeneration(generation int64) {
    method setFailed (line 196) | func (r *release) setFailed(err error, retry bool) {
    method delete (line 213) | func (r *release) delete(as *apps.ChartAssignment) {
    method update (line 235) | func (r *release) update(as *apps.ChartAssignment) {
  type releaseStatus (line 82) | type releaseStatus struct
  function loadAndExpandChart (line 281) | func loadAndExpandChart(as *apps.ChartAssignment) ([]*unstructured.Unstr...
  function loadChart (line 307) | func loadChart(cspec *apps.AssignedChart) (*chart.Chart, string, error) {
  function fetchChartTar (line 350) | func fetchChartTar(repoURL, name, version string) (io.Reader, error) {
  function newHTTPGetter (line 391) | func newHTTPGetter(url, certFile, keyFile, caFile string) (getter.Getter...
  function decodeManifests (line 395) | func decodeManifests(manifests map[string]string) ([]*unstructured.Unstr...

FILE: src/go/pkg/controller/chartassignment/release_test.go
  constant ChartName (line 14) | ChartName = "testchart"
  function verifyValues (line 17) | func verifyValues(t *testing.T, have string, wantValues chartutil.Values) {
  function Test_loadChart_mergesValues (line 25) | func Test_loadChart_mergesValues(t *testing.T) {
  function Test_loadChartWithoutTemplates_returnsZeroManifests (line 54) | func Test_loadChartWithoutTemplates_returnsZeroManifests(t *testing.T) {
  function Test_updateSynk_callsApply (line 74) | func Test_updateSynk_callsApply(t *testing.T) {
  function Test_deleteSynk_callsDelete (line 101) | func Test_deleteSynk_callsDelete(t *testing.T) {

FILE: src/go/pkg/controller/chartassignment/validator.go
  function NewValidationWebhook (line 33) | func NewValidationWebhook(mgr manager.Manager) *admission.Webhook {
  type chartAssignmentValidator (line 38) | type chartAssignmentValidator struct
    method Handle (line 48) | func (v *chartAssignmentValidator) Handle(_ context.Context, req admis...
    method validate (line 68) | func (v *chartAssignmentValidator) validate(cur, old *apps.ChartAssign...
  function newChartAssignmentValidator (line 42) | func newChartAssignmentValidator(sc *runtime.Scheme) *chartAssignmentVal...

FILE: src/go/pkg/controller/chartassignment/validator_test.go
  function unmarshalYAML (line 25) | func unmarshalYAML(t *testing.T, v interface{}, s string) {
  function TestValidate (line 32) | func TestValidate(t *testing.T) {

FILE: src/go/pkg/gcr/update_gcr_credential_test.go
  function TestDockercfgJSON (line 23) | func TestDockercfgJSON(t *testing.T) {

FILE: src/go/pkg/gcr/update_gcr_credentials.go
  constant SecretName (line 40) | SecretName = "gcr-json-key"
  function DockerCfgJSON (line 44) | func DockerCfgJSON(token string) []byte {
  function patchServiceAccount (line 69) | func patchServiceAccount(ctx context.Context, k8s *kubernetes.Clientset,...
  function UpdateGcrCredentials (line 91) | func UpdateGcrCredentials(ctx context.Context, k8s *kubernetes.Clientset...

FILE: src/go/pkg/kubetest/kubetest.go
  type Environment (line 59) | type Environment struct
    method Ctx (line 138) | func (e *Environment) Ctx() context.Context {
    method InstallChartArchive (line 152) | func (e *Environment) InstallChartArchive(cluster, name, namespace, pa...
    method Client (line 186) | func (e *Environment) Client(cluster string) client.Client {
    method Teardown (line 201) | func (e *Environment) Teardown() {
    method Run (line 225) | func (e *Environment) Run(tests ...TestFunc) {
    method Uniq (line 243) | func (e *Environment) Uniq(s string) string {
    method New (line 257) | func (env *Environment) New(testFn TestFunc) *Fixture {
  type Config (line 69) | type Config struct
  type ClusterConfig (line 77) | type ClusterConfig struct
  type cluster (line 81) | type cluster struct
  function New (line 90) | func New(t *testing.T, cfg Config) *Environment {
  function helmValues (line 142) | func helmValues(vars map[string]string) string {
  type TestFunc (line 222) | type TestFunc
  type Fixture (line 249) | type Fixture struct
    method Ctx (line 265) | func (f *Fixture) Ctx() context.Context {
    method ObjectKey (line 270) | func (f *Fixture) ObjectKey(o client.Object) client.ObjectKey {
    method Uniq (line 277) | func (f *Fixture) Uniq(s string) string {
    method FromYAML (line 284) | func (f *Fixture) FromYAML(tmpl string, vals, dst interface{}) {
    method Client (line 336) | func (f *Fixture) Client(cluster string) client.Client {
    method ChartAssignmentHasStatus (line 507) | func (f *Fixture) ChartAssignmentHasStatus(ca *crcapps.ChartAssignment...
  function BuildInlineChart (line 302) | func BuildInlineChart(t *testing.T, name, tmpl, values string) string {
  function addFileToTar (line 322) | func addFileToTar(tw *tar.Writer, path, content string) error {
  function setupCluster (line 342) | func setupCluster(synkPath string, cluster *cluster) error {
  function DeploymentReady (line 488) | func DeploymentReady(ctx context.Context, c client.Client, namespace, na...

FILE: src/go/pkg/kubeutils/kubeutils.go
  constant LocalContext (line 38) | LocalContext = "kubernetes-admin@kubernetes"
  constant localConfig (line 40) | localConfig            = "~/.kube/config"
  constant deletionTimeoutSeconds (line 41) | deletionTimeoutSeconds = 60
  function ExpandUser (line 45) | func ExpandUser(path string) string {
  function CloudKubernetesContextName (line 55) | func CloudKubernetesContextName(projectID, region string) string {
  function GetCloudKubernetesContext (line 60) | func GetCloudKubernetesContext() (string, error) {
  function GetRobotKubernetesContext (line 75) | func GetRobotKubernetesContext() (string, error) {
  function LoadOutOfClusterConfigLocal (line 85) | func LoadOutOfClusterConfigLocal() (*rest.Config, error) {
  function LoadOutOfClusterConfig (line 89) | func LoadOutOfClusterConfig(context string) (*rest.Config, error) {
  type PrefixingRoundtripper (line 102) | type PrefixingRoundtripper struct
    method RoundTrip (line 107) | func (pr *PrefixingRoundtripper) RoundTrip(r *http.Request) (*http.Res...
  function BuildCloudKubernetesConfig (line 119) | func BuildCloudKubernetesConfig(ts oauth2.TokenSource, remoteServer stri...
  function UpdateSecret (line 134) | func UpdateSecret(ctx context.Context, k8s kubernetes.Interface, input *...

FILE: src/go/pkg/robotauth/robotauth.go
  constant credentialsFile (line 49) | credentialsFile = "~/.config/cloud-robotics/robot-id.json"
  type RobotAuth (line 53) | type RobotAuth struct
    method StoreInFile (line 103) | func (r *RobotAuth) StoreInFile() error {
    method StoreInK8sSecret (line 123) | func (r *RobotAuth) StoreInK8sSecret(ctx context.Context, clientset ku...
    method CreatePrivateKey (line 143) | func (r *RobotAuth) CreatePrivateKey() error {
    method getTokenEndpoint (line 159) | func (r *RobotAuth) getTokenEndpoint() string {
    method CreateRobotTokenSource (line 166) | func (r *RobotAuth) CreateRobotTokenSource(ctx context.Context, gcpSaC...
    method CreateJWT (line 186) | func (r *RobotAuth) CreateJWT(ctx context.Context, lifetime time.Durat...
    method ServiceAccountEmail (line 232) | func (r *RobotAuth) ServiceAccountEmail(saName string) (string, error) {
  function filename (line 61) | func filename() string {
  function LoadFromFile (line 67) | func LoadFromFile(keyfile string) (*RobotAuth, error) {
  function LoadFromK8sSecret (line 85) | func LoadFromK8sSecret(ctx context.Context, clientset kubernetes.Interfa...
  type robotJWTSource (line 249) | type robotJWTSource struct
    method Token (line 256) | func (ts *robotJWTSource) Token() (*oauth2.Token, error) {
  constant jwtMinLifetime (line 293) | jwtMinLifetime = time.Minute
  function CreateJWTSource (line 298) | func CreateJWTSource() oauth2.TokenSource {

FILE: src/go/pkg/robotauth/robotauth_test.go
  constant testPubKey (line 20) | testPubKey = `
  constant testPrivKey (line 31) | testPrivKey = `
  function TestK8sSecretLoadStoreRoundtrip (line 63) | func TestK8sSecretLoadStoreRoundtrip(t *testing.T) {
  function TestCreateJWT (line 86) | func TestCreateJWT(t *testing.T) {
  type mockRoundTripper (line 114) | type mockRoundTripper struct
    method RoundTrip (line 118) | func (rt *mockRoundTripper) RoundTrip(req *http.Request) (*http.Respon...
  function makeTokenResponse (line 122) | func makeTokenResponse(token string) *http.Response {
  constant testToken (line 133) | testToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJyb2JvdC1yb2...
  constant textTokenExpiryUnix (line 135) | textTokenExpiryUnix = 1741615721
  function TestCreateJWTSource (line 138) | func TestCreateJWTSource(t *testing.T) {

FILE: src/go/pkg/setup/setupcommon.go
  function GetRobotName (line 45) | func GetRobotName(ctx context.Context, f util.Factory, client dynamic.Re...
  function exitIfNotRunningInTerminal (line 72) | func exitIfNotRunningInTerminal(message ...interface{}) {
  function selectRobot (line 81) | func selectRobot(f util.Factory, robots []unstructured.Unstructured) (st...
  function newExponentialBackoff (line 105) | func newExponentialBackoff(initialInterval time.Duration, multiplier flo...
  function WaitForDNS (line 118) | func WaitForDNS(domain string, retries uint64) error {
  function WaitForService (line 152) | func WaitForService(client *http.Client, url string, retries uint64) err...
  function PublishCredentialsToCloud (line 173) | func PublishCredentialsToCloud(client *http.Client, auth *robotauth.Robo...
  function isKeyRegistryAvailable (line 186) | func isKeyRegistryAvailable(auth *robotauth.RobotAuth, client *http.Clie...
  function publishPublicKeyToCloudRegistry (line 195) | func publishPublicKeyToCloudRegistry(auth *robotauth.RobotAuth, client *...
  function getPublicKey (line 226) | func getPublicKey(privateKey []byte) ([]byte, error) {
  function mergeMaps (line 259) | func mergeMaps(base, additions map[string]string) map[string]string {
  function CreateOrUpdateRobot (line 271) | func CreateOrUpdateRobot(ctx context.Context, client dynamic.ResourceInt...

FILE: src/go/pkg/setup/setupcommon_test.go
  function TestSelectRobot (line 26) | func TestSelectRobot(t *testing.T) {
  function TestWaitForService_OkIfServiceResponds (line 59) | func TestWaitForService_OkIfServiceResponds(t *testing.T) {

FILE: src/go/pkg/setup/util/factory.go
  type Factory (line 24) | type Factory interface
  type DefaultFactory (line 29) | type DefaultFactory struct
    method ScanInt (line 36) | func (f *DefaultFactory) ScanInt() (int, error) {
    method GetNetworkInterfaceIP (line 52) | func (f *DefaultFactory) GetNetworkInterfaceIP(namePrefix string) (str...
  function NewFactory (line 31) | func NewFactory() *DefaultFactory {

FILE: src/go/pkg/setup/util/fake.go
  type TestFactory (line 17) | type TestFactory struct
    method ScanInt (line 25) | func (f *TestFactory) ScanInt() (int, error) {
  function NewTestFactory (line 21) | func NewTestFactory() *TestFactory {

FILE: src/go/pkg/synk/interface.go
  type Interface (line 10) | type Interface interface

FILE: src/go/pkg/synk/sort.go
  type gvknn (line 25) | type gvknn struct
  function newGvknn (line 34) | func newGvknn(group, version, kind, namespace, name string) *gvknn {
  function less (line 51) | func less(l, r *gvknn) bool {
  function gvknnUnstructured (line 57) | func gvknnUnstructured(u *unstructured.Unstructured) *gvknn {
  function gvknnRSpecG (line 62) | func gvknnRSpecG(r *apps.ResourceSetSpecGroup) *gvknn {
  function gvknnRStatusG (line 66) | func gvknnRStatusG(r *apps.ResourceSetStatusGroup) *gvknn {
  function lessUnstructured (line 70) | func lessUnstructured(l, r *unstructured.Unstructured) bool {
  function lessResourceSetSpecGroup (line 74) | func lessResourceSetSpecGroup(l, r *apps.ResourceSetSpecGroup) bool {
  function lessResourceSetStatusGroup (line 78) | func lessResourceSetStatusGroup(l, r *apps.ResourceSetStatusGroup) bool {

FILE: src/go/pkg/synk/sort_test.go
  function TestLessResourceSetStatusGroup (line 23) | func TestLessResourceSetStatusGroup(t *testing.T) {
  function TestLessResourceSetSpecGroup (line 42) | func TestLessResourceSetSpecGroup(t *testing.T) {
  function TestLessUnstructured (line 61) | func TestLessUnstructured(t *testing.T) {
  function TestLess (line 72) | func TestLess(t *testing.T) {

FILE: src/go/pkg/synk/synk.go
  constant totalAnnotationSizeLimitB (line 57) | totalAnnotationSizeLimitB int = 256 * (1 << 10)
  type Synk (line 60) | type Synk struct
    method Init (line 134) | func (s *Synk) Init() error {
    method Delete (line 215) | func (s *Synk) Delete(ctx context.Context, name string) error {
    method Apply (line 224) | func (s *Synk) Apply(
    method applyAll (line 309) | func (s *Synk) applyAll(
    method initialize (line 415) | func (s *Synk) initialize(
    method populateNamespaces (line 484) | func (s *Synk) populateNamespaces(
    method applyOne (line 670) | func (s *Synk) applyOne(ctx context.Context, resource *unstructured.Un...
    method crdAvailable (line 826) | func (s *Synk) crdAvailable(ucrd *unstructured.Unstructured) (bool, er...
    method createResourceSet (line 871) | func (s *Synk) createResourceSet(ctx context.Context, rs *apps.Resourc...
    method updateResourceSetStatus (line 923) | func (s *Synk) updateResourceSetStatus(ctx context.Context, rs *apps.R...
    method deleteFailedResourceSets (line 982) | func (s *Synk) deleteFailedResourceSets(ctx context.Context, name stri...
    method deleteResourceSets (line 1016) | func (s *Synk) deleteResourceSets(ctx context.Context, name string, ve...
    method next (line 1042) | func (s *Synk) next(ctx context.Context, name string) (version int32, ...
  function New (line 68) | func New(client dynamic.Interface, discovery discovery.CachedDiscoveryIn...
  function NewForConfig (line 81) | func NewForConfig(cfg *rest.Config) (*Synk, error) {
  type ApplyOptions (line 99) | type ApplyOptions struct
    method logf (line 119) | func (o *ApplyOptions) logf(r *unstructured.Unstructured, action apps....
    method errorf (line 125) | func (o *ApplyOptions) errorf(r *unstructured.Unstructured, action app...
  constant StatusSuccess (line 115) | StatusSuccess = "success"
  constant StatusFailure (line 116) | StatusFailure = "failure"
  type transientErr (line 267) | type transientErr struct
  function IsTransientErr (line 272) | func IsTransientErr(err error) bool {
  function deleteAppliedAnnotation (line 544) | func deleteAppliedAnnotation(u *unstructured.Unstructured) {
  function setAppliedAnnotation (line 554) | func setAppliedAnnotation(u *unstructured.Unstructured) error {
  function getAppliedAnnotation (line 571) | func getAppliedAnnotation(u *unstructured.Unstructured) []byte {
  function validateOwnerRefs (line 577) | func validateOwnerRefs(r *unstructured.Unstructured, set *apps.ResourceS...
  function setOwnerRef (line 606) | func setOwnerRef(r *unstructured.Unstructured, set *apps.ResourceSet) {
  function canReplace (line 628) | func canReplace(resource *unstructured.Unstructured, patchErr error) bool {
  function replace (line 652) | func replace(ctx context.Context, client dynamic.ResourceInterface, reso...
  type applyResult (line 886) | type applyResult struct
    method String (line 892) | func (r *applyResult) String() string {
  type applyResults (line 896) | type applyResults
    method set (line 898) | func (r applyResults) set(res *unstructured.Unstructured, action apps....
    method failed (line 906) | func (r applyResults) failed(res *unstructured.Unstructured) bool {
    method list (line 913) | func (r applyResults) list() (l []*applyResult) {
  function isTestResource (line 1062) | func isTestResource(r *unstructured.Unstructured) bool {
  function isCustomResourceDefinition (line 1067) | func isCustomResourceDefinition(r *unstructured.Unstructured) bool {
  function separateCRDsFromResources (line 1071) | func separateCRDsFromResources(resources []*unstructured.Unstructured) (...
  function filter (line 1082) | func filter(in []*unstructured.Unstructured, f func(*unstructured.Unstru...
  function resourceSetName (line 1091) | func resourceSetName(s string, v int32) string {
  function decodeResourceSetName (line 1097) | func decodeResourceSetName(s string) (string, int32, bool) {
  function sortResources (line 1109) | func sortResources(res []*unstructured.Unstructured) {
  function resourceKey (line 1115) | func resourceKey(r *unstructured.Unstructured) string {
  function gvkKey (line 1123) | func gvkKey(group, version, kind string) string {
  function convert (line 1128) | func convert(from, to runtime.Object) error {

FILE: src/go/pkg/synk/synk_test.go
  type fakeCachedDiscoveryClient (line 44) | type fakeCachedDiscoveryClient struct
    method Invalidate (line 48) | func (d *fakeCachedDiscoveryClient) Invalidate() {}
    method ServerGroupsAndResources (line 50) | func (d *fakeCachedDiscoveryClient) ServerGroupsAndResources() ([]*met...
  type fixture (line 62) | type fixture struct
    method newSynk (line 76) | func (f *fixture) newSynk() *Synk {
    method addObjects (line 90) | func (f *fixture) addObjects(objs ...runtime.Object) {
    method expectActions (line 94) | func (f *fixture) expectActions(as ...k8stest.Action) {
    method verifyWriteActions (line 98) | func (f *fixture) verifyWriteActions() {
  function newFixture (line 72) | func newFixture(t *testing.T) *fixture {
  function TestSynk_IsTransientErr (line 114) | func TestSynk_IsTransientErr(t *testing.T) {
  function TestSynk_initialize (line 158) | func TestSynk_initialize(t *testing.T) {
  function TestSynk_updateResourceSetStatus (line 213) | func TestSynk_updateResourceSetStatus(t *testing.T) {
  function TestSynk_applyAllIsUpdatingResources (line 306) | func TestSynk_applyAllIsUpdatingResources(t *testing.T) {
  function TestSynk_applyAllIsCreatingResources (line 370) | func TestSynk_applyAllIsCreatingResources(t *testing.T) {
  function TestSynk_applyAllRetriesResourceExpired (line 416) | func TestSynk_applyAllRetriesResourceExpired(t *testing.T) {
  function TestSynk_skipsTestResources (line 491) | func TestSynk_skipsTestResources(t *testing.T) {
  function TestSynk_deleteResourceSets (line 532) | func TestSynk_deleteResourceSets(t *testing.T) {
  function TestSynk_deleteFailedResourceSets (line 561) | func TestSynk_deleteFailedResourceSets(t *testing.T) {
  function TestSynk_populateNamespaces (line 592) | func TestSynk_populateNamespaces(t *testing.T) {
  function TestSynk_skipLastAppliedAnnotationForLargeResource (line 641) | func TestSynk_skipLastAppliedAnnotationForLargeResource(t *testing.T) {
  function newUnstructured (line 662) | func newUnstructured(apiVersion, kind, namespace, name string) *unstruct...
  function unmarshalYAML (line 671) | func unmarshalYAML(t *testing.T, v interface{}, s string) {
  function toUnstructured (line 678) | func toUnstructured(t *testing.T, o runtime.Object) *unstructured.Unstru...
  function filterReadActions (line 688) | func filterReadActions(actions []k8stest.Action) (ret []k8stest.Action) {
  function sprintAction (line 698) | func sprintAction(a k8stest.Action) string {

FILE: src/go/tests/apps/apps_test.go
  constant robotClusterName (line 33) | robotClusterName = "robot"
  constant inlineChartTemplate (line 35) | inlineChartTemplate = `
  constant goodDeployment (line 46) | goodDeployment = `
  constant deploymentWithBadLabels (line 65) | deploymentWithBadLabels = `
  constant badJob (line 84) | badJob = `
  constant goodJob (line 100) | goodJob = `
  function TestAll (line 118) | func TestAll(t *testing.T) {
  function testCreateChartAssignment_WithChartReference_Works (line 157) | func testCreateChartAssignment_WithChartReference_Works(t *testing.T, f ...
  function testCreateChartAssignment_WithInlineChart_BecomesReady (line 214) | func testCreateChartAssignment_WithInlineChart_BecomesReady(t *testing.T...
  function testCreateChartAssignment_WithBadDeployment_BecomesFailed (line 238) | func testCreateChartAssignment_WithBadDeployment_BecomesFailed(t *testin...
  function testUpdateChartAssignment_WithFixedDeployment_BecomesReady (line 262) | func testUpdateChartAssignment_WithFixedDeployment_BecomesReady(t *testi...
  function testUpdateChartAssignment_WithFixedJob_BecomesReady (line 313) | func testUpdateChartAssignment_WithFixedJob_BecomesReady(t *testing.T, f...
  function testCreateChartAssignment_CopiesLabelledSecret (line 364) | func testCreateChartAssignment_CopiesLabelledSecret(t *testing.T, f *kub...

FILE: src/go/tests/k8s_integration_test.go
  constant appInitializationTimeout (line 39) | appInitializationTimeout = 7 * time.Minute
  constant podInitializationTimeout (line 40) | podInitializationTimeout = 5 * time.Minute
  function checkHealthOfKubernetesCluster (line 43) | func checkHealthOfKubernetesCluster(ctx context.Context, kubernetesConte...
  function convert (line 137) | func convert(from, to runtime.Object) error {
  function TestCloudClusterAppStatus (line 145) | func TestCloudClusterAppStatus(t *testing.T) {
  function TestKubernetesCloudClusterStatus (line 210) | func TestKubernetesCloudClusterStatus(t *testing.T) {
  function TestKubernetesRobotClusterStatus (line 222) | func TestKubernetesRobotClusterStatus(t *testing.T) {

FILE: src/go/tests/k8s_integration_test_auth_helper.go
  function init (line 47) | func init() {
  type gcpAuthProvider (line 118) | type gcpAuthProvider struct
    method WrapTransport (line 190) | func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.Rou...
    method Login (line 200) | func (g *gcpAuthProvider) Login() error { return nil }
  function newGCPAuthProvider (line 125) | func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister...
  function isCmdTokenSource (line 142) | func isCmdTokenSource(gcpConfig map[string]string) bool {
  function tokenSource (line 147) | func tokenSource(isCmd bool, gcpConfig map[string]string) (oauth2.TokenS...
  function parseScopes (line 179) | func parseScopes(gcpConfig map[string]string) []string {
  type cachedTokenSource (line 202) | type cachedTokenSource struct
    method Token (line 228) | func (t *cachedTokenSource) Token() (*oauth2.Token, error) {
    method cachedToken (line 246) | func (t *cachedTokenSource) cachedToken() *oauth2.Token {
    method update (line 256) | func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string {
    method baseCache (line 271) | func (t *cachedTokenSource) baseCache() map[string]string {
  function newCachedTokenSource (line 211) | func newCachedTokenSource(accessToken, expiry string, persister restclie...
  type commandTokenSource (line 283) | type commandTokenSource struct
    method Token (line 310) | func (c *commandTokenSource) Token() (*oauth2.Token, error) {
    method parseTokenCmdOutput (line 326) | func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oaut...
  function newCmdTokenSource (line 291) | func newCmdTokenSource(cmd string, args []string, tokenKey, expiryKey, t...
  function parseJSONPath (line 358) | func parseJSONPath(input interface{}, name, template string) (string, er...
  type conditionalTransport (line 370) | type conditionalTransport struct
    method RoundTrip (line 378) | func (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Res...
    method WrappedRoundTripper (line 397) | func (t *conditionalTransport) WrappedRoundTripper() http.RoundTripper...

FILE: src/go/tests/relay/in_process_relay_test.go
  function pickUnusedPortOrDie (line 27) | func pickUnusedPortOrDie() int {
  function initRelay (line 41) | func initRelay() {
  function serveFunction (line 91) | func serveFunction(
  function serveFunctionWithTimeout (line 96) | func serveFunctionWithTimeout(
  function TestHttpResponse (line 115) | func TestHttpResponse(t *testing.T) {
  function TestHttpTimeout (line 139) | func TestHttpTimeout(t *testing.T) {
  function TestHttpErrorPropagation (line 161) | func TestHttpErrorPropagation(t *testing.T) {

FILE: src/go/tests/relay/nok8s_relay_test.go
  constant RelayClientPath (line 45) | RelayClientPath = "src/go/cmd/http-relay-client/http-relay-client-app_/h...
  constant RelayServerPath (line 46) | RelayServerPath = "src/go/cmd/http-relay-server/http-relay-server-app_/h...
  type relay (line 62) | type relay struct
    method start (line 68) | func (r *relay) start(backendAddress string, extraClientArgs ...string...
    method stop (line 123) | func (r *relay) stop() error {
  function TestHttpRelay (line 136) | func TestHttpRelay(t *testing.T) {
  function TestDroppedUserClientFreesRelayChannel (line 209) | func TestDroppedUserClientFreesRelayChannel(t *testing.T) {
  function TestDroppedBidiStreamFreesRelayChannel (line 266) | func TestDroppedBidiStreamFreesRelayChannel(t *testing.T) {
  type testServer (line 329) | type testServer struct
    method EmptyCall (line 334) | func (s *testServer) EmptyCall(ctx context.Context, in *testpb.Empty) ...
    method UnaryCall (line 338) | func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleR...
  type relayWithGrpcServer (line 346) | type relayWithGrpcServer struct
    method mustStop (line 354) | func (r *relayWithGrpcServer) mustStop(t *testing.T) {
  function mustStartRelayWithGrpcServer (line 363) | func mustStartRelayWithGrpcServer(t *testing.T, service testpb.TestServi...
  function TestGrpcRelaySimpleCallWorks (line 398) | func TestGrpcRelaySimpleCallWorks(t *testing.T) {
  function TestGrpcRelayChunkingOfLargeResponseWorks (line 414) | func TestGrpcRelayChunkingOfLargeResponseWorks(t *testing.T) {
  function TestGrpcRelayErrorArePropagated (line 440) | func TestGrpcRelayErrorArePropagated(t *testing.T) {
Condensed preview — 462 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,878K chars).
[
  {
    "path": ".bazelignore",
    "chars": 12,
    "preview": "src/.gopath\n"
  },
  {
    "path": ".bazelrc",
    "chars": 2101,
    "preview": "# Enable Bzlmod for every Bazel command\ncommon --enable_bzlmod\n\n# Work around go issue with LLVM 15+: https://github.com"
  },
  {
    "path": ".bazelversion",
    "chars": 6,
    "preview": "8.4.2\n"
  },
  {
    "path": ".dockerignore",
    "chars": 2,
    "preview": "*\n"
  },
  {
    "path": ".editorconfig",
    "chars": 288,
    "preview": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = "
  },
  {
    "path": ".github/ci/.bazelrc",
    "chars": 950,
    "preview": "# Bazel config for CI/CD builds.\n\n# Default to keep going\nbuild --keep_going\n\n# Use rbe remote execution and caching on "
  },
  {
    "path": ".github/ci/Dockerfile.integration-test-image",
    "chars": 1421,
    "preview": "# Image used for integration_test.sh on Cloud Build.\n# Allows access to GKE and to run Bazel commands.\nFROM gcr.io/cloud"
  },
  {
    "path": ".github/ci/common.sh",
    "chars": 3027,
    "preview": "#!/bin/bash\n\n# Format for the xtrace lines\nexport 'PS4=+$(date --rfc-3339=seconds):${BASH_SOURCE}:${LINENO}: '\nset -o er"
  },
  {
    "path": ".github/ci/deploy_navtest.sh",
    "chars": 601,
    "preview": "#!/bin/bash\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\n\nPROJECT_DIR=\"${DIR}/depl"
  },
  {
    "path": ".github/ci/deploy_navtest_cloudbuild.yaml",
    "chars": 249,
    "preview": "# Call deploy_navtest.sh on Cloud Build.\n# TODO(b/323509860): Run directly on the Action runner when it supports WIF.\nst"
  },
  {
    "path": ".github/ci/deployments/robco-integration-test/config.sh",
    "chars": 550,
    "preview": "#!/usr/bin/env bash\n\n# Enable cloud robotics layer 2\nAPP_MANAGEMENT=true\n\nGCP_PROJECT_ID=robco-integration-test\nGCP_REGI"
  },
  {
    "path": ".github/ci/deployments/robco-integration-test/kubernetes/k8s-relay-rollout.yaml",
    "chars": 201,
    "preview": "apiVersion: apps.cloudrobotics.com/v1alpha1\nkind: AppRollout\nmetadata:\n  name: k8s-relay\n  labels:\n    app: k8s-relay\nsp"
  },
  {
    "path": ".github/ci/deployments/robco-navtest/config.sh",
    "chars": 493,
    "preview": "#!/usr/bin/env bash\n\n# Enable google cloud robotics layer 2\nAPP_MANAGEMENT=true\n\nGCP_PROJECT_ID=robco-navtest\nGCP_REGION"
  },
  {
    "path": ".github/ci/integration_test.sh",
    "chars": 1989,
    "preview": "#!/bin/bash\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\nsource \"./scripts/common."
  },
  {
    "path": ".github/ci/integration_test_cloudbuild.yaml",
    "chars": 1038,
    "preview": "# A Cloud Build job for running integration_test.sh.\n# TODO(b/323509860): Run directly on the Action runner when it supp"
  },
  {
    "path": ".github/ci/integration_test_image_builder.sh",
    "chars": 473,
    "preview": "#!/bin/bash\n# Builds and pushes a docker image that can be used in Cloud Build to run\n# the integration test (see integr"
  },
  {
    "path": ".github/ci/presubmit.sh",
    "chars": 1534,
    "preview": "#!/bin/bash\n#\n# Presubmit script for testing cloud robotics.\n# Expected to run remotely on a GitHub Actions runner, not "
  },
  {
    "path": ".github/ci/release_binary.sh",
    "chars": 2330,
    "preview": "#!/bin/bash\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n# shellcheck source=ci/common.sh"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 302,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/src/\"\n    schedule:\n      interval: \"weekly\"\n\n  - pa"
  },
  {
    "path": ".github/workflows/check-bazel.yml",
    "chars": 1332,
    "preview": "name: Check Bazel\n\non:\n  workflow_call:\n\npermissions:\n  contents: read\n  id-token: write\n\njobs:\n  check-bazel:\n    runs-"
  },
  {
    "path": ".github/workflows/postsubmit.yml",
    "chars": 1606,
    "preview": "name: Postsubmit\n\non:\n  schedule:\n    - cron: \"0 4 * * *\" # Once a day at 4am.\n  # Manual runs through Actions tab in th"
  },
  {
    "path": ".github/workflows/presubmit.yml",
    "chars": 2852,
    "preview": "name: Presubmit\n\non:\n  pull_request:\n    branches: [\"main\"]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  id-tok"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 2605,
    "preview": "name: Create release\n\non:\n  schedule:\n    - cron: \"0 5 * * *\" # Once a day at 5am.\n  # Manual runs through Actions tab i"
  },
  {
    "path": ".gitignore",
    "chars": 160,
    "preview": ".cache/\n*.pyc\nenv/\n\n# auto-generated by scripts/backport_kubeadm.sh\nnsenter\n\n# IntelliJ files\n.project/\n\n# Bazel\nbazel-*"
  },
  {
    "path": ".pep8",
    "chars": 53,
    "preview": "[pep8]\naggressive=2\nindent-size=2\nmax-line-length=80\n"
  },
  {
    "path": "BUILD.bazel",
    "chars": 977,
    "preview": "# Description:\n#   Root BUILD file for cloud-robotics\n\nload(\"@bazel_gazelle//:def.bzl\", \"gazelle\")\n\npackage(default_visi"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1358,
    "preview": "# How to Contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guid"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "METADATA",
    "chars": 341,
    "preview": "name: \"Cloud Robotics\"\n\nthird_party {\n  # https://nvd.nist.gov/products/cpe/search\n  security {\n    tag: \"NVD-CPE2.3:cpe"
  },
  {
    "path": "MODULE.bazel",
    "chars": 4291,
    "preview": "bazel_dep(name = \"aspect_bazel_lib\", version = \"2.21.2\")\nbazel_dep(name = \"bazel_skylib\", version = \"1.8.1\")\nbazel_dep(n"
  },
  {
    "path": "README.md",
    "chars": 1731,
    "preview": "# Cloud Robotics Core\n\nGoogle's Cloud Robotics Core is an open source platform that provides\ninfrastructure essential to"
  },
  {
    "path": "bazel/BUILD.bazel",
    "chars": 555,
    "preview": "exports_files([\n    \"app.bzl\",\n    \"app_chart.bzl\",\n    \"container_push.bzl\",\n    \"repositories.bzl\",\n])\n\nplatform(\n    "
  },
  {
    "path": "bazel/BUILD.sysroot",
    "chars": 1384,
    "preview": "filegroup(\n    name = \"all_files\",\n    srcs = glob(\n        [\n            \"lib/x86_64-linux-gnu/ld*\",\n            \"lib/x"
  },
  {
    "path": "bazel/app.bzl",
    "chars": 1471,
    "preview": "load(\"//bazel/build_rules/app_chart:run_parallel.bzl\", \"run_parallel\")\n\ndef app(name, charts, visibility = None):\n    \"\""
  },
  {
    "path": "bazel/app_chart.bzl",
    "chars": 5726,
    "preview": "load(\"//bazel:build_rules/helm_chart.bzl\", \"helm_chart\")\nload(\"//bazel/build_rules/app_chart:cache_gcr_credentials.bzl\","
  },
  {
    "path": "bazel/build_rules/app_chart/BUILD.bazel",
    "chars": 182,
    "preview": "exports_files([\n    \"cache_gcr_credentials.sh.tpl\",\n    \"Chart.yaml.template\",\n    \"push_all.sh.tpl\",\n    \"run_parallel."
  },
  {
    "path": "bazel/build_rules/app_chart/Chart.yaml.template",
    "chars": 109,
    "preview": "apiVersion: v1\nname: ${name}\nversion: ${version}\n# Linter expects an icon.\nicon: https://google.com/icon.png\n"
  },
  {
    "path": "bazel/build_rules/app_chart/cache_gcr_credentials.bzl",
    "chars": 1833,
    "preview": "def _get_runfile_path(ctx, f):\n    \"\"\"Return the runfiles relative path of f.\"\"\"\n    if ctx.workspace_name:\n        retu"
  },
  {
    "path": "bazel/build_rules/app_chart/cache_gcr_credentials.sh.tpl",
    "chars": 1170,
    "preview": "#!/usr/bin/env bash\n\nset -eu\n\nfunction guess_runfiles() {\n    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1\n    pwd\n"
  },
  {
    "path": "bazel/build_rules/app_chart/push_all.bzl",
    "chars": 2640,
    "preview": "load(\"//bazel:container_push.bzl\", \"container_push\")\n\ndef _get_runfile_path(ctx, f):\n    \"\"\"Return the runfiles relative"
  },
  {
    "path": "bazel/build_rules/app_chart/push_all.sh.tpl",
    "chars": 864,
    "preview": "#!/usr/bin/env bash\n\nset -eu\n\nif [[ \"$#\" -lt 1 ]]; then\n  echo \"Usage: $0 <container-registry>\"\n  exit 1\nfi\nCONTAINER_RE"
  },
  {
    "path": "bazel/build_rules/app_chart/run_parallel.bzl",
    "chars": 1584,
    "preview": "def _get_runfile_path(ctx, f):\n    \"\"\"Return the runfiles relative path of f.\"\"\"\n    if ctx.workspace_name:\n        retu"
  },
  {
    "path": "bazel/build_rules/app_chart/run_parallel.sh.tpl",
    "chars": 530,
    "preview": "#!/usr/bin/env bash\n\nset -eu\n\nfunction guess_runfiles() {\n    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1\n    pwd\n"
  },
  {
    "path": "bazel/build_rules/app_chart/values-cloud.yaml",
    "chars": 200,
    "preview": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\nr"
  },
  {
    "path": "bazel/build_rules/app_chart/values-robot.yaml",
    "chars": 127,
    "preview": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\n\nrobot:\n  na"
  },
  {
    "path": "bazel/build_rules/copy.bzl",
    "chars": 864,
    "preview": "\"\"\"Macros to help rearrange files.\"\"\"\n\ndef copy_files(name, srcs, outdir, visibility = None):\n    \"\"\"Creates copies of f"
  },
  {
    "path": "bazel/build_rules/helm_chart.bzl",
    "chars": 1893,
    "preview": "def helm_chart(ctx, name, chart, files, templates, values, version, helm, out):\n    \"\"\"Starlark function that builds a h"
  },
  {
    "path": "bazel/build_rules/helm_template.bzl",
    "chars": 1148,
    "preview": "def helm_template(name, release_name, chart, values, namespace = None, helm_version = 2):\n    \"\"\"Locally expand a helm c"
  },
  {
    "path": "bazel/container_push.bzl",
    "chars": 448,
    "preview": "load(\"@rules_oci//oci:defs.bzl\", \"oci_push\")\n\ndef container_push(*args, **kwargs):\n    \"\"\"Creates a script to push a con"
  },
  {
    "path": "bazel/debug_repository.bzl",
    "chars": 830,
    "preview": "\"\"\"Debug util for repository definitions.\"\"\"\n\ndef debug_repository(repo, *fields):\n    \"\"\"debug_repository(repo) identif"
  },
  {
    "path": "config.sh.tmpl",
    "chars": 2228,
    "preview": "#!/usr/bin/env bash\n\n### Required settings ###\n\n# Project ID of your Cloud Robotics GCP project. This project can be cre"
  },
  {
    "path": "current_versions.txt",
    "chars": 126,
    "preview": "{\n  \"cert-manager\": \"1.16.3\",\n  \"ingress-nginx\": \"1.8.4\",\n  \"oauth2-proxy\": \"7.5.1\",\n  \"stackdriver-logging-agent\": \"1.9"
  },
  {
    "path": "deploy.sh",
    "chars": 14547,
    "preview": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "docs/.gitignore",
    "chars": 188,
    "preview": "# Files created when running Jekyll locally, following\n# https://help.github.com/en/articles/setting-up-your-github-page"
  },
  {
    "path": "docs/_config.yml",
    "chars": 26,
    "preview": "theme: jekyll-theme-slate\n"
  },
  {
    "path": "docs/concepts/app-management.md",
    "chars": 3741,
    "preview": "# App Management\n\nThe Cloud Robotics Core application management (Layer 2) makes it easy to define and deploy\narbitrary "
  },
  {
    "path": "docs/concepts/config.md",
    "chars": 917,
    "preview": "# Project configuration\n\nThe project configuration that one has entered during the initial setup is\nstored with the proj"
  },
  {
    "path": "docs/concepts/device_identity.md",
    "chars": 2095,
    "preview": "# Device Identity\n\nDevice Identity, part of Layer 1, provides an identity for robot clusters and\nservices to integrate t"
  },
  {
    "path": "docs/concepts/federation.md",
    "chars": 7315,
    "preview": "# Federation\n\nFederation, part of Layer 1, is responsible for synchronizing the state between robot and cloud\nclusters. "
  },
  {
    "path": "docs/developers/debug-auth.md",
    "chars": 4585,
    "preview": "# Debugging authentication problems\n\nUseful tips for working with Authentication and Authorization systems.\n\n## Run a sa"
  },
  {
    "path": "docs/how-to/connecting-robot.md",
    "chars": 2766,
    "preview": "# Connecting a robot to the cloud\n\nEstimated time: 10 min\n\nThis page describes how to connect a Kubernetes cluster on a "
  },
  {
    "path": "docs/how-to/creating-declarative-api.md",
    "chars": 20006,
    "preview": "# Creating a declarative API\n\n<!-- Estimated time: TODO -->\n\nIn this guide we will use a Kubernetes-style declarative AP"
  },
  {
    "path": "docs/how-to/deploy-from-sources.md",
    "chars": 4980,
    "preview": "# Deploy Cloud Robotics Core from sources\n\nEstimated time: 30 min\n\nThis page describes how to set up a Google Cloud Plat"
  },
  {
    "path": "docs/how-to/deploying-grpc-service.md",
    "chars": 10988,
    "preview": "# Deploying a gRPC service written in C++\n\nEstimated time: 60 min\n\nIn this guide we will deploy a gRPC service written i"
  },
  {
    "path": "docs/how-to/deploying-service.md",
    "chars": 14325,
    "preview": "# Deploying a service to the cloud cluster\n\nEstimated time: 60 min\n\nIn this guide we will write a HTTP service in Python"
  },
  {
    "path": "docs/how-to/examples/charge-service/Dockerfile",
    "chars": 92,
    "preview": "FROM python:alpine\n\nWORKDIR /data\n\nCOPY server.py ./\n\nCMD [ \"python\", \"-u\", \"./server.py\" ]\n"
  },
  {
    "path": "docs/how-to/examples/charge-service/charge-action.yaml",
    "chars": 81,
    "preview": "apiVersion: example.com/v1\nkind: ChargeAction\nmetadata:\n  name: my-charge-action\n"
  },
  {
    "path": "docs/how-to/examples/charge-service/charge-controller.yaml",
    "chars": 843,
    "preview": "apiVersion: metacontroller.k8s.io/v1alpha1\nkind: CompositeController\nmetadata:\n  name: charge-controller\nspec:\n  generat"
  },
  {
    "path": "docs/how-to/examples/charge-service/charge-crd.yaml",
    "chars": 838,
    "preview": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chargeactions.example.com\n  annotat"
  },
  {
    "path": "docs/how-to/examples/charge-service/server.py",
    "chars": 3714,
    "preview": "from http.server import BaseHTTPRequestHandler, HTTPServer\nimport json\nimport signal\nimport sys\nimport time\nimport uuid\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/Makefile",
    "chars": 1727,
    "preview": "#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you "
  },
  {
    "path": "docs/how-to/examples/greeter-service/client/Dockerfile",
    "chars": 188,
    "preview": "FROM grpc/cxx:1.12.0\n\nWORKDIR /data\n\nCOPY client/client.cc ./client/\nCOPY proto/helloworld.proto ./proto/\nCOPY Makefile "
  },
  {
    "path": "docs/how-to/examples/greeter-service/client/client.cc",
    "chars": 3500,
    "preview": "/*\n *\n * Copyright 2019 The Cloud Robotics Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");"
  },
  {
    "path": "docs/how-to/examples/greeter-service/deploy.sh",
    "chars": 1783,
    "preview": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "docs/how-to/examples/greeter-service/greeter-server.yaml.tmpl",
    "chars": 1598,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: greeter-server-ingress\n  annotations:\n    nginx.ingress"
  },
  {
    "path": "docs/how-to/examples/greeter-service/proto/helloworld.proto",
    "chars": 971,
    "preview": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// yo"
  },
  {
    "path": "docs/how-to/examples/greeter-service/server/Dockerfile",
    "chars": 188,
    "preview": "FROM grpc/cxx:1.12.0\n\nWORKDIR /data\n\nCOPY server/server.cc ./server/\nCOPY proto/helloworld.proto ./proto/\nCOPY Makefile "
  },
  {
    "path": "docs/how-to/examples/greeter-service/server/server.cc",
    "chars": 2472,
    "preview": "/*\n *\n * Copyright 2019 The Cloud Robotics Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");"
  },
  {
    "path": "docs/how-to/examples/hello-service/client/Dockerfile",
    "chars": 145,
    "preview": "FROM python:alpine\n\nRUN pip install --no-cache-dir google-auth requests\n\nWORKDIR /data\n\nCOPY client.py ./\n\nCMD [ \"python"
  },
  {
    "path": "docs/how-to/examples/hello-service/client/client.py",
    "chars": 349,
    "preview": "import google.auth\nimport google.auth.transport.requests as requests\n\ncredentials, project_id = google.auth.default()\n\na"
  },
  {
    "path": "docs/how-to/examples/hello-service/server/Dockerfile",
    "chars": 92,
    "preview": "FROM python:alpine\n\nWORKDIR /data\n\nCOPY server.py ./\n\nCMD [ \"python\", \"-u\", \"./server.py\" ]\n"
  },
  {
    "path": "docs/how-to/examples/hello-service/server/hello-server.yaml",
    "chars": 1278,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: hello-server-ingress\n  annotations:\n    nginx.ingress.k"
  },
  {
    "path": "docs/how-to/examples/hello-service/server/server.py",
    "chars": 584,
    "preview": "from http import server\nimport signal\nimport sys\n\n\nclass MyRequestHandler(server.BaseHTTPRequestHandler):\n  def do_GET(s"
  },
  {
    "path": "docs/how-to/running-ros-node.md",
    "chars": 3598,
    "preview": "# Running a ROS node as a Kubernetes deployment\n\nEstimated time: 10 min\n\nThe following instructions describe how to setu"
  },
  {
    "path": "docs/how-to/setting-up-oauth.md",
    "chars": 2781,
    "preview": "# Setting up OAuth for web UIs\n\nEstimated time: 5 min\n\nWhen a user loads a web UI hosted in the cloud Kubernetes cluster"
  },
  {
    "path": "docs/how-to/using-cloud-storage.md",
    "chars": 4564,
    "preview": "# Using Cloud Storage from a robot\n\nEstimated time: 20 minutes\n\nThis page describes a simple Cloud Storage transaction t"
  },
  {
    "path": "docs/index.md",
    "chars": 2324,
    "preview": "Google's Cloud Robotics Core is an open source platform that provides\ninfrastructure essential to building and running r"
  },
  {
    "path": "docs/overview.md",
    "chars": 4854,
    "preview": "# Overview of Cloud Robotics Core\n\nTo understand Cloud Robotics Core, you should be familiar with the following concepts"
  },
  {
    "path": "docs/quickstart.md",
    "chars": 5452,
    "preview": "# Quickstart\n\nEstimated time: 10 min\n\nThis page describes how to set up a Google Cloud Platform (GCP) project\ncontaining"
  },
  {
    "path": "new_versions.txt",
    "chars": 127,
    "preview": "{\n  \"cert-manager\": \"1.13.2\",\n  \"ingress-nginx\": \"1.9.4\",\n  \"oauth2-proxy\": \"7.5.1\",\n  \"stackdriver-logging-agent\": \"1.1"
  },
  {
    "path": "non_module_deps.bzl",
    "chars": 3291,
    "preview": "load(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n# -- load statements -- #\n\ndef _non_module_deps_imp"
  },
  {
    "path": "nvchecker.toml",
    "chars": 1211,
    "preview": "[__config__]\noldver = \"current_versions.txt\"\nnewver = \"new_versions.txt\"\n\n# containers\n# git grep -E \"^\\s+image: \" *.yam"
  },
  {
    "path": "scripts/BUILD.bazel",
    "chars": 99,
    "preview": "exports_files([\n    \"common.sh\",\n    \"config.sh\",\n    \"include-config.sh\",\n    \"set-config.sh\",\n])\n"
  },
  {
    "path": "scripts/backup_robots.sh",
    "chars": 1658,
    "preview": "#!/bin/bash\n#\n# Copyright 2021 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "scripts/check-images.sh",
    "chars": 2478,
    "preview": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "scripts/common.sh",
    "chars": 2682,
    "preview": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "scripts/config.sh",
    "chars": 1721,
    "preview": "#!/bin/bash\n#\n# Copyright 2024 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "scripts/include-config.sh",
    "chars": 2245,
    "preview": "#!/usr/bin/env bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "scripts/migrate.sh",
    "chars": 5431,
    "preview": "#!/bin/bash\n#\n# Copyright 2025 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "scripts/pre-commit",
    "chars": 5996,
    "preview": "#!/bin/bash\n# git hook to ensure code style\n# ln -sf ../../scripts/pre-commit .git/hooks/\n\n# shellcheck disable=2044,204"
  },
  {
    "path": "scripts/robot-sim.sh",
    "chars": 3494,
    "preview": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "scripts/set-config.sh",
    "chars": 12130,
    "preview": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "src/.gitignore",
    "chars": 8,
    "preview": ".gopath\n"
  },
  {
    "path": "src/BUILD.bazel",
    "chars": 40,
    "preview": "exports_files(\n    srcs = [\"go.mod\"],\n)\n"
  },
  {
    "path": "src/README.md",
    "chars": 2305,
    "preview": "# Source Code\n\nThis directory contains the source code for Cloud Robotics Core components. Most\ncomponents are written i"
  },
  {
    "path": "src/app_charts/BUILD.bazel",
    "chars": 920,
    "preview": "load(\"//bazel/build_rules/app_chart:cache_gcr_credentials.bzl\", \"cache_gcr_credentials\")\nload(\"//bazel/build_rules/app_c"
  },
  {
    "path": "src/app_charts/README.md",
    "chars": 478,
    "preview": "# Testing instructions\n\nUse `bazel run :push` on the app directory to build & upload Docker images,\nupload Helm charts a"
  },
  {
    "path": "src/app_charts/akri/BUILD.bazel",
    "chars": 801,
    "preview": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\nload(\"//bazel:build_rules/helm_template.bzl\", "
  },
  {
    "path": "src/app_charts/akri/akri-robot.values.yaml",
    "chars": 404,
    "preview": "# kubernetesDistro describes the Kubernetes distro Akri is running on. It is used to conditionally set\n# distribution sp"
  },
  {
    "path": "src/app_charts/akri/robot/akri.yaml",
    "chars": 334,
    "preview": "# This includes all resources expanded from the akri chart using\n# the values in ../values.yaml.\n# Some pseudo-variables"
  },
  {
    "path": "src/app_charts/akri/values-robot.yaml",
    "chars": 112,
    "preview": "domain: \"example.com\"\nproject: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\n\nudev:\n  rules: []\n"
  },
  {
    "path": "src/app_charts/base/BUILD.bazel",
    "chars": 3597,
    "preview": "load(\"@rules_shell//shell:sh_test.bzl\", \"sh_test\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\nload(\"//bazel:build_rules/"
  },
  {
    "path": "src/app_charts/base/README.md",
    "chars": 318,
    "preview": "# Making changes to the `fluent-bit` configmap\n\nIf you want to change the `fluent-bit` spec, do not edit the autogenerat"
  },
  {
    "path": "src/app_charts/base/app_management_test.sh",
    "chars": 2867,
    "preview": "#!/usr/bin/env bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/app_charts/base/cert-manager-cloud.values.yaml",
    "chars": 446,
    "preview": "# Configuration for the cert-manager chart.\n# Reference: https://github.com/jetstack/cert-manager/blob/master/deploy/cha"
  },
  {
    "path": "src/app_charts/base/cert-manager-google-cas-issuer-cloud.values.yaml",
    "chars": 899,
    "preview": "# Configuration for the cert-manager chart.\n# Reference: https://github.com/jetstack/google-cas-issuer/blob/main/deploy/"
  },
  {
    "path": "src/app_charts/base/cert-manager-robot.values.yaml",
    "chars": 867,
    "preview": "# Configuration for the cert-manager chart.\n# Reference: https://github.com/jetstack/cert-manager/blob/master/deploy/cha"
  },
  {
    "path": "src/app_charts/base/cloud/app-management-policy.yaml",
    "chars": 4249,
    "preview": "# This policy lets app-rollout and chart-assigment controllers operate on the\n# apps & registry CRDs. It also grants cha"
  },
  {
    "path": "src/app_charts/base/cloud/app-management.yaml",
    "chars": 6211,
    "preview": "{{ if eq .Values.app_management \"true\" }}\n# app-rollout-controller\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name"
  },
  {
    "path": "src/app_charts/base/cloud/apps-crd.yaml",
    "chars": 6965,
    "preview": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: apps.apps.cloudrobotics.com\n  annot"
  },
  {
    "path": "src/app_charts/base/cloud/cert-ingress.yaml",
    "chars": 331,
    "preview": "# Owns the 'tls' block to ensure a cert is generated and avoids repetition of \n# the 'tls' block in other ingresses. Cer"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager-certificates.yaml",
    "chars": 899,
    "preview": "apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: selfsigned-ca\nspec:\n  isCA: true\n  duration: 8760h  #"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager-google-cas-issuer.yaml",
    "chars": 438,
    "preview": "{{- if eq .Values.certificate_provider \"google-cas\" }}\n# This includes all resources expanded from the cert-manager char"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager-issuers.yaml",
    "chars": 1438,
    "preview": "# A self-signing issuer for cluster-internal services.\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  na"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager.yaml",
    "chars": 354,
    "preview": "# This includes all resources expanded from the cert-manager chart using\n# the values in ../cert-manager-cloud.values.ya"
  },
  {
    "path": "src/app_charts/base/cloud/cr-syncer-auth-webhook.yaml",
    "chars": 1709,
    "preview": "{{ if eq .Values.onprem_federation \"true\" }}\n# The cr-syncer-auth-webhook verifies that requests from the cr-syncer are\n"
  },
  {
    "path": "src/app_charts/base/cloud/cr-syncer-policy.yaml",
    "chars": 2147,
    "preview": "{{ if eq .Values.onprem_federation \"true\" }}\n# This policy lets the cr-syncer operate on the apps & registry CRDs.\napiVe"
  },
  {
    "path": "src/app_charts/base/cloud/domain-redirect.yaml",
    "chars": 628,
    "preview": "{{ $endpointsURL := print \"www.endpoints.\" .Values.project \".cloud.goog\" }}\n{{ if ne $endpointsURL .Values.domain }}\napi"
  },
  {
    "path": "src/app_charts/base/cloud/fluentd-metrics.yaml",
    "chars": 764,
    "preview": "# Adds a Prometheus ServiceMonitor for scraping the fluentd metrics.\n# By default, google-fluentd exports some Prometheu"
  },
  {
    "path": "src/app_charts/base/cloud/kubernetes-api.yaml",
    "chars": 835,
    "preview": "{{ if eq .Values.onprem_federation \"true\" }}\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: kubernetes"
  },
  {
    "path": "src/app_charts/base/cloud/namespace.yaml",
    "chars": 134,
    "preview": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .Release.Namespace }}\n  labels:\n    certmanager.k8s.io/disable-valid"
  },
  {
    "path": "src/app_charts/base/cloud/nginx-ingress-controller-policy.yaml",
    "chars": 3629,
    "preview": "# Source: ingress-nginx/templates/controller-serviceaccount.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: i"
  },
  {
    "path": "src/app_charts/base/cloud/nginx-ingress-controller.yaml",
    "chars": 5456,
    "preview": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nginx-ingress-controller\ndata:\n  # This is the same as the default but "
  },
  {
    "path": "src/app_charts/base/cloud/oauth2-proxy.yaml",
    "chars": 2553,
    "preview": "{{ if and (ne .Values.oauth2_proxy.client_id \"\") (ne .Values.oauth2_proxy.client_secret \"\") }}\napiVersion: apps/v1\nkind:"
  },
  {
    "path": "src/app_charts/base/cloud/registry-crd.yaml",
    "chars": 2793,
    "preview": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: robottypes.registry.cloudrobotics.c"
  },
  {
    "path": "src/app_charts/base/cloud/registry-policy.yaml",
    "chars": 694,
    "preview": "# This policy lets the human-acl GCP SA register robots. For context, see the\n# IAM policy in service-account.tf.\napiVer"
  },
  {
    "path": "src/app_charts/base/cloud/relay-dashboards.yaml",
    "chars": 188,
    "preview": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: relay-dashboards-json\n  labels:\n    grafana: \"1\"\ndata:\n  relay-dashboar"
  },
  {
    "path": "src/app_charts/base/cloud/token-vendor-app-fwd.yaml",
    "chars": 449,
    "preview": "# The Token Vendor was moved to the app namespace.\n# We create this service here in default namespace \n# as some codepat"
  },
  {
    "path": "src/app_charts/base/cloud/token-vendor-rollout.yaml",
    "chars": 170,
    "preview": "apiVersion: apps.cloudrobotics.com/v1alpha1\nkind: AppRollout\nmetadata:\n  name: token-vendor\n  labels:\n    app: token-ven"
  },
  {
    "path": "src/app_charts/base/fluent-bit-helm.sh",
    "chars": 1558,
    "preview": "#!/bin/bash\n# needs at least helm v3.5.0\nOUTPUT=./robot/fluent-bit.yaml\nTEMPLATE_VERSION=0.48.9\nhelm repo add fluent htt"
  },
  {
    "path": "src/app_charts/base/fluent-bit-values.yaml",
    "chars": 7563,
    "preview": "image:\n  pullPolicy: IfNotPresent\n\nenv:\n  - name: MY_NODE_NAME\n    valueFrom:\n      fieldRef:\n        fieldPath: spec.no"
  },
  {
    "path": "src/app_charts/base/relay-dashboard.json",
    "chars": 32382,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\","
  },
  {
    "path": "src/app_charts/base/robot/app-management.yaml",
    "chars": 3019,
    "preview": "{{ if eq .Values.app_management \"true\" }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chart-assignment-contro"
  },
  {
    "path": "src/app_charts/base/robot/cert-manager-certificates.yaml",
    "chars": 364,
    "preview": "apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: selfsigned-ca\nspec:\n  isCA: true\n  duration: 8760h # "
  },
  {
    "path": "src/app_charts/base/robot/cert-manager-issuers.yaml",
    "chars": 302,
    "preview": "# A self-signing issuer for cluster-internal services.\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  na"
  },
  {
    "path": "src/app_charts/base/robot/cert-manager.yaml",
    "chars": 367,
    "preview": "{{ if eq .Values.app_management \"true\" }}\n# This includes all resources expanded from the cert-manager chart using\n# the"
  },
  {
    "path": "src/app_charts/base/robot/cr-syncer.yaml",
    "chars": 1235,
    "preview": "{{ if eq .Values.cr_syncer \"true\" }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cr-syncer\nspec:\n  replicas: "
  },
  {
    "path": "src/app_charts/base/robot/fluent-bit.yaml",
    "chars": 13253,
    "preview": "# !!! DO NOT EDIT THIS FILE !!!\n# This file is autogenerated using src/app_charts/base/fluent-bit-helm.sh.\n# See src/app"
  },
  {
    "path": "src/app_charts/base/robot/fluentd-gcp-addon.yaml",
    "chars": 246,
    "preview": "{{ if and (eq .Values.robot_authentication \"true\") (eq .Values.fluentd \"true\") }}\n{{ .Files.Get \"files/fluentd-gcp-confi"
  },
  {
    "path": "src/app_charts/base/robot/fluentd-metrics.yaml",
    "chars": 806,
    "preview": "# Adds a Prometheus ServiceMonitor for scraping the fluentd metrics.\n# By default, google-fluentd exports some Prometheu"
  },
  {
    "path": "src/app_charts/base/robot/gcr-credential-refresher.yaml",
    "chars": 1249,
    "preview": "{{ if and (eq .Values.robot_authentication \"true\") (ne .Values.project \"\") (eq .Values.running_on_gke \"false\") }}\napiVer"
  },
  {
    "path": "src/app_charts/base/robot/metadata-server.yaml",
    "chars": 1564,
    "preview": "{{ if and (eq .Values.robot_authentication \"true\") (ne .Values.project \"\") }}\napiVersion: apps/v1\nkind: DaemonSet\nmetada"
  },
  {
    "path": "src/app_charts/base/values-cloud.yaml",
    "chars": 758,
    "preview": "domain: \"example.com\"\ningress_ip: \"\"\nproject: \"my-gcp-project\"\nregion: \"us-north1-a\"\ndeploy_environment: \"GCP\"\nregistry:"
  },
  {
    "path": "src/app_charts/base/values-robot.yaml",
    "chars": 1306,
    "preview": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\n\n# Setting a"
  },
  {
    "path": "src/app_charts/k8s-relay/BUILD.bazel",
    "chars": 569,
    "preview": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"k8s-relay-cloud\",\n    "
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/ingress.yaml",
    "chars": 1686,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: kubernetes-relay-client\n  annotations:\n    nginx.ingres"
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/kubernetes-relay-server.yaml",
    "chars": 1101,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: kubernetes-relay-server\nspec:\n  replicas: 1\n  selector:\n    match"
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/service-monitor.yaml",
    "chars": 529,
    "preview": "apiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: kubernetes-relay-server\n  labels:\n    promet"
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/service.yaml",
    "chars": 301,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: kubernetes-relay-server\n  labels:\n    # This is used by the ServiceMonito"
  },
  {
    "path": "src/app_charts/k8s-relay/robot/kubernetes-relay-client.yaml",
    "chars": 1181,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: kubernetes-relay-client\nspec:\n  replicas: 1\n  selector:\n    match"
  },
  {
    "path": "src/app_charts/k8s-relay/values-cloud.yaml",
    "chars": 324,
    "preview": "domain: \"example.com\"\nproject: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\n\n# MetricRelabelConfigs to "
  },
  {
    "path": "src/app_charts/k8s-relay/values-robot.yaml",
    "chars": 90,
    "preview": "kubernetes_relay_client:\n  resources:\n    requests:\n      memory: \"10Mi\"\n      cpu: \"50m\"\n"
  },
  {
    "path": "src/app_charts/mission-crd/BUILD.bazel",
    "chars": 468,
    "preview": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"mission-crd-robot\",\n  "
  },
  {
    "path": "src/app_charts/mission-crd/mission_crd.yaml",
    "chars": 4895,
    "preview": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cr-syncer.cloudrobotics."
  },
  {
    "path": "src/app_charts/mission-crd/values.yaml",
    "chars": 105,
    "preview": "project: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\n\nrobot:\n  name: \"\"\n\ncrd_spec_source: \"cloud\"\n"
  },
  {
    "path": "src/app_charts/platform-apps/BUILD.bazel",
    "chars": 234,
    "preview": "load(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"platform-apps-cloud\",\n    extra_templates = [\n       "
  },
  {
    "path": "src/app_charts/platform-apps/values.yaml",
    "chars": 108,
    "preview": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\n"
  },
  {
    "path": "src/app_charts/prometheus/BUILD.bazel",
    "chars": 1869,
    "preview": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\nload(\"//bazel:build_rules/helm_template.bzl\", "
  },
  {
    "path": "src/app_charts/prometheus/README.md",
    "chars": 858,
    "preview": "# Prometheus App\n\nThe Prometheus app uses the upstream Helm chart\n`prometheus-community/prometheus-operator` as a basis "
  },
  {
    "path": "src/app_charts/prometheus/cloud/app.yaml",
    "chars": 8205,
    "preview": "apiVersion: app.k8s.io/v1beta1\nkind: Application\nmetadata:\n  name: \"prometheus\"\n  labels:\n    app.kubernetes.io/name: {{"
  },
  {
    "path": "src/app_charts/prometheus/cloud/base-alerts.yaml",
    "chars": 839,
    "preview": "apiVersion: monitoring.coreos.com/v1\nkind: PrometheusRule\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name"
  },
  {
    "path": "src/app_charts/prometheus/cloud/federation-service-monitor.yaml",
    "chars": 1302,
    "preview": "{{ range .Values.robots }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: prometheus-federa"
  },
  {
    "path": "src/app_charts/prometheus/cloud/grafana-ingress.yaml",
    "chars": 928,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: grafana\n  labels:\n    app.kubernetes.io/name: {{ .Chart"
  },
  {
    "path": "src/app_charts/prometheus/cloud/prometheus-ingress.yaml",
    "chars": 831,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: prometheus\n  labels:\n    app.kubernetes.io/name: {{ .Ch"
  },
  {
    "path": "src/app_charts/prometheus/cloud/prometheus-operator.yaml",
    "chars": 3463,
    "preview": "# This includes all resources expanded from the prometheus-operator chart using\n# the values in ../prometheus-cloud.valu"
  },
  {
    "path": "src/app_charts/prometheus/cloud/prometheus-relay.yaml",
    "chars": 2705,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prometheus-relay-server\n  labels:\n    app.kubernetes.io/name: {{ "
  },
  {
    "path": "src/app_charts/prometheus/cloud/storage-class.yaml",
    "chars": 216,
    "preview": "apiVersion: storage.k8s.io/v1\nkind: StorageClass\nmetadata:\n  name: ssd\n  labels:\n    app.kubernetes.io/name: {{ .Chart.N"
  },
  {
    "path": "src/app_charts/prometheus/prometheus-cloud.values.yaml",
    "chars": 6551,
    "preview": "# Configuration for the prometheus-operator chart.\n# Reference: \n# https://github.com/prometheus-community/helm-charts/b"
  },
  {
    "path": "src/app_charts/prometheus/prometheus-robot.values.yaml",
    "chars": 7547,
    "preview": "# Configuration for the prometheus-operator chart.\n# Reference:\n# https://github.com/prometheus-community/helm-charts/bl"
  },
  {
    "path": "src/app_charts/prometheus/robot/hw-exporter.yaml",
    "chars": 1714,
    "preview": "apiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: hw-exporter\nspec:\n  selector:\n    matchLabels:\n      app: hw-expor"
  },
  {
    "path": "src/app_charts/prometheus/robot/prometheus-adapter.yaml",
    "chars": 9816,
    "preview": "apiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adap"
  },
  {
    "path": "src/app_charts/prometheus/robot/prometheus-operator.yaml",
    "chars": 425,
    "preview": "# This includes all resources expanded from the prometheus-operator chart using\n# the values in ../prometheus-cloud.valu"
  },
  {
    "path": "src/app_charts/prometheus/robot/prometheus-relay-client.yaml",
    "chars": 994,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prometheus-relay-client\nspec:\n  replicas: 1\n  selector:\n    match"
  },
  {
    "path": "src/app_charts/prometheus/robot/smartctl-exporter.yaml",
    "chars": 1149,
    "preview": "apiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: smartctl-exporter\nspec:\n  selector:\n    matchLabels:\n      app: sm"
  },
  {
    "path": "src/app_charts/prometheus/update_prometheus_adapter.sh",
    "chars": 539,
    "preview": "#!/bin/bash\n\nVERSION=\"0.12.0\"\nOUT=\"robot/prometheus-adapter.yaml\"\nwget https://github.com/kubernetes-sigs/prometheus-ada"
  },
  {
    "path": "src/app_charts/prometheus/values-cloud.yaml",
    "chars": 2896,
    "preview": "domain: \"example.com\"\nproject: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\n\n# The default requests/lim"
  },
  {
    "path": "src/app_charts/token-vendor/BUILD.bazel",
    "chars": 395,
    "preview": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"token-vendor-cloud\",\n "
  },
  {
    "path": "src/app_charts/token-vendor/cloud/dashboard.yaml",
    "chars": 178,
    "preview": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: tokenvendor-dashboards-json\n  labels:\n    grafana: \"1\"\ndata:\n  relay.js"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/ingress.yaml",
    "chars": 2082,
    "preview": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: public-key-access\n  annotations:\n    nginx.ingress.kube"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/service-monitor.yaml",
    "chars": 325,
    "preview": "apiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: token-vendor\n  labels:\n    prometheus: kube-"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/service.yaml",
    "chars": 275,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: token-vendor\n  labels:\n    # This is used by the ServiceMonitor.\n    app:"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/token-vendor-policy.yaml",
    "chars": 601,
    "preview": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: token-vendor\n  annotations:\n    iam.gke.io/gcp-service-account: \"t"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/token-vendor.yaml",
    "chars": 1900,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: token-vendor\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n   "
  },
  {
    "path": "src/app_charts/token-vendor/dashboard.json",
    "chars": 10593,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n  "
  },
  {
    "path": "src/bootstrap/cloud/BUILD.bazel",
    "chars": 1551,
    "preview": "load(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\ngenrule(\n    name = \"setup-robot-digest\",\n    srcs = [\"//src/go/cmd/setup-ro"
  },
  {
    "path": "src/bootstrap/cloud/INSTALL_FROM_BINARY",
    "chars": 119,
    "preview": "# When this marker file is present, the deploy.sh script installs the binary instead of building\n# from local sources.\n"
  },
  {
    "path": "src/bootstrap/cloud/run-install.sh",
    "chars": 2751,
    "preview": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licen"
  },
  {
    "path": "src/bootstrap/cloud/terraform/.gitignore",
    "chars": 85,
    "preview": ".terraform\nconfig.auto.tfvars\nterraform.tfvars\nbackend.tf\nterraform.tfstate.*.backup\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/BUILD.bazel",
    "chars": 195,
    "preview": "filegroup(\n    name = \"terraform\",\n    srcs = [\n        \"www.yaml\",\n    ] + glob(\n        include = [\"*.tf\"],\n        ex"
  },
  {
    "path": "src/bootstrap/cloud/terraform/README.md",
    "chars": 1160,
    "preview": "# Terraform\n\nThese files can be used to create, update or delete your personal development\nor shared projects.\n\nTo updat"
  }
]

// ... and 262 more files (download for full content)

About this extraction

This page contains the full source code of the googlecloudrobotics/core GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 462 files (3.5 MB), approximately 951.7k tokens, and a symbol index with 1320 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!