Add comments on exported items, remove example command

Also add `make lint` to run the linter
This commit is contained in:
Djordje Lukic 2020-05-04 23:00:21 +02:00 committed by Djordje Lukic
parent 29737c2a23
commit 24c035e822
26 changed files with 136 additions and 196 deletions

33
.golangci.yml Normal file
View File

@ -0,0 +1,33 @@
linters:
run:
concurrency: 2
enable-all: false
disable-all: true
enable:
- deadcode
- errcheck
- gocyclo
- gofmt
- goimports
- golint
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- staticcheck
- structcheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
linters-settings:
gocyclo:
min-complexity: 16
lll:
line-length: 200
issues:
exclude-use-default: false

View File

@ -53,8 +53,14 @@ test: ## Run unit tests
cache-clear: # Clear the builder cache
@docker builder prune --force --filter type=exec.cachemount --filter=unused-for=24h
lint: ## run linter(s)
@echo "Linting..."
golangci-lint run --timeout 10m0s ./...
help: ## Show help
@echo Please specify a build target. The choices are:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: all protos cli cross test cache-clear help
FORCE:
.PHONY: all protos cli cross test cache-clear lint help

View File

@ -30,10 +30,7 @@ func init() {
}
func createACIContainers(ctx context.Context, aciContext store.AciContext, groupDefinition containerinstance.ContainerGroup) (c containerinstance.ContainerGroup, err error) {
containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
if err != nil {
return c, fmt.Errorf("cannot get container group client: %v", err)
}
containerGroupsClient := getContainerGroupsClient(aciContext.SubscriptionID)
// Check if the container group already exists
_, err = containerGroupsClient.Get(ctx, aciContext.ResourceGroup, *groupDefinition.Name)
@ -96,28 +93,6 @@ func createACIContainers(ctx context.Context, aciContext store.AciContext, group
return containerGroup, err
}
func listACIContainers(aciContext store.AciContext) (c []containerinstance.ContainerGroup, err error) {
ctx := context.TODO()
containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
if err != nil {
return c, fmt.Errorf("cannot get container group client: %v", err)
}
var containers []containerinstance.ContainerGroup
result, err := containerGroupsClient.ListByResourceGroup(ctx, aciContext.ResourceGroup)
if err != nil {
return []containerinstance.ContainerGroup{}, err
}
for result.NotDone() {
containers = append(containers, result.Values()...)
if err := result.NextWithContext(ctx); err != nil {
return []containerinstance.ContainerGroup{}, err
}
}
return containers, err
}
func execACIContainer(ctx context.Context, aciContext store.AciContext, command, containerGroup string, containerName string) (c containerinstance.ContainerExecResponse, err error) {
containerClient := getContainerClient(aciContext.SubscriptionID)
rows, cols := getTermSize()
@ -233,11 +208,11 @@ func getACIContainerLogs(ctx context.Context, aciContext store.AciContext, conta
return *logs.Content, err
}
func getContainerGroupsClient(subscriptionID string) (containerinstance.ContainerGroupsClient, error) {
func getContainerGroupsClient(subscriptionID string) containerinstance.ContainerGroupsClient {
auth, _ := auth.NewAuthorizerFromCLI()
containerGroupsClient := containerinstance.NewContainerGroupsClient(subscriptionID)
containerGroupsClient.Authorizer = auth
return containerGroupsClient, nil
return containerGroupsClient
}
func getContainerClient(subscriptionID string) containerinstance.ContainerClient {

View File

@ -36,6 +36,7 @@ func getter() interface{} {
return &store.AciContext{}
}
// New creates a backend that can manage containers on ACI
func New(ctx context.Context) (containers.ContainerService, error) {
currentContext := apicontext.CurrentContext(ctx)
contextStore, err := store.New()

View File

@ -21,8 +21,10 @@ const (
volumeDriveroptsAccountNameKey = "storage_account_name"
volumeDriveroptsAccountKeyKey = "storage_account_key"
singleContainerName = "single--container--aci"
secretInlineMark = "inline:"
)
// ToContainerGroup converts a compose project into a ACI container group
func ToContainerGroup(aciContext store.AciContext, p compose.Project) (containerinstance.ContainerGroup, error) {
project := projectAciHelper(p)
containerGroupName := strings.ToLower(project.Name)
@ -98,8 +100,8 @@ func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, err
var secretVolumes []containerinstance.Volume
for secretName, filepathToRead := range p.Secrets {
var data []byte
if strings.HasPrefix(filepathToRead.File, compose.SecretInlineMark) {
data = []byte(filepathToRead.File[len(compose.SecretInlineMark):])
if strings.HasPrefix(filepathToRead.File, secretInlineMark) {
data = []byte(filepathToRead.File[len(secretInlineMark):])
} else {
var err error
data, err = ioutil.ReadFile(filepathToRead.File)

View File

@ -7,43 +7,46 @@ import (
)
var (
ErrNoType = errors.New("backend: no type")
ErrNoName = errors.New("backend: no name")
ErrTypeRegistered = errors.New("backend: already registered")
errNoType = errors.New("backend: no type")
errNoName = errors.New("backend: no name")
errTypeRegistered = errors.New("backend: already registered")
)
type InitFunc func(context.Context) (interface{}, error)
type initFunc func(context.Context) (interface{}, error)
type Backend struct {
type registeredBackend struct {
name string
backendType string
init InitFunc
init initFunc
}
var backends = struct {
r []*Backend
r []*registeredBackend
}{}
func Register(name string, backendType string, init InitFunc) {
// Register adds a typed backend to the registry
func Register(name string, backendType string, init initFunc) {
if name == "" {
panic(ErrNoName)
panic(errNoName)
}
if backendType == "" {
panic(ErrNoType)
panic(errNoType)
}
for _, b := range backends.r {
if b.backendType == backendType {
panic(ErrTypeRegistered)
panic(errTypeRegistered)
}
}
backends.r = append(backends.r, &Backend{
backends.r = append(backends.r, &registeredBackend{
name,
backendType,
init,
})
}
// Get returns the backend registered for a particular type, it returns
// an error if there is no registered backends for the given type.
func Get(ctx context.Context, backendType string) (interface{}, error) {
for _, b := range backends.r {
if b.backendType == backendType {

View File

@ -38,9 +38,7 @@ import (
"github.com/docker/api/context/store"
)
type CliContext struct {
}
// ContextCommand manages contexts
func ContextCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "context",

View File

@ -1,71 +0,0 @@
/*
Copyright (c) 2020 Docker Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package cmd
import (
"context"
"encoding/json"
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra"
v1 "github.com/docker/api/backend/v1"
"github.com/docker/api/client"
)
var ExampleCommand = cobra.Command{
Use: "example",
Short: "sample command using backend, to be removed later",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
c, err := client.New(ctx)
if err != nil {
return errors.Wrap(err, "cannot connect to backend")
}
info, err := c.BackendInformation(ctx, &v1.BackendInformationRequest{})
if err != nil {
return errors.Wrap(err, "fetch backend information")
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(info)
},
}
type backendAddressKey struct{}
func BackendAddress(ctx context.Context) (string, error) {
v, ok := ctx.Value(backendAddressKey{}).(string)
if !ok {
return "", errors.New("no backend address key")
}
return v, nil
}

View File

@ -2,6 +2,7 @@ package cmd
import (
"context"
"fmt"
"io"
"os"
"strings"
@ -17,6 +18,7 @@ type execOpts struct {
Tty bool
}
// ExecCommand runs a command in a running container
func ExecCommand() *cobra.Command {
var opts execOpts
cmd := &cobra.Command{
@ -52,7 +54,9 @@ func runExec(ctx context.Context, opts execOpts, name string, command string) er
return err
}
defer func() {
con.Reset()
if err := con.Reset(); err != nil {
fmt.Println("Unable to close the console")
}
}()
stdout = con

View File

@ -16,6 +16,7 @@ type logsOpts struct {
Tail string
}
// LogsCommand fetches and shows logs of a container
func LogsCommand() *cobra.Command {
var opts logsOpts
cmd := &cobra.Command{

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/api/client"
)
// PsCommand lists containers
var PsCommand = cobra.Command{
Use: "ps",
Short: "List containers",

View File

@ -36,6 +36,7 @@ import (
"github.com/docker/api/client"
)
// Command runs a container
func Command() *cobra.Command {
var opts runOpts
cmd := &cobra.Command{

View File

@ -20,6 +20,7 @@ type serveOpts struct {
address string
}
// ServeCommand returns the command to serve the API
func ServeCommand() *cobra.Command {
var opts serveOpts
cmd := &cobra.Command{
@ -42,9 +43,10 @@ func runServe(ctx context.Context, opts serveOpts) error {
if err != nil {
return errors.Wrap(err, "listen unix socket")
}
// nolint
defer listener.Close()
p := proxy.NewContainerApi()
p := proxy.NewContainerAPI()
containersv1.RegisterContainersServer(s, p)
cliv1.RegisterCliServer(s, &cliServer{

View File

@ -51,7 +51,7 @@ import (
)
type mainOpts struct {
apicontext.ContextFlags
apicontext.Flags
debug bool
}
@ -99,7 +99,6 @@ func main() {
cmd.ContextCommand(),
&cmd.PsCommand,
cmd.ServeCommand(),
&cmd.ExampleCommand,
run.Command(),
cmd.ExecCommand(),
cmd.LogsCommand(),

View File

@ -41,7 +41,7 @@ import (
"github.com/docker/api/context/store"
)
// New returns a GRPC client
// New returns a backend client
func New(ctx context.Context) (*Client, error) {
currentContext := apicontext.CurrentContext(ctx)
s := store.ContextStore(ctx)
@ -68,6 +68,7 @@ func New(ctx context.Context) (*Client, error) {
}
// Client is a multi-backend client
type Client struct {
backendv1.BackendClient
cliv1.CliClient
@ -78,6 +79,7 @@ type Client struct {
cc containers.ContainerService
}
// ContainerService returns the backend service for the current context
func (c *Client) ContainerService() containers.ContainerService {
return c.cc
}

View File

@ -14,17 +14,14 @@ import (
"github.com/sirupsen/logrus"
)
const (
SecretInlineMark = "inline:"
)
var SupportedFilenames = []string{
var supportedFilenames = []string{
"compose.yml",
"compose.yaml",
"docker-compose.yml",
"docker-compose.yaml",
}
// ProjectOptions configures a compose project
type ProjectOptions struct {
Name string
WorkDir string
@ -32,6 +29,7 @@ type ProjectOptions struct {
Environment []string
}
// Project represents a compose project with a name
type Project struct {
types.Config
projectDir string
@ -100,7 +98,7 @@ func getConfigPathFromOptions(options *ProjectOptions) ([]string, error) {
for {
var candidates []string
for _, n := range SupportedFilenames {
for _, n := range supportedFilenames {
f := filepath.Join(pwd, n)
if _, err := os.Stat(f); err == nil {
candidates = append(candidates, f)
@ -116,7 +114,7 @@ func getConfigPathFromOptions(options *ProjectOptions) ([]string, error) {
}
parent := filepath.Dir(pwd)
if parent == pwd {
return nil, fmt.Errorf("Can't find a suitable configuration file in this directory or any parent. Is %q the right directory?", pwd)
return nil, fmt.Errorf("can't find a suitable configuration file in this directory or any parent. Is %q the right directory?", pwd)
}
pwd = parent
}
@ -129,12 +127,11 @@ func parseConfigs(configPaths []string) ([]types.ConfigFile, error) {
var err error
if f == "-" {
return []types.ConfigFile{}, errors.New("reading compose file from stdin is not supported")
} else {
if _, err := os.Stat(f); err != nil {
return nil, err
}
b, err = ioutil.ReadFile(f)
}
if _, err := os.Stat(f); err != nil {
return nil, err
}
b, err = ioutil.ReadFile(f)
if err != nil {
return nil, err
}

View File

@ -1,30 +0,0 @@
/*
Copyright (c) 2019 Docker Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package api
const DockerContextKey = "DOCKER_CONTEXT_KEY"

View File

@ -11,7 +11,7 @@ type Container struct {
Status string
Image string
Command string
CpuTime uint64
CPUTime uint64
MemoryUsage uint64
MemoryLimit uint64
PidsCurrent uint64
@ -37,6 +37,7 @@ type ContainerConfig struct {
Ports []Port
}
// LogsRequest contains configuration about a log request
type LogsRequest struct {
Follow bool
Tail string

View File

@ -34,6 +34,7 @@ import (
"path/filepath"
)
// LoadConfigFile loads the docker configuration
func LoadConfigFile(configDir string, configFileName string) (*ConfigFile, error) {
filename := filepath.Join(configDir, configFileName)
configFile := &ConfigFile{
@ -45,6 +46,7 @@ func LoadConfigFile(configDir string, configFileName string) (*ConfigFile, error
if err != nil {
return nil, fmt.Errorf("can't read %s: %w", filename, err)
}
// nolint
defer file.Close()
err = json.NewDecoder(file).Decode(&configFile)
if err != nil {
@ -59,6 +61,7 @@ func LoadConfigFile(configDir string, configFileName string) (*ConfigFile, error
return configFile, nil
}
// ConfigFile contains the current context from the docker configuration file
type ConfigFile struct {
Filename string `json:"-"` // Note: for internal use only
CurrentContext string `json:"currentContext,omitempty"`

View File

@ -6,10 +6,13 @@ import (
"golang.org/x/net/context"
)
const KEY = "context_key"
// Key is the key where the current docker context is stored in the metadata
// of a gRPC request
const Key = "context_key"
type currentContextKey struct{}
// WithCurrentContext sets the name of the current docker context
func WithCurrentContext(ctx gocontext.Context, contextName string) context.Context {
return context.WithValue(ctx, currentContextKey{}, contextName)
}

View File

@ -41,12 +41,14 @@ const (
configFileDir = ".docker"
)
type ContextFlags struct {
// Flags are the global cli flags
type Flags struct {
Config string
Context string
}
func (c *ContextFlags) AddFlags(flags *pflag.FlagSet) {
// AddFlags adds persistent (globa) flags
func (c *Flags) AddFlags(flags *pflag.FlagSet) {
flags.StringVar(&c.Config, "config", filepath.Join(home(), configFileDir), "Location of the client config files `DIRECTORY`")
flags.StringVarP(&c.Context, "context", "c", os.Getenv("DOCKER_CONTEXT"), "context")
}

View File

@ -47,16 +47,18 @@ const (
type contextStoreKey struct{}
// WithContextStore adds the store to the context
func WithContextStore(ctx context.Context, store Store) context.Context {
return context.WithValue(ctx, contextStoreKey{}, store)
}
// ContextStore returns the store from the context
func ContextStore(ctx context.Context) Store {
s, _ := ctx.Value(contextStoreKey{}).(Store)
return s
}
// Store
// Store is the context store
type Store interface {
// Get returns the context with name, it returns an error if the context
// doesn't exist
@ -74,16 +76,18 @@ type store struct {
root string
}
type StoreOpt func(*store)
// Opt is a functional option for the store
type Opt func(*store)
func WithRoot(root string) StoreOpt {
// WithRoot sets a new root to the store
func WithRoot(root string) Opt {
return func(s *store) {
s.root = root
}
}
// New returns a configured context store
func New(opts ...StoreOpt) (Store, error) {
// New returns a configured context store with $HOME/.docker as root
func New(opts ...Opt) (Store, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
@ -182,7 +186,7 @@ func (s *store) Create(name string, data TypedContext) error {
dir := contextdirOf(name)
metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir)
if _, err := os.Stat(metaDir); !os.IsNotExist(err) {
return fmt.Errorf("Context %q already exists", name)
return fmt.Errorf("context %q already exists", name)
}
err := os.Mkdir(metaDir, 0755)
@ -191,15 +195,15 @@ func (s *store) Create(name string, data TypedContext) error {
}
if data.Data == nil {
data.Data = DummyContext{}
data.Data = dummyContext{}
}
meta := Metadata{
Name: name,
Metadata: data,
Endpoints: map[string]interface{}{
"docker": DummyContext{},
(data.Type): DummyContext{},
"docker": dummyContext{},
(data.Type): dummyContext{},
},
}
@ -237,8 +241,9 @@ func contextdirOf(name string) string {
return digest.FromString(name).Encoded()
}
type DummyContext struct{}
type dummyContext struct{}
// Metadata represents the docker context metadata
type Metadata struct {
Name string `json:",omitempty"`
Metadata TypedContext `json:",omitempty"`
@ -257,12 +262,14 @@ type untypedContext struct {
Type string `json:",omitempty"`
}
// TypedContext is a context with a type (moby, aci, etc...)
type TypedContext struct {
Type string `json:",omitempty"`
Description string `json:",omitempty"`
Data interface{} `json:",omitempty"`
}
// AciContext is the context for ACI
type AciContext struct {
SubscriptionID string `json:",omitempty"`
Location string `json:",omitempty"`

View File

@ -55,7 +55,8 @@ func (suite *StoreTestSuite) BeforeTest(suiteName, testName string) {
}
func (suite *StoreTestSuite) AfterTest(suiteName, testName string) {
os.RemoveAll(suite.dir)
err := os.RemoveAll(suite.dir)
require.Nil(suite.T(), err)
}
func (suite *StoreTestSuite) TestCreate() {

View File

@ -13,14 +13,10 @@ type containerService struct{}
func init() {
backend.Register("example", "example", func(ctx context.Context) (interface{}, error) {
return New(), nil
return &containerService{}, nil
})
}
func New() containers.ContainerService {
return &containerService{}
}
func (cs *containerService) List(ctx context.Context) ([]containers.Container, error) {
return []containers.Container{
{

View File

@ -10,22 +10,25 @@ import (
type clientKey struct{}
// WithClient adds the client to the context
func WithClient(ctx context.Context, c *client.Client) (context.Context, error) {
return context.WithValue(ctx, clientKey{}, c), nil
}
// Client returns the client from the context
func Client(ctx context.Context) *client.Client {
c, _ := ctx.Value(clientKey{}).(*client.Client)
return c
}
func NewContainerApi() v1.ContainersServer {
return &proxyContainerApi{}
// NewContainerAPI creates a proxy container server
func NewContainerAPI() v1.ContainersServer {
return &proxyContainerAPI{}
}
type proxyContainerApi struct{}
type proxyContainerAPI struct{}
func (p *proxyContainerApi) List(ctx context.Context, _ *v1.ListRequest) (*v1.ListResponse, error) {
func (p *proxyContainerAPI) List(ctx context.Context, _ *v1.ListRequest) (*v1.ListResponse, error) {
client := Client(ctx)
c, err := client.ContainerService().List(ctx)
@ -46,7 +49,7 @@ func (p *proxyContainerApi) List(ctx context.Context, _ *v1.ListRequest) (*v1.Li
return response, nil
}
func (p *proxyContainerApi) Create(ctx context.Context, request *v1.CreateRequest) (*v1.CreateResponse, error) {
func (p *proxyContainerAPI) Create(ctx context.Context, request *v1.CreateRequest) (*v1.CreateResponse, error) {
client := Client(ctx)
err := client.ContainerService().Run(ctx, containers.ContainerConfig{
@ -57,26 +60,26 @@ func (p *proxyContainerApi) Create(ctx context.Context, request *v1.CreateReques
return &v1.CreateResponse{}, err
}
func (p *proxyContainerApi) Start(_ context.Context, _ *v1.StartRequest) (*v1.StartResponse, error) {
func (p *proxyContainerAPI) Start(_ context.Context, _ *v1.StartRequest) (*v1.StartResponse, error) {
panic("not implemented") // TODO: Implement
}
func (p *proxyContainerApi) Stop(_ context.Context, _ *v1.StopRequest) (*v1.StopResponse, error) {
func (p *proxyContainerAPI) Stop(_ context.Context, _ *v1.StopRequest) (*v1.StopResponse, error) {
panic("not implemented") // TODO: Implement
}
func (p *proxyContainerApi) Kill(_ context.Context, _ *v1.KillRequest) (*v1.KillResponse, error) {
func (p *proxyContainerAPI) Kill(_ context.Context, _ *v1.KillRequest) (*v1.KillResponse, error) {
panic("not implemented") // TODO: Implement
}
func (p *proxyContainerApi) Delete(_ context.Context, _ *v1.DeleteRequest) (*v1.DeleteResponse, error) {
func (p *proxyContainerAPI) Delete(_ context.Context, _ *v1.DeleteRequest) (*v1.DeleteResponse, error) {
panic("not implemented") // TODO: Implement
}
func (p *proxyContainerApi) Update(_ context.Context, _ *v1.UpdateRequest) (*v1.UpdateResponse, error) {
func (p *proxyContainerAPI) Update(_ context.Context, _ *v1.UpdateRequest) (*v1.UpdateResponse, error) {
panic("not implemented") // TODO: Implement
}
func (p *proxyContainerApi) Exec(_ context.Context, _ *v1.ExecRequest) (*v1.ExecResponse, error) {
func (p *proxyContainerAPI) Exec(_ context.Context, _ *v1.ExecRequest) (*v1.ExecResponse, error) {
panic("not implemented") // TODO: Implement
}

View File

@ -71,7 +71,7 @@ func unaryMeta(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
return nil, errors.New("missing metadata")
}
key := md[apicontext.KEY]
key := md[apicontext.Key]
if len(key) == 1 {
s, err := store.New()