feat: introduce ledger and version (#87)
#### Features - Introduce ledger for gathering analytics abt usage of foundryctl - Adds version command Related: https://github.com/SigNoz/platform-pod/issues/1910
This commit is contained in:
@@ -8,15 +8,50 @@ permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
MAKE: make --no-print-directory --makefile=.primus/src/make/main.mk
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.build-info.outputs.version }}
|
||||
hash: ${{ steps.build-info.outputs.hash }}
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- id: token
|
||||
name: github-token-gen
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.PRIMUS_APP_ID }}
|
||||
private-key: ${{ secrets.PRIMUS_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: primus-checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: signoz/primus
|
||||
ref: main
|
||||
path: .primus
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: build-info
|
||||
id: build-info
|
||||
run: |
|
||||
echo "version=$($MAKE info-version)" >> $GITHUB_OUTPUT
|
||||
echo "hash=$($MAKE info-commit-short)" >> $GITHUB_OUTPUT
|
||||
docker:
|
||||
uses: signoz/primus.workflows/.github/workflows/go-build.yaml@main
|
||||
needs: [prepare]
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
GO_BUILD_CONTEXT: ./cmd/foundryctl
|
||||
GO_VERSION: 1.25
|
||||
GO_BUILD_FLAGS: ""
|
||||
GO_BUILD_FLAGS: >-
|
||||
-ldflags='-s -w
|
||||
-X github.com/signoz/foundry/internal/version.version=${{ needs.prepare.outputs.version }}
|
||||
-X github.com/signoz/foundry/internal/version.hash=${{ needs.prepare.outputs.hash }}
|
||||
-X github.com/signoz/foundry/internal/ledger.key=YPscRxkyHv2Fn0aOucmLrMYBBVAC1AFu"
|
||||
GO_NAME: foundryctl
|
||||
DOCKER_BASE_IMAGES: '{"distroless":"gcr.io/distroless/base:latest"}'
|
||||
DOCKER_DOCKERFILE_PATH: Dockerfile.multi-arch
|
||||
|
||||
@@ -24,6 +24,11 @@ builds:
|
||||
goarm64:
|
||||
- v8.0
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X github.com/signoz/foundry/internal/version.version=v{{ .Version }}
|
||||
- -X github.com/signoz/foundry/internal/version.hash={{ .ShortCommit }}
|
||||
- -X github.com/signoz/foundry/internal/ledger.key=YPscRxkyHv2Fn0aOucmLrMYBBVAC1AFu
|
||||
tags:
|
||||
- timetzdata
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
FOUNDRYCTL := go run ./cmd/foundryctl
|
||||
GOTMPL := go run go.opentelemetry.io/build-tools/gotmpl@latest
|
||||
CASTINGS_JSON = $$(cat docs/examples/castings.json)
|
||||
NO_LEDGER := "--no-ledger"
|
||||
|
||||
clean:
|
||||
cd pours/deployment && docker compose -p dev down --remove-orphans --volumes
|
||||
@@ -10,26 +11,26 @@ clean:
|
||||
rm -rf ./pours
|
||||
|
||||
gauge:
|
||||
$(FOUNDRYCTL) gauge --debug -f ./tmp/casting.yaml
|
||||
$(FOUNDRYCTL) gauge --debug $(NO_LEDGER) -f ./tmp/casting.yaml
|
||||
|
||||
forge:
|
||||
$(FOUNDRYCTL) forge --debug -f ./tmp/casting.yaml
|
||||
$(FOUNDRYCTL) forge --debug $(NO_LEDGER) -f ./tmp/casting.yaml
|
||||
|
||||
cast:
|
||||
$(FOUNDRYCTL) cast --debug -f ./tmp/casting.yaml
|
||||
$(FOUNDRYCTL) cast --debug $(NO_LEDGER) -f ./tmp/casting.yaml
|
||||
|
||||
test:
|
||||
make forge
|
||||
make docker
|
||||
|
||||
gen-examples:
|
||||
$(FOUNDRYCTL) gen --debug examples
|
||||
$(FOUNDRYCTL) gen --debug $(NO_LEDGER) examples
|
||||
|
||||
gen-schemas:
|
||||
$(FOUNDRYCTL) gen --debug schemas
|
||||
$(FOUNDRYCTL) gen --debug $(NO_LEDGER) schemas
|
||||
|
||||
gen-docs:
|
||||
$(FOUNDRYCTL) catalog --debug --format json --output "docs/examples/castings.json"
|
||||
$(FOUNDRYCTL) catalog --debug $(NO_LEDGER) --format json --output "docs/examples/castings.json"
|
||||
$(GOTMPL) -b README.md.gotmpl -d "$(CASTINGS_JSON)" -o README.md
|
||||
$(GOTMPL) -b docs/examples/README.md.gotmpl -d "$(CASTINGS_JSON)" -o docs/examples/README.md
|
||||
|
||||
|
||||
+20
-5
@@ -10,6 +10,7 @@ import (
|
||||
foundryerrors "github.com/signoz/foundry/internal/errors"
|
||||
"github.com/signoz/foundry/internal/foundry"
|
||||
"github.com/signoz/foundry/internal/instrumentation"
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -20,22 +21,26 @@ func registerCastCmd(rootCmd *cobra.Command) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
logger := instrumentation.NewLogger(commonCfg.Debug)
|
||||
tracker := newTracker()
|
||||
defer func() {
|
||||
_ = tracker.Close()
|
||||
}()
|
||||
|
||||
if !castCfg.NoGauge {
|
||||
err := runGauge(ctx, logger, commonCfg.File)
|
||||
err := runGauge(ctx, logger, tracker, commonCfg.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !castCfg.NoForge {
|
||||
err := runForge(ctx, logger, commonCfg.File, poursCfg.Path)
|
||||
err := runForge(ctx, logger, tracker, commonCfg.File, poursCfg.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return runCast(ctx, logger, poursCfg.Path, commonCfg.File)
|
||||
return runCast(ctx, logger, tracker, poursCfg.Path, commonCfg.File)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -43,7 +48,7 @@ func registerCastCmd(rootCmd *cobra.Command) {
|
||||
castCfg.RegisterFlags(castCmd)
|
||||
}
|
||||
|
||||
func runCast(ctx context.Context, logger *slog.Logger, poursPath string, configPath string) error {
|
||||
func runCast(ctx context.Context, logger *slog.Logger, tracker ledger.Ledger, poursPath string, configPath string) error {
|
||||
foundry, err := foundry.New(logger)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create foundry, please report this issues to developers at https://github.com/signoz/foundry/issues", foundryerrors.LogAttr(err))
|
||||
@@ -59,8 +64,18 @@ func runCast(ctx context.Context, logger *slog.Logger, poursPath string, configP
|
||||
lock, err := foundry.Config.GetV1Alpha1Lock(ctx, configPath)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to load generated casting.yaml.lock", foundryerrors.LogAttr(err))
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("cast"), err))
|
||||
return err
|
||||
}
|
||||
|
||||
return foundry.Cast(ctx, lock, poursPath)
|
||||
props := ledger.CastingProperties("cast", lock)
|
||||
|
||||
err = foundry.Cast(ctx, lock, poursPath)
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(props, err))
|
||||
return err
|
||||
}
|
||||
|
||||
tracker.Track(ctx, ledger.WithSuccess(props))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"os"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/signoz/foundry/api/v1alpha1"
|
||||
"github.com/signoz/foundry/internal/foundry"
|
||||
"github.com/signoz/foundry/internal/instrumentation"
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -19,8 +21,12 @@ func registerCatalogCmd(rootCmd *cobra.Command) {
|
||||
Short: "Show available castings",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
logger := instrumentation.NewLogger(commonCfg.Debug)
|
||||
tracker := newTracker()
|
||||
defer func() {
|
||||
_ = tracker.Close()
|
||||
}()
|
||||
|
||||
return runCatalog(logger)
|
||||
return runCatalog(cmd.Context(), logger, tracker)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -61,9 +67,10 @@ func catalogGroup(e castingEntry) int {
|
||||
}
|
||||
}
|
||||
|
||||
func runCatalog(logger *slog.Logger) error {
|
||||
func runCatalog(ctx context.Context, logger *slog.Logger, tracker ledger.Ledger) error {
|
||||
f, err := foundry.New(logger)
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("catalog"), err))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -88,13 +95,25 @@ func runCatalog(logger *slog.Logger) error {
|
||||
if catalogCfg.Format == "json" {
|
||||
data, err := json.MarshalIndent(map[string]any{"Castings": entries}, "", " ")
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("catalog"), err))
|
||||
return err
|
||||
}
|
||||
if catalogCfg.OutPath != "" {
|
||||
return os.WriteFile(catalogCfg.OutPath, data, 0644)
|
||||
err = os.WriteFile(catalogCfg.OutPath, data, 0644)
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("catalog"), err))
|
||||
return err
|
||||
}
|
||||
tracker.Track(ctx, ledger.WithSuccess(ledger.CommandProperties("catalog")))
|
||||
return nil
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
return err
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("catalog"), err))
|
||||
return err
|
||||
}
|
||||
tracker.Track(ctx, ledger.WithSuccess(ledger.CommandProperties("catalog")))
|
||||
return nil
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
@@ -103,5 +122,12 @@ func runCatalog(logger *slog.Logger) error {
|
||||
_ = table.Append(e.Mode, e.Flavor, e.Platform, e.Example)
|
||||
}
|
||||
|
||||
return table.Render()
|
||||
err = table.Render()
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("catalog"), err))
|
||||
return err
|
||||
}
|
||||
|
||||
tracker.Track(ctx, ledger.WithSuccess(ledger.CommandProperties("catalog")))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,13 +17,15 @@ var (
|
||||
)
|
||||
|
||||
type commonConfig struct {
|
||||
File string
|
||||
Debug bool
|
||||
File string
|
||||
Debug bool
|
||||
NoLedger bool
|
||||
}
|
||||
|
||||
func (c *commonConfig) RegisterFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().StringVarP(&c.File, "file", "f", "casting.yaml", "Path to the casting configuration file.")
|
||||
cmd.PersistentFlags().BoolVarP(&c.Debug, "debug", "d", false, "Enable debug mode.")
|
||||
cmd.PersistentFlags().BoolVar(&c.NoLedger, "no-ledger", false, "Disable anonymous usage ledger.")
|
||||
}
|
||||
|
||||
type poursConfig struct {
|
||||
|
||||
+13
-2
@@ -9,6 +9,7 @@ import (
|
||||
foundryerrors "github.com/signoz/foundry/internal/errors"
|
||||
"github.com/signoz/foundry/internal/foundry"
|
||||
"github.com/signoz/foundry/internal/instrumentation"
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
"github.com/signoz/foundry/internal/writer"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -21,15 +22,19 @@ func registerForgeCmd(rootCmd *cobra.Command) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
logger := instrumentation.NewLogger(commonCfg.Debug)
|
||||
tracker := newTracker()
|
||||
defer func() {
|
||||
_ = tracker.Close()
|
||||
}()
|
||||
|
||||
return runForge(ctx, logger, commonCfg.File, poursCfg.Path)
|
||||
return runForge(ctx, logger, tracker, commonCfg.File, poursCfg.Path)
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(forgeCmd)
|
||||
}
|
||||
|
||||
func runForge(ctx context.Context, logger *slog.Logger, path string, poursPath string) error {
|
||||
func runForge(ctx context.Context, logger *slog.Logger, tracker ledger.Ledger, path string, poursPath string) error {
|
||||
foundry, err := foundry.New(logger)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create foundry, please report this issues to developers at https://github.com/signoz/foundry/issues", foundryerrors.LogAttr(err))
|
||||
@@ -38,18 +43,24 @@ func runForge(ctx context.Context, logger *slog.Logger, path string, poursPath s
|
||||
|
||||
config, err := foundry.Config.GetV1Alpha1(ctx, path)
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("forge"), err))
|
||||
return err
|
||||
}
|
||||
|
||||
props := ledger.CastingProperties("forge", config)
|
||||
|
||||
poursAbsPath, err := filepath.Abs(poursPath)
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(props, err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = foundry.Forge(ctx, config, path, &writer.Options{Output: &os.File{}, TargetDirectory: poursAbsPath})
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(props, err))
|
||||
return err
|
||||
}
|
||||
|
||||
tracker.Track(ctx, ledger.WithSuccess(props))
|
||||
return nil
|
||||
}
|
||||
|
||||
+12
-2
@@ -7,6 +7,7 @@ import (
|
||||
foundryerrors "github.com/signoz/foundry/internal/errors"
|
||||
"github.com/signoz/foundry/internal/foundry"
|
||||
"github.com/signoz/foundry/internal/instrumentation"
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -17,15 +18,19 @@ func registerGaugeCmd(rootCmd *cobra.Command) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
logger := instrumentation.NewLogger(commonCfg.Debug)
|
||||
tracker := newTracker()
|
||||
defer func() {
|
||||
_ = tracker.Close()
|
||||
}()
|
||||
|
||||
return runGauge(ctx, logger, commonCfg.File)
|
||||
return runGauge(ctx, logger, tracker, commonCfg.File)
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(gaugeCmd)
|
||||
}
|
||||
|
||||
func runGauge(ctx context.Context, logger *slog.Logger, path string) error {
|
||||
func runGauge(ctx context.Context, logger *slog.Logger, tracker ledger.Ledger, path string) error {
|
||||
foundry, err := foundry.New(logger)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create foundry, please report this issues to developers at https://github.com/signoz/foundry/issues", foundryerrors.LogAttr(err))
|
||||
@@ -35,14 +40,19 @@ func runGauge(ctx context.Context, logger *slog.Logger, path string) error {
|
||||
casting, err := foundry.Config.GetV1Alpha1(ctx, path)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, err.Error())
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("gauge"), err))
|
||||
return err
|
||||
}
|
||||
|
||||
props := ledger.CastingProperties("gauge", casting)
|
||||
|
||||
err = foundry.Gauge(ctx, casting)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, err.Error())
|
||||
tracker.Track(ctx, ledger.WithError(props, err))
|
||||
return err
|
||||
}
|
||||
|
||||
tracker.Track(ctx, ledger.WithSuccess(props))
|
||||
return nil
|
||||
}
|
||||
|
||||
+20
-6
@@ -11,6 +11,7 @@ import (
|
||||
foundryerrors "github.com/signoz/foundry/internal/errors"
|
||||
"github.com/signoz/foundry/internal/foundry"
|
||||
"github.com/signoz/foundry/internal/instrumentation"
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
"github.com/signoz/foundry/internal/types"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
@@ -35,8 +36,12 @@ func registerGenExamples(rootCmd *cobra.Command) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
logger := instrumentation.NewLogger(commonCfg.Debug)
|
||||
tracker := newTracker()
|
||||
defer func() {
|
||||
_ = tracker.Close()
|
||||
}()
|
||||
|
||||
return runGenExamples(ctx, logger)
|
||||
return runGenExamples(ctx, logger, tracker)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,19 +54,23 @@ func registerGenSchemas(rootCmd *cobra.Command) {
|
||||
Short: "Generate schema files.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
logger := instrumentation.NewLogger(commonCfg.Debug)
|
||||
tracker := newTracker()
|
||||
defer func() {
|
||||
_ = tracker.Close()
|
||||
}()
|
||||
|
||||
return runGenSchemas(ctx, logger)
|
||||
return runGenSchemas(ctx, tracker)
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(genSchemasCmd)
|
||||
}
|
||||
|
||||
func runGenExamples(ctx context.Context, logger *slog.Logger) error {
|
||||
func runGenExamples(ctx context.Context, logger *slog.Logger, tracker ledger.Ledger) error {
|
||||
foundry, err := foundry.New(logger)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create foundry, please report this issues to developers at https://github.com/signoz/foundry/issues", foundryerrors.LogAttr(err))
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("gen.examples"), err))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -82,27 +91,32 @@ func runGenExamples(ctx context.Context, logger *slog.Logger) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = runForge(ctx, logger, filepath.Join(rootPath, "casting.yaml"), filepath.Join(rootPath, "pours"))
|
||||
err = runForge(ctx, logger, tracker, filepath.Join(rootPath, "casting.yaml"), filepath.Join(rootPath, "pours"))
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to forge casting", slog.Any("deployment", deployment), foundryerrors.LogAttr(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tracker.Track(ctx, ledger.WithSuccess(ledger.CommandProperties("gen.examples")))
|
||||
return nil
|
||||
}
|
||||
func runGenSchemas(context.Context, *slog.Logger) error {
|
||||
|
||||
func runGenSchemas(ctx context.Context, tracker ledger.Ledger) error {
|
||||
reflector := jsonschema.Reflector{}
|
||||
|
||||
schema, err := reflector.Reflect(v1alpha1.Casting{})
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("gen.schemas"), err))
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath.Join("docs", "schemas", "v1alpha1.yaml"), types.MustMarshalYAML(schema), 0644)
|
||||
if err != nil {
|
||||
tracker.Track(ctx, ledger.WithError(ledger.CommandProperties("gen.schemas"), err))
|
||||
return err
|
||||
}
|
||||
|
||||
tracker.Track(ctx, ledger.WithSuccess(ledger.CommandProperties("gen.schemas")))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
|
||||
foundryerrors "github.com/signoz/foundry/internal/errors"
|
||||
"github.com/signoz/foundry/internal/instrumentation"
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
"github.com/signoz/foundry/internal/ledger/noopledger"
|
||||
"github.com/signoz/foundry/internal/ledger/segmentledger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -29,6 +32,7 @@ func main() {
|
||||
registerCastCmd(rootCmd)
|
||||
registerGenCmd(rootCmd)
|
||||
registerCatalogCmd(rootCmd)
|
||||
registerVersionCmd(rootCmd)
|
||||
|
||||
logger := instrumentation.NewLogger(false)
|
||||
|
||||
@@ -37,3 +41,19 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// newTracker creates a Ledger based on config.
|
||||
// Returns a no-op ledger when --no-ledger is passed.
|
||||
func newTracker() ledger.Ledger {
|
||||
config := ledger.NewConfig()
|
||||
if commonCfg.NoLedger {
|
||||
config.Enabled = false
|
||||
}
|
||||
|
||||
switch config.Provider() {
|
||||
case "segment":
|
||||
return segmentledger.New(config)
|
||||
default:
|
||||
return noopledger.New()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/signoz/foundry/internal/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versionCfg versionConfig
|
||||
|
||||
type versionConfig struct {
|
||||
Short bool
|
||||
}
|
||||
|
||||
func (c *versionConfig) RegisterFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVar(&c.Short, "short", false, "Print version in a single line")
|
||||
}
|
||||
|
||||
func registerVersionCmd(rootCmd *cobra.Command) {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version information",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if versionCfg.Short {
|
||||
_, _ = fmt.Fprintln(os.Stdout, version.Info.Short())
|
||||
return
|
||||
}
|
||||
version.Info.PrettyPrint()
|
||||
},
|
||||
}
|
||||
|
||||
versionCfg.RegisterFlags(versionCmd)
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Ledger
|
||||
|
||||
Foundryctl maintains an anonymous usage ledger to help the SigNoz team understand how the tool is used, identify common errors, and prioritize improvements. **No personally identifiable information (PII) is collected.**
|
||||
|
||||
## What is collected
|
||||
|
||||
Each command execution sends a single `foundryctl` event with the following properties:
|
||||
|
||||
| Property | Description | Example |
|
||||
|---|---|---|
|
||||
| `command` | The command that was run | `forge`, `cast` |
|
||||
| `platform` | Deployment platform from casting.yaml | `aws`, `docker`, `linux` |
|
||||
| `mode` | Deployment mode | `docker`, `systemd`, `kubernetes` |
|
||||
| `flavor` | Deployment flavor | `compose`, `binary`, `helm` |
|
||||
| `patches_configured` | Whether patches are defined | `true` / `false` |
|
||||
| `patch_count` | Number of patch entries | `0`, `2` |
|
||||
| `infrastructure_enabled` | Whether IaC generation is enabled | `true` / `false` |
|
||||
| `metastore_kind` | MetaStore backend type | `postgres`, `sqlite` |
|
||||
| `telemetry_store_kind` | TelemetryStore backend type | `clickhouse` |
|
||||
| `telemetry_keeper_kind` | TelemetryKeeper backend type | `clickhousekeeper` |
|
||||
| `success` | Whether the command succeeded | `true` / `false` |
|
||||
| `error` | Error message (on failure only) | `missing tool: docker` |
|
||||
| `os` | Operating system | `linux`, `darwin` |
|
||||
| `arch` | CPU architecture | `amd64`, `arm64` |
|
||||
| `foundry_version` | foundryctl version | `0.1.0` |
|
||||
|
||||
### Identity
|
||||
|
||||
Events are attributed to the machine hostname as an anonymous identifier. No usernames, emails, IP addresses, or file contents are sent.
|
||||
|
||||
## Tracked commands
|
||||
|
||||
All commands send the same `foundryctl` event, differentiated by the `command` property:
|
||||
|
||||
- `gauge`
|
||||
- `forge`
|
||||
- `cast`
|
||||
- `gen.examples`
|
||||
- `gen.schemas`
|
||||
- `catalog`
|
||||
|
||||
## How to disable the ledger
|
||||
|
||||
### Per-command
|
||||
|
||||
Use the `--no-ledger` flag on any command:
|
||||
|
||||
```bash
|
||||
foundryctl forge --no-ledger
|
||||
foundryctl --no-ledger cast
|
||||
```
|
||||
@@ -6,6 +6,7 @@ require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/evanphx/json-patch/v5 v5.9.11
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/segmentio/analytics-go/v3 v3.3.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/swaggest/jsonschema-go v0.3.79
|
||||
@@ -28,6 +29,7 @@ require (
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.10.0 // indirect
|
||||
@@ -91,6 +93,7 @@ require (
|
||||
github.com/rubenv/sql-migrate v1.8.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/segmentio/backo-go v1.0.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
|
||||
@@ -26,6 +26,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/bool64/dev v0.2.43 h1:yQ7qiZVef6WtCl2vDYU0Y+qSq+0aBrQzY8KXkklk9cQ=
|
||||
github.com/bool64/dev v0.2.43/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
|
||||
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
|
||||
@@ -256,6 +258,10 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/segmentio/analytics-go/v3 v3.3.0 h1:8VOMaVGBW03pdBrj1CMFfY9o/rnjJC+1wyQHlVxjw5o=
|
||||
github.com/segmentio/analytics-go/v3 v3.3.0/go.mod h1:p8owAF8X+5o27jmvUognuXxdtqvSGtD0ZrfY2kcS9bE=
|
||||
github.com/segmentio/backo-go v1.0.0 h1:kbOAtGJY2DqOR0jfRkYEorx/b18RgtepGtY3+Cpe6qA=
|
||||
github.com/segmentio/backo-go v1.0.0/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package ledger
|
||||
|
||||
import "github.com/signoz/foundry/api/v1alpha1"
|
||||
|
||||
// key is the Segment write key, set via ldflags at build time.
|
||||
// Example: -ldflags "-X github.com/signoz/foundry/internal/ledger.key=<key>".
|
||||
var key string = "<unset>"
|
||||
|
||||
// Config holds ledger configuration.
|
||||
type Config struct {
|
||||
Enabled bool
|
||||
Segment Segment
|
||||
}
|
||||
|
||||
// Segment holds Segment-specific configuration.
|
||||
type Segment struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
// NewConfig returns the default ledger configuration.
|
||||
// The Segment write key is populated from the ldflags-injected value.
|
||||
func NewConfig() Config {
|
||||
return Config{
|
||||
Enabled: true,
|
||||
Segment: Segment{
|
||||
Key: key,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Provider returns the provider name based on the configuration.
|
||||
func (c Config) Provider() string {
|
||||
if c.Enabled {
|
||||
return "segment"
|
||||
}
|
||||
return "noop"
|
||||
}
|
||||
|
||||
// Property keys for casting details.
|
||||
const (
|
||||
PropCommand = "command"
|
||||
PropPlatform = "platform"
|
||||
PropMode = "mode"
|
||||
PropFlavor = "flavor"
|
||||
PropPatchesConfigured = "patches_configured"
|
||||
PropPatchCount = "patch_count"
|
||||
PropInfrastructureEnabled = "infrastructure_enabled"
|
||||
PropMetaStoreKind = "metastore_kind"
|
||||
PropTelemetryStoreKind = "telemetry_store_kind"
|
||||
PropTelemetryKeeperKind = "telemetry_keeper_kind"
|
||||
PropSuccess = "success"
|
||||
PropError = "error"
|
||||
)
|
||||
|
||||
// CommandProperties returns a minimal property map with just the command name.
|
||||
func CommandProperties(command string) map[string]any {
|
||||
return map[string]any{
|
||||
PropCommand: command,
|
||||
}
|
||||
}
|
||||
|
||||
// CastingProperties extracts trackable properties from a Casting config.
|
||||
func CastingProperties(command string, casting v1alpha1.Casting) map[string]any {
|
||||
return map[string]any{
|
||||
PropCommand: command,
|
||||
PropPlatform: casting.Spec.Deployment.Platform,
|
||||
PropMode: casting.Spec.Deployment.Mode,
|
||||
PropFlavor: casting.Spec.Deployment.Flavor,
|
||||
PropPatchesConfigured: len(casting.Spec.Patches) > 0,
|
||||
PropPatchCount: len(casting.Spec.Patches),
|
||||
PropInfrastructureEnabled: casting.Spec.Infrastructure.Enabled,
|
||||
PropMetaStoreKind: casting.Spec.MetaStore.Kind.String(),
|
||||
PropTelemetryStoreKind: casting.Spec.TelemetryStore.Kind.String(),
|
||||
PropTelemetryKeeperKind: casting.Spec.TelemetryKeeper.Kind.String(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithSuccess adds success=true to the properties.
|
||||
func WithSuccess(props map[string]any) map[string]any {
|
||||
props[PropSuccess] = true
|
||||
return props
|
||||
}
|
||||
|
||||
// WithError adds success=false and the error message to the properties.
|
||||
func WithError(props map[string]any, err error) map[string]any {
|
||||
props[PropSuccess] = false
|
||||
props[PropError] = err.Error()
|
||||
return props
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Package ledger provides anonymous usage tracking for foundryctl commands.
|
||||
package ledger
|
||||
|
||||
import "context"
|
||||
|
||||
// Ledger is the interface for tracking CLI usage events.
|
||||
type Ledger interface {
|
||||
// Track records a single foundryctl event with the given properties.
|
||||
Track(ctx context.Context, properties map[string]any)
|
||||
|
||||
// Close flushes any pending events and releases resources.
|
||||
Close() error
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package noopledger
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
)
|
||||
|
||||
// provider is a no-op implementation of ledger.Ledger.
|
||||
type provider struct{}
|
||||
|
||||
// New creates a ledger that does nothing.
|
||||
func New() ledger.Ledger {
|
||||
return &provider{}
|
||||
}
|
||||
|
||||
func (p *provider) Track(_ context.Context, _ map[string]any) {}
|
||||
func (p *provider) Close() error { return nil }
|
||||
@@ -0,0 +1,69 @@
|
||||
package segmentledger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
segment "github.com/segmentio/analytics-go/v3"
|
||||
"github.com/signoz/foundry/internal/ledger"
|
||||
"github.com/signoz/foundry/internal/ledger/noopledger"
|
||||
"github.com/signoz/foundry/internal/version"
|
||||
)
|
||||
|
||||
// provider implements ledger.Ledger using Segment.
|
||||
type provider struct {
|
||||
client segment.Client
|
||||
}
|
||||
|
||||
// New creates a new Segment ledger provider.
|
||||
// Returns a noop provider if the write key is not set.
|
||||
func New(config ledger.Config) ledger.Ledger {
|
||||
if config.Segment.Key == "" || config.Segment.Key == "<unset>" {
|
||||
return noopledger.New()
|
||||
}
|
||||
|
||||
client, err := segment.NewWithConfig(config.Segment.Key, segment.Config{})
|
||||
if err != nil {
|
||||
return noopledger.New()
|
||||
}
|
||||
|
||||
return &provider{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *provider) Track(_ context.Context, properties map[string]any) {
|
||||
if properties == nil {
|
||||
properties = make(map[string]any)
|
||||
}
|
||||
|
||||
properties["os"] = runtime.GOOS
|
||||
properties["arch"] = runtime.GOARCH
|
||||
properties["foundry_version"] = version.Info.Version()
|
||||
|
||||
props := segment.NewProperties()
|
||||
for k, v := range properties {
|
||||
props.Set(k, v)
|
||||
}
|
||||
|
||||
_ = p.client.Enqueue(segment.Track{
|
||||
AnonymousId: getDistinctID(),
|
||||
Event: "foundryctl",
|
||||
Properties: props,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *provider) Close() error {
|
||||
return p.client.Close()
|
||||
}
|
||||
|
||||
// getDistinctID returns a stable anonymous identifier for the machine.
|
||||
// It uses the hostname so there is no PII stored.
|
||||
func getDistinctID() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
return hostname
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These will be set via ldflags at build time.
|
||||
var (
|
||||
version string = "<unset>"
|
||||
hash string = "<unset>"
|
||||
)
|
||||
|
||||
var (
|
||||
Info Build = Build{
|
||||
version: version,
|
||||
hash: hash,
|
||||
goVersion: runtime.Version(),
|
||||
}
|
||||
)
|
||||
|
||||
// Build contains information about the build environment.
|
||||
type Build struct {
|
||||
// The version of the current build.
|
||||
version string
|
||||
|
||||
// The git hash of the current build.
|
||||
hash string
|
||||
|
||||
// The version of go.
|
||||
goVersion string
|
||||
}
|
||||
|
||||
func (b Build) Version() string {
|
||||
return b.version
|
||||
}
|
||||
|
||||
func (b Build) Hash() string {
|
||||
return b.hash
|
||||
}
|
||||
|
||||
func (b Build) GoVersion() string {
|
||||
return b.goVersion
|
||||
}
|
||||
|
||||
func (b Build) Short() string {
|
||||
return fmt.Sprintf("foundryctl %s (%s) %s", b.version, b.hash, b.goVersion)
|
||||
}
|
||||
|
||||
func (b Build) PrettyPrint() {
|
||||
year := time.Now().Year()
|
||||
ascii := []string{
|
||||
"███████╗ ██████╗ ██╗ ██╗███╗ ██╗██████╗ ██████╗ ██╗ ██╗",
|
||||
"██╔════╝██╔═══██╗██║ ██║████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝",
|
||||
"█████╗ ██║ ██║██║ ██║██╔██╗ ██║██║ ██║██████╔╝ ╚████╔╝ ",
|
||||
"██╔══╝ ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══██╗ ╚██╔╝ ",
|
||||
"██║ ╚██████╔╝╚██████╔╝██║ ╚████║██████╔╝██║ ██║ ██║ ",
|
||||
"╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ",
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(os.Stdout)
|
||||
for _, line := range ascii {
|
||||
_, _ = fmt.Fprintln(os.Stdout, line)
|
||||
}
|
||||
_, _ = fmt.Fprintln(os.Stdout)
|
||||
_, _ = fmt.Fprintf(os.Stdout, " Version: %s\n", b.version)
|
||||
_, _ = fmt.Fprintf(os.Stdout, " Commit: %s\n", b.hash)
|
||||
_, _ = fmt.Fprintf(os.Stdout, " Go: %s\n", b.goVersion)
|
||||
_, _ = fmt.Fprintln(os.Stdout)
|
||||
_, _ = fmt.Fprintf(os.Stdout, " Copyright %d SigNoz, All rights reserved.\n", year)
|
||||
_, _ = fmt.Fprintln(os.Stdout)
|
||||
}
|
||||
Reference in New Issue
Block a user