Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added base repo scaffolding #2

Merged
merged 4 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
23 changes: 23 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
; https://editorconfig.org/

root = true

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

[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_size = 4
indent_style = tab

[*.md]
indent_size = 4
trim_trailing_whitespace = false

eclint_indent_style = unset

[Dockerfile]
indent_size = 4
92 changes: 92 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
run:
# Timeout for analysis, e.g. 30s, 5m.
timeout: 5m

# Include test files.
tests: true

issues:
# Set to 0 to not skip any issues.
max-issues-per-linter: 0

# Set to 0 to not skip any issues.
max-same-issues: 0

output:
# Sort results by: filepath, then line, then column.
sort-results: true

# Make issues output unique by line.
uniq-by-line: false

linters:
# Enable specific linter
enable:
# Detect context.Context contained in structs.
- containedctx
# Check whether a function uses a non-inherited context.
- contextcheck
# Find declarations and assignments with too many blank identifiers.
- dogsled
# Check for unchecked errors.
- errcheck
# Find code that will cause problems with the error wrapping scheme.
- errorlint
# Find exporting pointers for loop variables.
- exportloopref
# Inspects source code for security problems.
- gosec
# Check that compiler directives are valid.
- gocheckcompilerdirectives
# Calculate cognitive complexities of functions.
- gocognit
# Find repeated strings that could be replaced by a constant.
- goconst
# Provides functionalities missing from other linters.
- gocritic
# Calculates cyclomatic complexity of a function.
- gocyclo
# Check if comments end with a dot.
- godot
# A stricter replacement for gofmt.
- gofumpt
# GO Magic Number Detector.
- gomnd
# Simplify the code.
- gosimple
# Check for correctness of programs.
- govet
# Detect ineffectual assignments.
- ineffassign
# Correct commonly misspelled English words in source files.
- misspell
# Finds the code that returns nil even if it checks that the error is not nil.
- nilerr
# Checks that there is no simultaneous return of nil error and an invalid value.
- nilnil
# Find incorrect usages of t.Parallel().
- paralleltest
# Reports direct reads from proto message fields when getters should be used.
- protogetter
# Drop-in replacement of golint.
- revive
# Ensure consistent code style when using log/slog.
- sloglint
# Find bugs and performance issues statically.
- staticcheck
# Checks Go code for unused constants, variables, functions and types.
- unused
# Empty lines linter.
- wsl

# Setting of specific linters.
linters-settings:
paralleltest:
# Ignore missing calls to `t.Parallel()` and only report incorrect uses of it.
ignore-missing: false

sloglint:
# Enforce using key-value pairs only (incompatible with attr-only).
kv-only: true
# Enforce a single key naming convention.
key-naming-case: snake
75 changes: 75 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright 2023 Linode, LLC
shanduur marked this conversation as resolved.
Show resolved Hide resolved
#
# 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.

SHELL := /usr/bin/env bash -o errexit -o pipefail -o nounset
.DEFAULT_GOAL := help

GO ?= go
ENGINE ?= docker

VERSION ?= $(shell git rev-parse HEAD)
TOOLCHAIN_VERSION := $(shell sed -En 's/^go (.*)$$/\1/p' go.mod)

REGISTRY := docker.io
IMAGE := linode/linode-cosi-driver

CONTAINERFILE ?= Dockerfile
OCI_TAGS += --tag=${REGISTRY}/${IMAGE}:${VERSION}
OCI_BUILDARGS += --build-arg=VERSION=${VERSION}

LDFLAGS ?=
GOFLAGS ?=
GO_SETTINGS += CGO_ENABLED=0

.PHONY: all
all: test build image # Run all targets.

.PHONY: build
build: clean # Build the binary.
${GO_SETTINGS} ${GO} build \
${GOFLAGS} \
-ldflags="${LDFLAGS}" \
-o ./bin/${NAME} \
./cmd/linode-cosi-driver

.PHONY: image
image: clean-image # Build container image.
${ENGINE} build \
${OCI_TAGS} ${OCI_BUILDARGS} \
--file=${CONTAINERFILE} \
--target=runtime \
.

.PHONY: test
test: # Run unit tests.
${GO} test ${GOFLAGS} \
-race \
-cover -covermode=atomic -coverprofile=coverage.out \
./...

.PHONY: e2e
e2e: # Run end to end tests. (FIXME: this is placeholder)
@-echo "this is placeholder"

.PHONY: clean
clean: # Clean the previous build files.
@rm -rf ./bin

.PHONY: clean-image
clean-image: # Attempt to remove the old container image builds.
@-${ENGINE} image rm -f $(shell ${ENGINE} image ls -aq ${REGISTRY}/${REPOSITORY}/${NAME}:${VERSION} | xargs -n1 | sort -u | xargs)

.PHONY: help
help: # Show help for each of the Makefile recipes.
@grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done
164 changes: 164 additions & 0 deletions cmd/linode-cosi-driver/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright 2023 Linode, LLC
//
// 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.

package main

import (
"context"
"errors"
"fmt"
"log/slog"
"net/url"
"os"
"os/signal"
"sync"
"syscall"
"time"

"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
"github.com/linode/linode-cosi-driver/internal/endpoint"
"github.com/linode/linode-cosi-driver/internal/envflag"
"github.com/linode/linode-cosi-driver/internal/grpc/handlers"
"github.com/linode/linode-cosi-driver/internal/grpc/logger"
"github.com/linode/linode-cosi-driver/pkg/servers/identity"
"github.com/linode/linode-cosi-driver/pkg/servers/provisioner"
"google.golang.org/grpc"
cosi "sigs.k8s.io/container-object-storage-interface-spec"
)

var log *slog.Logger

const (
driverName = "objectstorage.cosi.linode.com"
gracePeriod = 5 * time.Second
)

func main() {
linodeToken := envflag.String("LINODE_TOKEN", "")
linodeURL := envflag.String("LINODE_API_URL", "")
cosiEndpoint := envflag.String("COSI_ENDPOINT", "unix:///var/lib/cosi/cosi.sock")

// TODO: any logger settup must be done here, before first log call.
log = slog.Default()

if err := realMain(context.Background(), cosiEndpoint, linodeToken, linodeURL); err != nil {
slog.Error("critical failure", "error", err)
os.Exit(1)
}
}

func realMain(ctx context.Context, cosiEndpoint, _, _ string) error {
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
defer stop()

// create identity server
idSrv, err := identity.New(driverName)
if err != nil {
return fmt.Errorf("failed to create identity server: %w", err)
}

// create provisioner server
prvSrv, err := provisioner.New(log)
if err != nil {
return fmt.Errorf("failed to create provisioner server: %w", err)
}

// parse endpoint
endpointURL, err := url.Parse(cosiEndpoint)
if err != nil {
return fmt.Errorf("unable to parse COSI endpoint: %w", err)
}

// create the endpoint handler
lis, err := endpoint.New(endpointURL).Listener(ctx)
if err != nil {
return fmt.Errorf("unable to create new listener: %w", err)
}
defer lis.Close()

// create the grpcServer
srv, err := grpcServer(ctx, idSrv, prvSrv)
if err != nil {
return fmt.Errorf("gRPC server creation failed: %w", err)
}

var wg sync.WaitGroup

wg.Add(1)

go shutdown(ctx, &wg, srv)

slog.Info("starting server", "endpoint", endpointURL)

err = srv.Serve(lis)
if err != nil {
return fmt.Errorf("gRPC server failed: %w", err)
}

wg.Wait()

return nil
}

func grpcServer(ctx context.Context, identity cosi.IdentityServer, provisioner cosi.ProvisionerServer) (*grpc.Server, error) {
server := grpc.NewServer(
grpc.ChainUnaryInterceptor(
logging.UnaryServerInterceptor(logger.Wrap(log)),
recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(handlers.PanicRecovery(ctx, log))),
),
)

if identity == nil || provisioner == nil {
return nil, errors.New("provisioner and identity servers cannot be nil")
}

cosi.RegisterIdentityServer(server, identity)
cosi.RegisterProvisionerServer(server, provisioner)

return server, nil
}

// shutdown handles shutdown with grace period consideration.
func shutdown(ctx context.Context, wg *sync.WaitGroup, g *grpc.Server) {
<-ctx.Done()
defer wg.Done()
defer slog.Info("stopped")

slog.Info("shutting down")

dctx, stop := context.WithTimeout(context.Background(), gracePeriod)
defer stop()

c := make(chan struct{})

if g != nil {
go func() {
g.GracefulStop()
c <- struct{}{}
}()

for {
select {
case <-dctx.Done():
slog.Warn("forcing shutdown")
g.Stop()

return
case <-c:
return
}
}
}
}
31 changes: 31 additions & 0 deletions cmd/linode-cosi-driver/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2023 Linode, LLC
//
// 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.

package main_test

import "testing"

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

for _, tc := range []struct {
testName string
}{} {
tc := tc

t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
})
}
}
Loading