Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This team will own the entire repository
* @qdrant/cloud-unit-platform
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"

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

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
20 changes: 20 additions & 0 deletions .github/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'feat'
- 'enhancement'
- 'enh'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
labels:
- 'chore'
- 'dependencies'
template: |
# What’s Changed

$CHANGES
19 changes: 19 additions & 0 deletions .github/workflows/label-enforcer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Enforce Labels

on:
pull_request:
types: [opened, edited, labeled, unlabeled, synchronize]

permissions:
contents: read

jobs:
enforce-label:
permissions:
contents: read # for TimonVS/pr-labeler-action to read config file
pull-requests: write # for TimonVS/pr-labeler-action to add labels in PR
runs-on: ubuntu-latest
steps:
- uses: TimonVS/pr-labeler-action@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
61 changes: 61 additions & 0 deletions .github/workflows/pr-workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: PR Workflow
on:
pull_request:
types: [synchronize, opened, reopened]
branches: ["main"]
env:
GOPRIVATE: github.com/qdrant/qdrant-cloud-public-api

permissions:
contents: read

jobs:
linter:
name: Linter
runs-on: ubuntu-latest
timeout-minutes: 10 # Sets a timeout of 10 minutes for this job (default is 1 minute)
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup access for private go modules
run: |
git config --global url.'https://${{ secrets.GH_REPO_READ_TOKEN }}@github.com'.insteadOf 'https://github.com'

- uses: actions/setup-go@v5
with:
go-version: "^1.24"
cache: false

- name: Check Go Formatting
run: |
files=$(gofmt -l .) && echo $files && [ -z "$files" ]

- name: Golang CI Lint
uses: golangci/golangci-lint-action@v7
with:
version: v2.0.1
args: --timeout 10m

unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup access for private go modules
run: |
git config --global url.'https://${{ secrets.GH_REPO_READ_TOKEN }}@github.com'.insteadOf 'https://github.com'

- uses: actions/setup-go@v5
with:
go-version: "^1.24"

- name: Unit tests
run: |
make test_unit
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ go.work.sum

# env file
.env

.DS_Store
.idea
*.log
tmp/
bin/
72 changes: 72 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9\/-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

##@ Development

.PHONY: fmt
fmt: ## Run go fmt and gci against code.
go fmt ./...
$(GCI) write ./ --skip-generated -s standard -s default -s 'prefix(github.com/qdrant)' -s 'prefix(github.com/qdrant/qdrant-cloud-buf-plugins/)'

.PHONY: vet
vet: ## Run go vet (static analysis tool) against code.
go vet ./...

.PHONY: lint
lint: bootstrap ## Run project linters
$(GOLANGCI_LINT) run

.PHONY: test
test: fmt vet test_unit ## Run all tests.

.PHONY: test_unit
test_unit: ## Run unit tests.
echo "Running Go unit tests..."
go test ./... -coverprofile cover.out -v

##@ Dependencies

## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)

## Tool Binaries
GO ?= go
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
GCI = $(LOCALBIN)/gci

## Tool Versions
GOLANGCI_LINT_VERSION ?= v2.0.1
GCI_VERSION ?= v0.13.5

.PHONY: bootstrap
bootstrap: install/gci install/golangci-lint ## Install required dependencies to work with this project

.PHONY: golangci-lint
install/golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))

.PHONY: install/gci
install/gci: $(GCI) ## Download gci locally if necessary.
$(GCI): $(LOCALBIN)
$(call go-install-tool,$(GCI),github.com/daixiang0/gci,$(GCI_VERSION))

# copied from kube-builder
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f "$(1)-$(3)" ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
rm -f $(1) || true ;\
GOBIN=$(LOCALBIN) go install $${package} ;\
mv $(1) $(1)-$(3) ;\
} ;\
ln -sf $(1)-$(3) $(1)
endef
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# qdrant-cloud-buf-plugins

