golang / protobuf

Go support for Google's protocol buffers
BSD 3-Clause "New" or "Revised" License
9.78k stars 1.58k forks source link

protobuf/types/known/timestamppb not recognized by gRPC module #1579

Open iisharankov opened 11 months ago

iisharankov commented 11 months ago

What version of protobuf and what language are you using?

Version: google.golang.org/protobuf v1.31.0

What did you do?

I am trying to use a timestamppb type in my proto file, but even though the file exists timestamppb.go exists locally in the correct location, and I can import it in my own projects, my package (uploaded to a local artifactory storage solution then downloaded as a user) does not see the timestamppb module.

evaluation.proto ```proto syntax = "proto3"; package evaluation.v1; import "google/protobuf/timestamp.proto"; option go_package = "evaluation/v1"; [...] message EvaluationContext { google.protobuf.Timestamp request_time = 1; // Time of the request } ```

The corresponding go.mod when running buf generate and the building in go looks like:

go.mod ```go module evaluation go 1.20 require ( google.golang.org/grpc v1.56.1 google.golang.org/protobuf v1.31.0 ) require ( github.com/golang/protobuf v1.5.3 // indirect golang.org/x/net v0.9.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect ) ```

Above is all in the repository where the protobuf contracts are defined, built, and deployed to Artifactory.

Next, I have a project that downloads from Artifactory the contract and uses it. It also has a go.mod that looks like:

go.mod ```go module openfeature-provider-golang go 1.21.3 require ( github.com/open-feature/go-sdk v1.8.0 google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.31.0 evaluation v1.0.0 ) require ( github.com/go-logr/logr v1.3.0 // indirect github.com/golang/protobuf v1.5.3 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect ) ```

And the main.go looks like

main.go ```go package main import ( "context" "fmt" "time" provider "subDirectory/provider" "github.com/open-feature/go-sdk/pkg/openfeature" "google.golang.org/protobuf/types/known/timestamppb" // Imported just as a test to see if it resolves. Here it does ) func main() { ctx := context.Background() serverURL := "localhost:8080" myProvider:= provider.MyProvider(serverURL , ctx) _ = timestamppb.Now() // Resolves and works. // [ CODE ] } ```

This repo uses the contract to send flag data to a server. Above, I've imported timestamppb, and it builds fine and is able to use timestamppb.Now() without issue. The problem is the provider module provider "subDirectory/provider" uses the evaluation.pb.go file from the protobuf contract, which in and of itself cannot resolve timestamppb for some reason. The provider.go file looks as follows:

provider.go ```go package provider import ( "context" "log" v1 "evaluation/evaluation/v1" "github.com/open-feature/go-sdk/pkg/openfeature" "google.golang.org/grpc" ) [...] ````

Here, v1 "evaluation/evaluation/v1" calls the generated proto files, where the following two lines exist at the start of evaluation.pb.go:

evaluation.pb.go ```go protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" ```

protoimpl imports without issue, but timestamppb fails, stating

$ go build
# evaluation/evaluation/v1
../../../../../go/pkg/mod/evaluation@v1.0.0/evaluation/v1/evaluation.pb.go:12:14: could not import google.golang.org/protobuf/types/known/timestamppb (open : no such file or directory)

