Skip to content
Open
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: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ COPY --from=builder /build/dependencies/lib/ /lib/
COPY --from=builder /build/ceph-api /bin/ceph-api
WORKDIR /bin

CMD ["ceph-api"]
ENTRYPOINT ["/bin/ceph-api", "serve"]
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ In this way Ceph API could be deployed anywhere with access to Ceph `mon`.
CEPH_DEMO_TAG=latest docker-compose up
```

Ceph-API docker container starts with both REST and grpc APIs on port `:9969` and creates default admin account with both username `admin` and password `yoursecretpass`.
Ceph-API docker container runs `ceph-api serve`, starts both REST and grpc APIs on port `:9969`, and creates default admin account with both username `admin` and password `yoursecretpass`.

2. Get API Access token:

Expand Down Expand Up @@ -70,7 +70,17 @@ In this way Ceph API could be deployed anywhere with access to Ceph `mon`.
## Config

By default, both gRPC and REST API are exposed on the same port `:9969`. See all default configurations in [`pkg/config/config.yaml`](./pkg/config/config.yaml).
To override default configuration, create similar YAML file and provide its path to application binary as `-config` or `-config-override` arguments. The latter will override the former and can be useful to manage secret values (mount k8s secret to this path).
Start the server with `ceph-api serve`. To override default configuration, create similar YAML file and provide its path with the `--config` or `--config-override` arguments. The latter will override the former and can be useful to manage secret values (mount k8s secret to this path).

```shell
ceph-api serve --config /path/to/config.yaml --config-override /path/to/override.yaml
```

The Docker image uses `/bin/ceph-api serve` as its entrypoint, so server configuration flags can be passed directly after the image name:

```shell
docker run ghcr.io/clyso/ceph-api:latest --config /path/to/config.yaml
```

Additionally, any config parameter can be set with envar in following format:
`CFG_<yaml properties seprated with _>=<value>`. For example:
Expand All @@ -85,16 +95,16 @@ app:
API config uses the following precedence order:

1. Default [`config.yaml`](./pkg/config/config.yaml)
2. YAML file provided in `-config`
3. YAML file provided in `-config-override`
2. YAML file provided in `--config`
3. YAML file provided in `--config-override`
4. Envars

## Mock Mode

To run Ceph API in mock mode without a real Ceph cluster:

```shell
CFG_APP_CREATEADMIN=true CFG_APP_ADMINUSERNAME=admin CFG_APP_ADMINPASSWORD=yoursecretpass go run -tags=mock ./cmd/ceph-api/main.go
CFG_APP_CREATEADMIN=true CFG_APP_ADMINUSERNAME=admin CFG_APP_ADMINPASSWORD=yoursecretpass go run -tags=mock ./cmd/ceph-api serve
```

## Security
Expand Down
42 changes: 3 additions & 39 deletions cmd/ceph-api/main.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,19 @@
package main

import (
"context"
"flag"
"os"
"os/signal"
"syscall"

"github.com/clyso/ceph-api/pkg/app"
"github.com/clyso/ceph-api/pkg/config"
"github.com/rs/zerolog"
stdlog "github.com/rs/zerolog/log"
)

// this information will be collected when built, by -ldflags="-X 'main.version=$(tag)' -X 'main.commit=$(commit)'".
var (
version = "development"
commit = "not set"
configPath = flag.String("config", "", "set path to config directory")
configOverridePath = flag.String("config-override", "", "set path to config override directory")
version = "development"
commit = "not set"
)

func main() {
flag.Parse()
var configs []config.Src
if configPath != nil && *configPath != "" {
configs = append(configs, config.Path(*configPath))
}
if configOverridePath != nil && *configOverridePath != "" {
configs = append(configs, config.Path(*configOverridePath))
}

ctx, cancel := context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT, syscall.SIGTERM)
go func() {
<-signals
zerolog.Ctx(ctx).Info().Msg("received shutdown signal.")
cancel()
}()
var conf config.Config
err := config.Get(&conf, configs...)
if err != nil {
stdlog.Fatal().Err(err).Msg("critical error. Unable to read app config")
}

err = app.Start(ctx, conf, config.Build{
Version: version,
Commit: commit,
})
if err != nil {
if err := newRootCmd().Execute(); err != nil {
stdlog.Err(err).Msg("critical error. Shutdown application")
os.Exit(1)
}
Expand Down
30 changes: 30 additions & 0 deletions cmd/ceph-api/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import "github.com/spf13/cobra"

type rootOptions struct {
configPath string
configOverridePath string
}

func newRootCmd() *cobra.Command {
opts := &rootOptions{}

cmd := &cobra.Command{
Use: "ceph-api",
Short: "Ceph API server and client",
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runServe(cmd.Context(), opts)
},
}

cmd.PersistentFlags().StringVar(&opts.configPath, "config", "", "set path to config directory")
cmd.PersistentFlags().StringVar(&opts.configOverridePath, "config-override", "", "set path to config override directory")

cmd.AddCommand(newServeCmd(opts))
cmd.AddCommand(newVersionCmd())

return cmd
}
64 changes: 64 additions & 0 deletions cmd/ceph-api/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"context"
"os"
"os/signal"
"syscall"

"github.com/clyso/ceph-api/pkg/app"
"github.com/clyso/ceph-api/pkg/config"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
)

func newServeCmd(opts *rootOptions) *cobra.Command {
return &cobra.Command{
Use: "serve",
Short: "Start the ceph-api server",
RunE: func(cmd *cobra.Command, args []string) error {
return runServe(cmd.Context(), opts)
},
}
}

func runServe(parent context.Context, opts *rootOptions) error {
var configs []config.Src
if opts.configPath != "" {
configs = append(configs, config.Path(opts.configPath))
}
if opts.configOverridePath != "" {
configs = append(configs, config.Path(opts.configOverridePath))
}

ctx, cancel := signalContext(parent)
defer cancel()

var conf config.Config
if err := config.Get(&conf, configs...); err != nil {
return err
}

return app.Start(ctx, conf, config.Build{
Version: version,
Commit: commit,
})
}

func signalContext(parent context.Context) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(parent)
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT, syscall.SIGTERM)

go func() {
select {
case <-signals:
zerolog.Ctx(ctx).Info().Msg("received shutdown signal.")
cancel()
case <-ctx.Done():
}
signal.Stop(signals)
}()

return ctx, cancel
}
18 changes: 18 additions & 0 deletions cmd/ceph-api/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"fmt"

"github.com/spf13/cobra"
)

func newVersionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print version information",
RunE: func(cmd *cobra.Command, args []string) error {
_, err := fmt.Fprintf(cmd.OutOrStdout(), "ceph-api %s (%s)\n", version, commit)
return err
},
}
}
30 changes: 30 additions & 0 deletions cmd/ceph-api/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"bytes"
"testing"
)

func TestVersionCommand(t *testing.T) {
oldVersion := version
oldCommit := commit
version = "v1.2.3"
commit = "abc123"
t.Cleanup(func() {
version = oldVersion
commit = oldCommit
})

cmd := newRootCmd()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetArgs([]string{"version"})

if err := cmd.Execute(); err != nil {
t.Fatalf("Execute() error = %v", err)
}

if got, want := buf.String(), "ceph-api v1.2.3 (abc123)\n"; got != want {
t.Fatalf("version output = %q, want %q", got, want)
}
}
5 changes: 3 additions & 2 deletions deploy/ceph-api/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ spec:
{{- end }}
command:
- "ceph-api"
- "-config"
- "serve"
- "--config"
- "/bin/config/config.yaml"
- "-config-override"
- "--config-override"
- "/bin/config/override.yaml"
volumeMounts:
- mountPath: /bin/config/config.yaml
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ require (

require (
github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.8.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0
golang.org/x/oauth2 v0.20.0
)
Expand Down Expand Up @@ -87,7 +88,6 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 // indirect
Expand Down
11 changes: 6 additions & 5 deletions pkg/rados/production_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package rados

import (
"fmt"
"strconv"

"github.com/ceph/go-ceph/rados"
Expand All @@ -21,26 +22,26 @@ func NewRadosConn(conf Config) (RadosConnInterface, error) {
// Create a real connection.
conn, err := rados.NewConnWithUser(conf.User)
if err != nil {
return nil, err
return nil, fmt.Errorf("initialize Ceph/RADOS client for user %q: ensure Ceph libraries are installed and available: %w", conf.User, err)
}
if conf.MonHost == "" || conf.UserKeyring == "" || conf.RadosTimeout == 0 {
err = conn.ReadDefaultConfigFile()
} else {
err = conn.ParseCmdLineArgs([]string{"--mon-host", conf.MonHost, "--key", conf.UserKeyring, "--client_mount_timeout", "3"})
}
if err != nil {
return nil, err
return nil, fmt.Errorf("load Ceph/RADOS configuration: provide rados.monHost and rados.userKeyring, or ensure the default Ceph config/keyring files are readable: %w", err)
}

timeout := strconv.FormatFloat(conf.RadosTimeout.Seconds(), 'f', -1, 64)
if err = conn.SetConfigOption("rados_osd_op_timeout", timeout); err != nil {
return nil, err
return nil, fmt.Errorf("configure Ceph/RADOS OSD operation timeout: %w", err)
}
if err = conn.SetConfigOption("rados_mon_op_timeout", timeout); err != nil {
return nil, err
return nil, fmt.Errorf("configure Ceph/RADOS monitor operation timeout: %w", err)
}
if err = conn.Connect(); err != nil {
return nil, err
return nil, fmt.Errorf("connect to Ceph/RADOS cluster: ensure Ceph config/keyring are valid and monitors are reachable: %w", err)
}

// Wrap the real connection.
Expand Down