Collection of [Buf plugins](https://buf.build/docs/cli/buf-plugins/overview/) used by Qdrant Cloud APIs.

## Development

This project leverages Make to automate common development tasks. To view all available commands, run:

``` sh
make help
```

### Setup

To work with this project locally, you need to have [Go](https://go.dev/doc/install) installed.
Additionally, there are other required dependencies that you can install running:

``` sh
make bootstrap
```

### Running tests

To run the tests, execute:

``` sh
make test
```

### Formatting & linting code

To format and lint the code of the project, execute:

``` sh
make fmt
make lint
```
75 changes: 75 additions & 0 deletions cmd/buf-plugin-method-options/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Package main implements a plugin that checks that all rpc methods set the
// required options (permissions, http).
//
// To use this plugin:
//
// # buf.yaml
// version: v2
// lint:
// use:
// - STANDARD # omit if you do not want to use the rules builtin to buf
// - QDRANT_CLOUD_METHOD_OPTIONS
// plugins:
// - plugin: buf-plugin-method-options
package main

import (
"context"

"buf.build/go/bufplugin/check"
"buf.build/go/bufplugin/check/checkutil"
"buf.build/go/bufplugin/info"
googleann "google.golang.org/genproto/googleapis/api/annotations"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"

commonv1 "github.com/qdrant/qdrant-cloud-public-api/gen/go/qdrant/cloud/common/v1"
)

const (
methodOptionsRuleID = "QDRANT_CLOUD_METHOD_OPTIONS"
)

var (
methodOptionsRuleSpec = &check.RuleSpec{
ID: methodOptionsRuleID,
Default: true,
Purpose: `Checks that all rpc methods define a set of required options.`,
Type: check.RuleTypeLint,
Handler: checkutil.NewMethodRuleHandler(checkMethodOptions, checkutil.WithoutImports()),
}
spec = &check.Spec{
Rules: []*check.RuleSpec{
methodOptionsRuleSpec,
},
Info: &info.Spec{
Documentation: `A plugin that checks that all rpc methods define a set of required options.`,
SPDXLicenseID: "",
LicenseURL: "",
},
}
requiredMethodOptionExtensions = []*protoimpl.ExtensionInfo{
commonv1.E_Permissions,
googleann.E_Http,
}
)

func main() {
check.Main(spec)
}

func checkMethodOptions(ctx context.Context, responseWriter check.ResponseWriter, request check.Request, methodDescriptor protoreflect.MethodDescriptor) error {
options := methodDescriptor.Options()

for _, extension := range requiredMethodOptionExtensions {
if !proto.HasExtension(options, extension) {
responseWriter.AddAnnotation(
check.WithMessagef("Method %q does not define the %q option", methodDescriptor.FullName(), extension.TypeDescriptor().FullName()),
check.WithDescriptor(methodDescriptor),
)
}
}

return nil
}
64 changes: 64 additions & 0 deletions cmd/buf-plugin-method-options/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"testing"

"buf.build/go/bufplugin/check/checktest"
)

func TestSpec(t *testing.T) {
t.Parallel()
checktest.SpecTest(t, spec)
}

func TestSimpleSuccess(t *testing.T) {
t.Parallel()

checktest.CheckTest{
Request: &checktest.RequestSpec{
Files: &checktest.ProtoFileSpec{
DirPaths: []string{"testdata/simple_success"},
FilePaths: []string{"simple.proto"},
},
},
Spec: spec,
}.Run(t)
}

func TestSimpleFailure(t *testing.T) {
t.Parallel()

checktest.CheckTest{
Request: &checktest.RequestSpec{
Files: &checktest.ProtoFileSpec{
DirPaths: []string{"testdata/simple_failure"},
FilePaths: []string{"simple.proto"},
},
},
Spec: spec,
ExpectedAnnotations: []checktest.ExpectedAnnotation{
{
RuleID: methodOptionsRuleID,
Message: "Method \"simple.GreeterService.HelloWorld\" does not define the \"google.api.http\" option",
FileLocation: &checktest.ExpectedFileLocation{
FileName: "simple.proto",
StartLine: 9,
StartColumn: 4,
EndLine: 12,
EndColumn: 5,
},
},
{
RuleID: methodOptionsRuleID,
Message: "Method \"simple.GreeterService.HelloWorld\" does not define the \"qdrant.cloud.common.v1.permissions\" option",
FileLocation: &checktest.ExpectedFileLocation{
FileName: "simple.proto",
StartLine: 9,
StartColumn: 4,
EndLine: 12,
EndColumn: 5,
},
},
},
}.Run(t)
}
13 changes: 13 additions & 0 deletions cmd/buf-plugin-method-options/testdata/common.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
syntax = "proto3";

// As a commodity, we re-define it here to avoid relying on the real dependency.

package qdrant.cloud.common.v1;

import "google/protobuf/descriptor.proto";

// The extension for adding permissions to the system
extend google.protobuf.MethodOptions {
// A list of permissions which ALL need to be met by the current user.
repeated string permissions = 50001;
}
Loading
Loading