blob: 49aa57128333060ac6728067a4d33e5ca41eb406 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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.
package bufpluginexec
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"path/filepath"
"strings"
)
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.uber.org/multierr"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/pluginpb"
)
import (
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/app"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/app/appproto"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/command"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/ioextended"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/protoencoding"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/storage"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/storage/storageos"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/tmp"
)
type protocProxyHandler struct {
storageosProvider storageos.Provider
runner command.Runner
protocPath string
pluginName string
tracer trace.Tracer
}
func newProtocProxyHandler(
storageosProvider storageos.Provider,
runner command.Runner,
protocPath string,
pluginName string,
) *protocProxyHandler {
return &protocProxyHandler{
storageosProvider: storageosProvider,
runner: runner,
protocPath: protocPath,
pluginName: pluginName,
tracer: otel.GetTracerProvider().Tracer("bufbuild/buf"),
}
}
func (h *protocProxyHandler) Handle(
ctx context.Context,
container app.EnvStderrContainer,
responseWriter appproto.ResponseBuilder,
request *pluginpb.CodeGeneratorRequest,
) (retErr error) {
ctx, span := h.tracer.Start(ctx, "protoc_proxy", trace.WithAttributes(
attribute.Key("plugin").String(filepath.Base(h.pluginName)),
))
defer span.End()
defer func() {
if retErr != nil {
span.RecordError(retErr)
span.SetStatus(codes.Error, retErr.Error())
}
}()
protocVersion, err := h.getProtocVersion(ctx, container)
if err != nil {
return err
}
if h.pluginName == "kotlin" && !getKotlinSupportedAsBuiltin(protocVersion) {
return fmt.Errorf("kotlin is not supported for protoc version %s", versionString(protocVersion))
}
// When we create protocProxyHandlers in NewHandler, we always prefer protoc-gen-.* plugins
// over builtin plugins, so we only get here if we did not find protoc-gen-js, so this
// is an error
if h.pluginName == "js" && !getJSSupportedAsBuiltin(protocVersion) {
return errors.New("js moved to a separate plugin hosted at https://github.com/protocolbuffers/protobuf-javascript in v21, you must install this plugin")
}
fileDescriptorSet := &descriptorpb.FileDescriptorSet{
File: request.ProtoFile,
}
fileDescriptorSetData, err := protoencoding.NewWireMarshaler().Marshal(fileDescriptorSet)
if err != nil {
return err
}
descriptorFilePath := app.DevStdinFilePath
var tmpFile tmp.File
if descriptorFilePath == "" {
// since we have no stdin file (i.e. Windows), we're going to have to use a temporary file
tmpFile, err = tmp.NewFileWithData(fileDescriptorSetData)
if err != nil {
return err
}
defer func() {
retErr = multierr.Append(retErr, tmpFile.Close())
}()
descriptorFilePath = tmpFile.AbsPath()
}
tmpDir, err := tmp.NewDir()
if err != nil {
return err
}
defer func() {
retErr = multierr.Append(retErr, tmpDir.Close())
}()
args := []string{
fmt.Sprintf("--descriptor_set_in=%s", descriptorFilePath),
fmt.Sprintf("--%s_out=%s", h.pluginName, tmpDir.AbsPath()),
}
if getSetExperimentalAllowProto3OptionalFlag(protocVersion) {
args = append(
args,
"--experimental_allow_proto3_optional",
)
}
if parameter := request.GetParameter(); parameter != "" {
args = append(
args,
fmt.Sprintf("--%s_opt=%s", h.pluginName, parameter),
)
}
args = append(
args,
request.FileToGenerate...,
)
stdin := ioextended.DiscardReader
if descriptorFilePath != "" && descriptorFilePath == app.DevStdinFilePath {
stdin = bytes.NewReader(fileDescriptorSetData)
}
if err := h.runner.Run(
ctx,
h.protocPath,
command.RunWithArgs(args...),
command.RunWithEnv(app.EnvironMap(container)),
command.RunWithStdin(stdin),
command.RunWithStderr(container.Stderr()),
); err != nil {
// TODO: strip binary path as well?
// We don't know if this is a system error or plugin error, so we assume system error
return handlePotentialTooManyFilesError(err)
}
if getFeatureProto3OptionalSupported(protocVersion) {
responseWriter.SetFeatureProto3Optional()
}
// no need for symlinks here, and don't want to support
readWriteBucket, err := h.storageosProvider.NewReadWriteBucket(tmpDir.AbsPath())
if err != nil {
return err
}
return storage.WalkReadObjects(
ctx,
readWriteBucket,
"",
func(readObject storage.ReadObject) error {
data, err := io.ReadAll(readObject)
if err != nil {
return err
}
return responseWriter.AddFile(
&pluginpb.CodeGeneratorResponse_File{
Name: proto.String(readObject.Path()),
Content: proto.String(string(data)),
},
)
},
)
}
func (h *protocProxyHandler) getProtocVersion(
ctx context.Context,
container app.EnvContainer,
) (*pluginpb.Version, error) {
stdoutBuffer := bytes.NewBuffer(nil)
if err := h.runner.Run(
ctx,
h.protocPath,
command.RunWithArgs("--version"),
command.RunWithEnv(app.EnvironMap(container)),
command.RunWithStdout(stdoutBuffer),
); err != nil {
// TODO: strip binary path as well?
return nil, handlePotentialTooManyFilesError(err)
}
return parseVersionForCLIVersion(strings.TrimSpace(stdoutBuffer.String()))
}