This is in the evaluation.pb.go file, located at ~go/pkg/mod/evaluation@v1.0.0/evaluation/v1/evaluation.pb.go. It fails to find timestamppb located at :~/go/pkg/mod/google.golang.org/protobuf@v1.31.0/types/known/timestamppb/, even though in my workspace directory where the project i (the above main.go, the exact same import, timestamppb "google.golang.org/protobuf/types/known/timestamppb" is used and correctly resolves the package.

I've lost days to trying to understand where the issue is. Why does my project see google.golang.org/protobuf/types/known/timestamppb, but the protobuf generated file does not see it the same path?

Anything else we should know about your project / environment?

Have two go envs that matter, since my project has set GOPROXY

` go env` in my project ```sh GO111MODULE='' GOARCH='amd64' GOBIN='' GOCACHE='/home/MYUSER/.cache/go-build' GOENV='/home/MYUSER/.config/go/env' GOEXE='' GOEXPERIMENT='' GOFLAGS='' GOHOSTARCH='amd64' GOHOSTOS='linux' GOINSECURE='' GOMODCACHE='/home/MYUSER/go/pkg/mod' GONOPROXY='' GONOSUMDB='REDACTED.com' GOOS='linux' GOPATH='/home/MYUSER/go' GOPRIVATE='' GOPROXY='https://USERNAME:PASSWORD@artifactory.REDACTED.ca/artifactory/api/go/featureflag-go-local' GOROOT='/usr/local/go' GOSUMDB='sum.golang.org' GOTMPDIR='' GOTOOLCHAIN='auto' GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64' GOVCS='' GOVERSION='go1.21.3' GCCGO='gccgo' GOAMD64='v1' AR='ar' CC='gcc' CXX='g++' CGO_ENABLED='1' GOMOD='/home/MYUSER/Workspace/github/PROJECT/go.mod' GOWORK='' CGO_CFLAGS='-O2 -g' CGO_CPPFLAGS='' CGO_CXXFLAGS='-O2 -g' CGO_FFLAGS='-O2 -g' CGO_LDFLAGS='-O2 -g' PKG_CONFIG='pkg-config' GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3981534992=/tmp/go-build -gno-record-gcc-switches' ```

Yes, I had to change the proxy to collect from the local artifactory solution. The go.env in the protobuf file location (~go/pkg/mod/evaluation@v1.0.0/evaluation/v1/evaluation.pb.go) on the other hand is:

`go env` at protobuf repo ```sh GO111MODULE='' GOARCH='amd64' GOBIN='' GOCACHE='/home/MYUSER/.cache/go-build' GOENV='/home/MYUSER/.config/go/env' GOEXE='' GOEXPERIMENT='' GOFLAGS='' GOHOSTARCH='amd64' GOHOSTOS='linux' GOINSECURE='' GOMODCACHE='/home/MYUSER/go/pkg/mod' GONOPROXY='' GONOSUMDB='' GOOS='linux' GOPATH='/home/MYUSER/go' GOPRIVATE='' GOPROXY='https://proxy.golang.org,direct' GOROOT='/usr/local/go' GOSUMDB='sum.golang.org' GOTMPDIR='' GOTOOLCHAIN='auto' GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64' GOVCS='' GOVERSION='go1.21.3' GCCGO='gccgo' GOAMD64='v1' AR='ar' CC='gcc' CXX='g++' CGO_ENABLED='1' GOMOD='/home/MYUSER/go/pkg/mod/google.golang.org/protobuf@v1.31.0/go.mod' GOWORK='' CGO_CFLAGS='-O2 -g' CGO_CPPFLAGS='' CGO_CXXFLAGS='-O2 -g' CGO_FFLAGS='-O2 -g' CGO_LDFLAGS='-O2 -g' PKG_CONFIG='pkg-config' GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1863516080=/tmp/go-build -gno-record-gcc-switches' ```
puellanivis commented 11 months ago

I’m confused a lot by these examples. You seem to be using relative imports (which I have not used at all since we started using go modules), but also you seem to have two separate go.mod files?

iisharankov commented 11 months ago

I have a repository where I define the .proto contract, and generate it into Golang among other languages. When pushing these changes, Jenkins builds the golang repo and generates the go.mod you see. So the first block (the contract) and the first go.mod are in a repo where the generated proto files. This I thought was useful as it shows that google.golang.org/protobuf v1.31.0 is a direct dependency and github.com/golang/protobuf v1.5.3 // indirect as an indirect dependency.

The next go.mod file is for a repo that downloads the latest contract version from artifactory, and uses it to actually serialize and send the data. For debugging, I imported "google.golang.org/protobuf/types/known/timestamppb" to see if it is resolved, and it is. provider "subDirectory/provider" is a relative import because it's a package in my repository. It is this package (shown in the next code block, package provider), that imports the contract (line v1 "evaluation/evaluation/v1"), which is at go/pkg/mod/evaluation@v1.0.0/evaluation/v1/evaluation.pb.go. Here the import line timestamppb "google.golang.org/protobuf/types/known/timestamppb" exists, which was auto generated by protobuf, and triggers the error seen in the go build step, stating timestamppb was not resolved since the directory/file was not there, even though it is.

There was a bit of clean up to post this, as well as the error being so strange (lost a few days to it pulling hairs) that it's hard to explain. But put in a sentence: Why is the protobuf auto generated timestamppb "google.golang.org/protobuf/types/known/timestamppb" import failing as unresolved when importing the golang package the contract files are in, even though every other project can resolve timestamppb "google.golang.org/protobuf/types/known/timestamppb" safely. Stranger yet, protoimpl "google.golang.org/protobuf/runtime/protoimpl" is respoved, meaning runetime/protoimpl is discovered, but types/known/timestamppb is not in the protobuf package.

puellanivis commented 11 months ago

Is this the complete and total output of the compilation error:

$ go build
# evaluation/evaluation/v1
../../../../../go/pkg/mod/evaluation@v1.0.0/evaluation/v1/evaluation.pb.go:12:14: could not import google.golang.org/protobuf/types/known/timestamppb (open : no such file or directory)

This message oddly suggest something is wrong with your evaluation.pb.go imports and this has nothing to do with timestamppb itself, could you post the first 20-ish or so lines?

iisharankov commented 11 months ago

I didn't catch anything odd with the import line there, but sure thing!

evaluation_grpc.pb.go ```go // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 // - protoc (unknown) // source: evaluation/v1/evaluation.proto package v1 import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 ```
evaluation.pb.go ```go // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.31.0 // protoc (unknown) // source: evaluation/v1/evaluation.proto package v1 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) ```
puellanivis commented 11 months ago

And can you confirm that this was the full output of compilation error?

iisharankov commented 11 months ago

Yes. In my personal project running go build gives exactly

$ go build
# evaluation/evaluation/v1
../../../go/pkg/mod/evaluation@v1.0.0/evaluation/v1/evaluation.pb.go:12:14: could not import google.golang.org/protobuf/types/known/timestamppb (open : no such file or directory)

With no other errors

puellanivis commented 11 months ago

Hm… this is just so confusing. I’m wondering if it’s possible for you to create a minimal repro, which we would be able to check out and poke around with?

iisharankov commented 11 months ago

Sorry for the long delay. Needed some approval to upload things and had to clean the repo, and since then got quite sick for a bit. That time of year. I also worried this was an internal proxy issue and was wasting your time, but I cannot explain how that could be part of it anymore after another two weeks of wrangling with this.

Here's a link to a cleaned up repo of the feature flag system I'm building. Below is a simplified proto file that creates the same issue

evaluation.proto ```proto syntax = "proto3"; package evaluation.v1; import "google/protobuf/timestamp.proto"; option go_package = "flag-management-system/evaluation/v1"; service FlagManagementSystemService { rpc BoolEvaluation(BoolEvaluationRequest) returns (BoolEvaluationResponse) {} } // // // // // Base Response // // // // // // Response status structure message ResponseStatus { bool success = 1; ErrorMessage error = 2; } // Error message structure message ErrorMessage { string code = 1; string message = 2; } // // // // // Base Flag Evaluation // // // // // // Extended evaluation request to include more types message EvaluationRequest { string flag_path = 1; EvaluationContext context = 10; // Possible addition of evaluation context? } // Response structure for different types of feature flags message EvaluationResponse { string flag_path = 1; ResponseStatus status = 5; } // // // // // Bool Flag Evaluation // // // // // // Boolean flags request message BoolEvaluationRequest { uint32 batch_size = 1; repeated EvaluationRequest requests = 10; } // Single Boolean flag response message BoolSingleFlagResponse { bool value = 1; EvaluationResponse metadata = 5; } // Boolean flags response message BoolEvaluationResponse { uint32 response_size = 1; repeated BoolSingleFlagResponse results = 10; } // // // // // Context Evaluation // // // // // // Provider Context message EvaluationContext { // Provider-specific information string provider_id = 1; string provider_has = 2; // Environmental and temporal data google.protobuf.Timestamp request_time = 3; string request_time_zone = 4; string provider_language = 5; string provider_version = 6; } ```

And the relevant part of the buf.gen.yaml

buf.gen.yaml ```yaml version: v1 plugins: # Go - plugin: buf.build/protocolbuffers/go out: gen/go opt: - paths=source_relative - plugin: buf.build/grpc/go out: gen/go opt: - paths=source_relative ```

As said before, when this is generated with buf generate, I then go build and deploy via Jenkins to Artifactory (not included) so it can be downloaded and sourced by users. The repo shared has a provider that should download the protobuf files from artifactory (note the modification to GONOSUMDB). For a while I've worried this is an issue on the internal proxy and artifactory and not with protobuf, but I've continued going in circles for days, and can't understand why runetime/protoimpl is discovered, but types/known/timestamppb is not, as mentioned before. Let me know if there's anything more I can provide.

puellanivis commented 11 months ago

I checked out your repo, but I get an issue with REDACTED.com/evaluation@v1.0.0: malformed module path "REDACTED.com/evaluation": invalid char 'R' in first path element"

Removing this line from the go.mod I get main.go:8:2: package openfeature-provider-golang/provider is not in std (/usr/local/go/src/openfeature-provider-golang/provider