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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ local-build-commands.txt

.idea

config/*test*

14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Upcoming changes...
-

## [0.7.0] - 2026-01-30
### Added
- Added database version info (`schema_version`, `created_at`) to `StatusResponse` across all component service endpoints
- Added server version to `StatusResponse`
- Log database version info on service startup
### Changed
- Moved server version from constructor parameter to `ServerConfig.App.Version`, configurable via `APP_VERSION` env var (defaults to embedded binary version)
- Log error when querying db version fails with an error other than `ErrTableNotFound`
- Updated `github.com/scanoss/go-models` to v0.3.0
- Updated `github.com/scanoss/go-grpc-helper` to v0.11.0

## [0.6.0] - 2025-09-18
### Added
- Added `name` field to component search and version response DTOs
Expand All @@ -28,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- ?

[0.7.0]: https://github.com/scanoss/components/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/scanoss/components/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/scanoss/components/compare/v0.4.0...v0.5.0
[0.0.1]: https://github.com/scanoss/components/compare/v0.0.0...v0.0.1
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
github.com/scanoss/go-grpc-helper v0.9.0
github.com/scanoss/go-grpc-helper v0.11.0
github.com/scanoss/go-models v0.4.0
github.com/scanoss/go-purl-helper v0.2.1
github.com/scanoss/papi v0.21.0
github.com/scanoss/papi v0.28.0
github.com/scanoss/zap-logging-helper v0.4.0
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/metric v1.38.0
Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -616,14 +616,16 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/scanoss/go-grpc-helper v0.9.0 h1:lK9JtKtyOqR9XxjnYR0wbW5KCNDH82P1E1LJwwP5Xb4=
github.com/scanoss/go-grpc-helper v0.9.0/go.mod h1:EPI1NBg+DJ+krWehxC9eHyNpM5Pii5odOJcNdNG9qA0=
github.com/scanoss/go-grpc-helper v0.11.0 h1:DifUX7KrQObTo9ta/vc4vqSzAdDEy1yNl+zWKuX5iOc=
github.com/scanoss/go-grpc-helper v0.11.0/go.mod h1:p2lhQTs6X5Y4E2F50qG6DbGpATtX/YYMycEcFwo9XVE=
github.com/scanoss/go-models v0.4.0 h1:TPAWgFzseChYe12RHVcsfdouZH8AleiPphKA7TwOd04=
github.com/scanoss/go-models v0.4.0/go.mod h1:Dq8ag9CI/3h0sqDWYUrTjW/jO8l5L6oopWJRKtJxzqA=
github.com/scanoss/go-purl-helper v0.2.1 h1:jp960a585ycyJSlqZky1NatMJBIQi/JGITDfNSu/9As=
github.com/scanoss/go-purl-helper v0.2.1/go.mod h1:v20/bKD8G+vGrILdiq6r0hyRD2bO8frCJlu9drEcQ38=
github.com/scanoss/ipfilter/v2 v2.0.2 h1:GaB9i8kVJg9JQZm5XGStYkEpiaCVdsrj7ezI2wV/oh8=
github.com/scanoss/ipfilter/v2 v2.0.2/go.mod h1:AwrpX4XGbZ7EKISMi1d6E5csBk1nWB8+ugpvXHFcTpA=
github.com/scanoss/papi v0.21.0 h1:aVt0q9pxaPHMq3QsFFnlIXju1NYpou/ziweWdIIkkPs=
github.com/scanoss/papi v0.21.0/go.mod h1:Z4E/4IpwYdzHHRJXTgBCGG1GjksgrFjNW5cvhbKUfeU=
github.com/scanoss/papi v0.28.0 h1:uvevFYoxwzvSH1hvgBoAkScIGTK2U1+rLzHSoJdnARk=
github.com/scanoss/papi v0.28.0/go.mod h1:Z4E/4IpwYdzHHRJXTgBCGG1GjksgrFjNW5cvhbKUfeU=
github.com/scanoss/zap-logging-helper v0.4.0 h1:2qTYoaFa9+MlD2/1wmPtiDHfh+42NIEwgKVU3rPpl0Y=
github.com/scanoss/zap-logging-helper v0.4.0/go.mod h1:9QuEZcq73g/0Izv1tWeOWukoIK0oTBzM4jSNQ5kRR1w=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
Expand Down
23 changes: 21 additions & 2 deletions pkg/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/scanoss/go-grpc-helper/pkg/files"
gd "github.com/scanoss/go-grpc-helper/pkg/grpc/database"
gs "github.com/scanoss/go-grpc-helper/pkg/grpc/server"
gomodels "github.com/scanoss/go-models/pkg/models"
zlog "github.com/scanoss/zap-logging-helper/pkg/logger"
_ "modernc.org/sqlite"
"net/http"
Expand All @@ -38,6 +39,9 @@ import (
"strings"
)

//TODO: Now the config includes the app version.
// This might be worth moving to the file pkg/config/server_config.go

//go:generate bash ../../get_version.sh
//go:embed version.txt
var version string
Expand Down Expand Up @@ -96,7 +100,11 @@ func RunServer() error {
return err
}

zlog.S.Infof("Starting SCANOSS Component Service: %v", strings.TrimSpace(version))
// Set the default version from the embedded binary version if not overridden by config/env
if len(cfg.App.Version) == 0 {
cfg.App.Version = strings.TrimSpace(version)
}
zlog.S.Infof("Starting SCANOSS Component Service: %v", cfg.App.Version)

// Setup database connection pool
db, err := gd.OpenDBConnection(cfg.Database.Dsn, cfg.Database.Driver, cfg.Database.User, cfg.Database.Passwd,
Expand All @@ -108,6 +116,17 @@ func RunServer() error {
return err
}
defer gd.CloseDBConnection(db)
// Log database version info
dbVersionModel := gomodels.NewDBVersionModel(db)
dbVersion, dbVersionErr := dbVersionModel.GetCurrentVersion(context.Background())
if dbVersionErr != nil {
zlog.S.Warnf("Could not read db_version table: %v", dbVersionErr)
} else if len(dbVersion.SchemaVersion) > 0 {
zlog.S.Infof("Loaded decoration DB: package=%s, schema=%s, created_at=%s",
dbVersion.PackageName, dbVersion.SchemaVersion, dbVersion.CreatedAt)
} else {
zlog.S.Warn("db_version table is empty")
}
// Setup dynamic logging (if necessary)
zlog.SetupAppDynamicLogging(cfg.Logging.DynamicPort, cfg.Logging.DynamicLogging)
// Register the component service
Expand All @@ -121,7 +140,7 @@ func RunServer() error {
}
}
// Start the gRPC service
server, err := grpc.RunServer(cfg, v2API, cfg.App.GRPCPort, allowedIPs, deniedIPs, startTLS, version)
server, err := grpc.RunServer(cfg, v2API, cfg.App.GRPCPort, allowedIPs, deniedIPs, startTLS)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/config/server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
type ServerConfig struct {
App struct {
Name string `env:"APP_NAME"`
Version string `env:"APP_VERSION"`
GRPCPort string `env:"APP_PORT"`
RESTPort string `env:"REST_PORT"`
Debug bool `env:"APP_DEBUG"` // true/false
Expand Down
4 changes: 2 additions & 2 deletions pkg/protocol/grpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ import (

// RunServer runs gRPC service to publish.
func RunServer(config *myconfig.ServerConfig, v2API pb.ComponentsServer, port string,
allowedIPs, deniedIPs []string, startTLS bool, version string) (*grpc.Server, error) {
allowedIPs, deniedIPs []string, startTLS bool) (*grpc.Server, error) {
// Start up Open Telemetry is requested
var oltpShutdown = func() {}
if config.Telemetry.Enabled {
var err error
oltpShutdown, err = otel.InitTelemetryProviders(config.App.Name, "scanoss-components", version,
oltpShutdown, err = otel.InitTelemetryProviders(config.App.Name, "scanoss-components", config.App.Version,
config.Telemetry.OltpExporter, otel.GetTraceSampler(config.App.Mode), false)
if err != nil {
return nil, err
Expand Down
98 changes: 80 additions & 18 deletions pkg/service/component_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,34 @@ package service

import (
"context"
"errors"
"time"

"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/jmoiron/sqlx"
"github.com/scanoss/go-grpc-helper/pkg/grpc/database"
gomodels "github.com/scanoss/go-models/pkg/models"
common "github.com/scanoss/papi/api/commonv2"
pb "github.com/scanoss/papi/api/componentsv2"
myconfig "scanoss.com/components/pkg/config"
se "scanoss.com/components/pkg/errors"
"scanoss.com/components/pkg/usecase"
"time"
)

type componentServer struct {
pb.ComponentsServer
db *sqlx.DB
config *myconfig.ServerConfig
db *sqlx.DB
config *myconfig.ServerConfig
dbVersionModel *gomodels.DBVersionModel
}

func NewComponentServer(db *sqlx.DB, config *myconfig.ServerConfig) pb.ComponentsServer {
setupMetrics()
return &componentServer{db: db, config: config}
return &componentServer{
db: db,
config: config,
dbVersionModel: gomodels.NewDBVersionModel(db),
}
}

// Echo sends back the same message received
Expand All @@ -54,30 +62,47 @@ func (d componentServer) SearchComponents(ctx context.Context, request *pb.CompS
s := ctxzap.Extract(ctx).Sugar()
s.Info("Processing component name request...")
if len(request.Search) == 0 && len(request.Component) == 0 && len(request.Vendor) == 0 {
return &pb.CompSearchResponse{Status: se.HandleServiceError(ctx, s, se.NewBadRequestError("No data supplied", nil))}, nil
status := se.HandleServiceError(ctx, s, se.NewBadRequestError("No data supplied", nil))
status.Db = d.getDBVersion()
status.Server = &common.StatusResponse_Server{Version: d.config.App.Version}
return &pb.CompSearchResponse{Status: status}, nil
}
dtoRequest, err := convertSearchComponentInput(s, request) // Convert to internal DTO for processing
if err != nil {
return &pb.CompSearchResponse{Status: se.HandleServiceError(ctx, s, err)}, nil
status := se.HandleServiceError(ctx, s, err)
status.Db = d.getDBVersion()
status.Server = &common.StatusResponse_Server{Version: d.config.App.Version}
return &pb.CompSearchResponse{Status: status}, nil
}

// Search the KB for information about the components
compUc := usecase.NewComponents(ctx, s, d.db, database.NewDBSelectContext(s, d.db, nil, d.config.Database.Trace))
dtoComponents, err := compUc.SearchComponents(dtoRequest)
if err != nil {
return &pb.CompSearchResponse{Status: se.HandleServiceError(ctx, s, err)}, nil
status := se.HandleServiceError(ctx, s, err)
status.Db = d.getDBVersion()
status.Server = &common.StatusResponse_Server{Version: d.config.App.Version}
return &pb.CompSearchResponse{Status: status}, nil
}
s.Debugf("Parsed Components: %+v", dtoComponents)
componentsResponse, err := convertSearchComponentOutput(s, dtoComponents) // Convert the internal data into a response object
if err != nil {
s.Errorf("Failed to convert parsed components: %v", err)
statusResp := common.StatusResponse{Status: common.StatusCode_FAILED, Message: "Problems encountered extracting components data"}
return &pb.CompSearchResponse{Status: &statusResp}, nil
return &pb.CompSearchResponse{Status: &common.StatusResponse{
Status: common.StatusCode_FAILED,
Message: "Problems encountered extracting components data",
Db: d.getDBVersion(),
Server: &common.StatusResponse_Server{Version: d.config.App.Version},
}}, nil
}
telemetryCompNameRequestTime(ctx, d.config, requestStartTime) // Record the request processing time
// Set the status and respond with the data
statusResp := common.StatusResponse{Status: common.StatusCode_SUCCESS, Message: "Success"}
return &pb.CompSearchResponse{Components: componentsResponse.Components, Status: &statusResp}, nil
return &pb.CompSearchResponse{Components: componentsResponse.Components, Status: &common.StatusResponse{
Status: common.StatusCode_SUCCESS,
Message: "Success",
Db: d.getDBVersion(),
Server: &common.StatusResponse_Server{Version: d.config.App.Version},
}}, nil
}

func (d componentServer) GetComponentVersions(ctx context.Context, request *pb.CompVersionRequest) (*pb.CompVersionResponse, error) {
Expand All @@ -87,30 +112,47 @@ func (d componentServer) GetComponentVersions(ctx context.Context, request *pb.C
s.Info("Processing component versions request...")
//Verify the input request
if len(request.Purl) == 0 {
return &pb.CompVersionResponse{Status: se.HandleServiceError(ctx, s, se.NewBadRequestError("No purl supplied", nil))}, nil
status := se.HandleServiceError(ctx, s, se.NewBadRequestError("No purl supplied", nil))
status.Db = d.getDBVersion()
status.Server = &common.StatusResponse_Server{Version: d.config.App.Version}
return &pb.CompVersionResponse{Status: status}, nil
}
//Convert the request to internal DTO
dtoRequest, err := convertCompVersionsInput(s, request)
if err != nil {
return &pb.CompVersionResponse{Status: se.HandleServiceError(ctx, s, err)}, nil
status := se.HandleServiceError(ctx, s, err)
status.Db = d.getDBVersion()
status.Server = &common.StatusResponse_Server{Version: d.config.App.Version}
return &pb.CompVersionResponse{Status: status}, nil
}
// Creates the use case
compUc := usecase.NewComponents(ctx, s, d.db, database.NewDBSelectContext(s, d.db, nil, d.config.Database.Trace))
dtoOutput, err := compUc.GetComponentVersions(dtoRequest)
if err != nil {
return &pb.CompVersionResponse{Status: se.HandleServiceError(ctx, s, err)}, nil
status := se.HandleServiceError(ctx, s, err)
status.Db = d.getDBVersion()
status.Server = &common.StatusResponse_Server{Version: d.config.App.Version}
return &pb.CompVersionResponse{Status: status}, nil
}

reqResponse, err := convertCompVersionsOutput(s, dtoOutput)
if err != nil {
s.Errorf("Failed to convert parsed components: %v", err)
statusResp := common.StatusResponse{Status: common.StatusCode_FAILED, Message: "Problems encountered extracting components data"}
return &pb.CompVersionResponse{Status: &statusResp}, nil
return &pb.CompVersionResponse{Status: &common.StatusResponse{
Status: common.StatusCode_FAILED,
Message: "Problems encountered extracting components data",
Db: d.getDBVersion(),
Server: &common.StatusResponse_Server{Version: d.config.App.Version},
}}, nil
}
telemetryCompVersionRequestTime(ctx, d.config, requestStartTime)
// Set the status and respond with the data
statusResp := common.StatusResponse{Status: common.StatusCode_SUCCESS, Message: "Success"}
return &pb.CompVersionResponse{Component: reqResponse.Component, Status: &statusResp}, nil
return &pb.CompVersionResponse{Component: reqResponse.Component, Status: &common.StatusResponse{
Status: common.StatusCode_SUCCESS,
Message: "Success",
Db: d.getDBVersion(),
Server: &common.StatusResponse_Server{Version: d.config.App.Version},
}}, nil
}

// telemetryCompNameRequestTime records the name request time to telemetry.
Expand All @@ -128,3 +170,23 @@ func telemetryCompVersionRequestTime(ctx context.Context, config *myconfig.Serve
oltpMetrics.compVersionHistogram.Record(ctx, elapsedTime) // Record dep request time
}
}

// getDBVersion fetches the database version from the db_version table.
// Returns nil if the table doesn't exist or query fails (backward compatibility).
func (d componentServer) getDBVersion() *common.StatusResponse_DB {
dbVersion, err := d.dbVersionModel.GetCurrentVersion(context.Background())
if err != nil {
if !errors.Is(err, gomodels.ErrTableNotFound) {
s := ctxzap.Extract(context.Background()).Sugar()
s.Errorf("Failed to get db version: %v", err)
}
return nil
}
if len(dbVersion.SchemaVersion) == 0 {
return nil
}
return &common.StatusResponse_DB{
SchemaVersion: dbVersion.SchemaVersion,
CreatedAt: dbVersion.CreatedAt,
}
}
15 changes: 9 additions & 6 deletions pkg/service/component_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func TestComponentServer_Echo(t *testing.T) {
if err != nil {
t.Fatalf("failed to load Config: %v", err)
}
myConfig.App.Version = "test-version"
s := NewComponentServer(db, myConfig)

type args struct {
Expand Down Expand Up @@ -106,6 +107,7 @@ func TestComponentServer_SearchComponents(t *testing.T) {
if err != nil {
t.Fatalf("failed to load Config: %v", err)
}
myConfig.App.Version = "test-version"
s := NewComponentServer(db, myConfig)

var compRequestData = `{
Expand Down Expand Up @@ -137,7 +139,7 @@ func TestComponentServer_SearchComponents(t *testing.T) {
ctx: ctx,
req: &compReq,
},
want: &pb.CompSearchResponse{Status: &common.StatusResponse{Status: common.StatusCode_FAILED, Message: "No components found matching the search criteria"}},
want: &pb.CompSearchResponse{Status: &common.StatusResponse{Status: common.StatusCode_FAILED, Message: "No components found matching the search criteria", Server: &common.StatusResponse_Server{Version: "test-version"}}},
},
{
name: "Search for a empty request",
Expand All @@ -146,7 +148,7 @@ func TestComponentServer_SearchComponents(t *testing.T) {
ctx: ctx,
req: &pb.CompSearchRequest{},
},
want: &pb.CompSearchResponse{Status: &common.StatusResponse{Status: common.StatusCode_FAILED, Message: "No data supplied"}},
want: &pb.CompSearchResponse{Status: &common.StatusResponse{Status: common.StatusCode_FAILED, Message: "No data supplied", Server: &common.StatusResponse_Server{Version: "test-version"}}},
wantErr: false,
},
}
Expand All @@ -159,7 +161,7 @@ func TestComponentServer_SearchComponents(t *testing.T) {
return
}
if err == nil && !reflect.DeepEqual(got.Status, tt.want.Status) {
t.Errorf("service.SearchComponents() = %v, want %v", got, tt.want)
t.Errorf("service.SearchComponents() status = %v, want %v", got.Status, tt.want.Status)
}
})
}
Expand All @@ -186,6 +188,7 @@ func TestComponentServer_GetComponentVersions(t *testing.T) {
if err != nil {
t.Fatalf("failed to load Config: %v", err)
}
myConfig.App.Version = "test-version"
s := NewComponentServer(db, myConfig)

var compVersionRequestData = `{
Expand Down Expand Up @@ -216,7 +219,7 @@ func TestComponentServer_GetComponentVersions(t *testing.T) {
ctx: ctx,
req: &compVersionReq,
},
want: &pb.CompVersionResponse{Status: &common.StatusResponse{Status: common.StatusCode_SUCCESS, Message: "Success"}},
want: &pb.CompVersionResponse{Status: &common.StatusResponse{Status: common.StatusCode_SUCCESS, Message: "Success", Server: &common.StatusResponse_Server{Version: "test-version"}}},
},
{
name: "Search for a empty request",
Expand All @@ -225,7 +228,7 @@ func TestComponentServer_GetComponentVersions(t *testing.T) {
ctx: ctx,
req: &pb.CompVersionRequest{},
},
want: &pb.CompVersionResponse{Status: &common.StatusResponse{Status: common.StatusCode_FAILED, Message: "No purl supplied"}},
want: &pb.CompVersionResponse{Status: &common.StatusResponse{Status: common.StatusCode_FAILED, Message: "No purl supplied", Server: &common.StatusResponse_Server{Version: "test-version"}}},
wantErr: false,
},
}
Expand All @@ -238,7 +241,7 @@ func TestComponentServer_GetComponentVersions(t *testing.T) {
return
}
if err == nil && !reflect.DeepEqual(got.Status, tt.want.Status) {
t.Errorf("service.SearchComponents() = %v, want %v", got, tt.want)
t.Errorf("service.GetComponentVersions() status = %v, want %v", got.Status, tt.want.Status)
}
})
}
Expand Down
Loading