Skip to content

Commit 7675e7e

Browse files
committed
refactor: Centralize Application Metadata in AppContext
Migrating from hardcoded constants to a context-driven architecture where `AppContext` serves as the single source of truth for application metadata. Assisted-by: Claude
1 parent d8f8f12 commit 7675e7e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+478
-247
lines changed

cmd/tssc/main.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"os"
66

77
"github.com/redhat-appstudio/tssc-cli/installer"
8+
"github.com/redhat-appstudio/tssc-cli/pkg/api"
89
"github.com/redhat-appstudio/tssc-cli/pkg/chartfs"
9-
"github.com/redhat-appstudio/tssc-cli/pkg/constants"
1010
"github.com/redhat-appstudio/tssc-cli/pkg/framework"
1111
"github.com/redhat-appstudio/tssc-cli/pkg/subcmd"
1212
)
@@ -34,22 +34,26 @@ func main() {
3434
ofs := chartfs.NewOverlayFS(tfs, os.DirFS(cwd))
3535
cfs := chartfs.New(ofs)
3636

37-
// Compute TSSC-specific MCP server image based on build-time commit ID
37+
// Create TSSC-specific application context (metadata/configuration).
38+
appCtx := api.NewAppContext(
39+
"tssc",
40+
api.WithVersion(version),
41+
api.WithCommitID(commitID),
42+
api.WithShortDescription("Trusted Software Supply Chain CLI"),
43+
)
44+
45+
// TSSC-specific MCP server image based on build-time commit ID.
3846
mcpImage := "quay.io/redhat-user-workloads/rhtap-shared-team-tenant/tssc-cli"
3947
if commitID == "" {
4048
mcpImage = fmt.Sprintf("%s:latest", mcpImage)
4149
} else {
4250
mcpImage = fmt.Sprintf("%s:%s", mcpImage, commitID)
4351
}
4452

45-
// Creating a new TSSC application instance using all standard integration
46-
// modules.
53+
// Create application runtime with context and dependencies.
4754
app, err := framework.NewApp(
48-
constants.AppName,
55+
appCtx,
4956
cfs,
50-
framework.WithVersion(version),
51-
framework.WithCommitID(commitID),
52-
framework.WithShortDescription("Trusted Software Supply Chain CLI"),
5357
framework.WithIntegrations(subcmd.StandardModules()...),
5458
framework.WithMCPImage(mcpImage),
5559
)

pkg/api/appcontext.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package api
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// AppContext holds immutable application metadata.
8+
// This is passed throughout the component tree as the single source of truth
9+
// for application identity, versioning, and organizational information.
10+
//
11+
// AppContext is separated from App to distinguish between configuration
12+
// (what the app is) and runtime (how the app runs).
13+
type AppContext struct {
14+
Name string // application name
15+
Version string // application version
16+
CommitID string // git commit ID
17+
Namespace string // default installation namespace
18+
OrgName string // organization name
19+
Domain string // organization domain
20+
Short string // short description for CLI
21+
Long string // long description for CLI
22+
}
23+
24+
// RepoURI returns the reverse repository URI.
25+
func (a *AppContext) RepoURI() string {
26+
return fmt.Sprintf("%s.%s.%s", a.Name, a.OrgName, a.Domain)
27+
}
28+
29+
// ContextOption is a functional option for configuring AppContext.
30+
type ContextOption func(*AppContext)
31+
32+
// WithNamespace sets the default installation namespace.
33+
func WithNamespace(namespace string) ContextOption {
34+
return func(a *AppContext) {
35+
a.Namespace = namespace
36+
}
37+
}
38+
39+
// WithOrganization sets the organization name.
40+
func WithOrganization(org string) ContextOption {
41+
return func(a *AppContext) {
42+
a.OrgName = org
43+
}
44+
}
45+
46+
// WithDomain sets the domain.
47+
func WithDomain(domain string) ContextOption {
48+
return func(a *AppContext) {
49+
a.Domain = domain
50+
}
51+
}
52+
53+
// WithVersion sets the application version.
54+
func WithVersion(version string) ContextOption {
55+
return func(a *AppContext) {
56+
a.Version = version
57+
}
58+
}
59+
60+
// WithCommitID sets the git commit ID.
61+
func WithCommitID(commitID string) ContextOption {
62+
return func(a *AppContext) {
63+
a.CommitID = commitID
64+
}
65+
}
66+
67+
// WithShortDescription sets the short CLI description.
68+
func WithShortDescription(short string) ContextOption {
69+
return func(a *AppContext) {
70+
a.Short = short
71+
}
72+
}
73+
74+
// WithLongDescription sets the long CLI description.
75+
func WithLongDescription(long string) ContextOption {
76+
return func(a *AppContext) {
77+
a.Long = long
78+
}
79+
}
80+
81+
// NewAppContext creates a new application context with sensible defaults.
82+
// The only required parameter is the application name; all other fields
83+
// can be configured via functional options.
84+
func NewAppContext(name string, opts ...ContextOption) *AppContext {
85+
appCtx := &AppContext{
86+
Name: name,
87+
Namespace: name,
88+
Domain: "github.com",
89+
OrgName: "redhat-appstudio",
90+
Version: "v0.0.0-SNAPSHOT",
91+
CommitID: "unknown",
92+
Short: "",
93+
Long: "",
94+
}
95+
for _, opt := range opts {
96+
opt(appCtx)
97+
}
98+
return appCtx
99+
}

pkg/api/constants.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package api
2+
3+
const (
4+
// ConfigFilename is the installer configuration file (framework contract).
5+
// All installers using this framework must provide this file.
6+
ConfigFilename = "config.yaml"
7+
8+
// ValuesFilename is the values template file (framework contract).
9+
// All installers using this framework must provide this file.
10+
ValuesFilename = "values.yaml.tpl"
11+
12+
// InstructionsFilename is the MCP instructions file (framework convention).
13+
// This file provides instructions for the Model Context Protocol server.
14+
InstructionsFilename = "instructions.md"
15+
)

pkg/api/types.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ type IntegrationModule struct {
1818
Init func(*slog.Logger, *k8s.Kube) integration.Interface
1919

2020
// Command creates the CLI subcommand for this integration.
21-
// It receives the initialized integration wrapper (which handles Secrets).
22-
Command func(*slog.Logger, *k8s.Kube, *integration.Integration) SubCommand
21+
// It receives the application context and initialized integration wrapper.
22+
Command func(
23+
*AppContext,
24+
*slog.Logger,
25+
*k8s.Kube,
26+
*integration.Integration,
27+
) SubCommand
2328
}

pkg/chartfs/chartfs_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"os"
55
"testing"
66

7-
"github.com/redhat-appstudio/tssc-cli/pkg/constants"
8-
97
o "github.com/onsi/gomega"
108
)
119

@@ -16,7 +14,7 @@ func TestNewChartFS(t *testing.T) {
1614
g.Expect(c).ToNot(o.BeNil())
1715

1816
t.Run("ReadFile", func(t *testing.T) {
19-
valuesTmplBytes, err := c.ReadFile(constants.ValuesFilename)
17+
valuesTmplBytes, err := c.ReadFile("values.yaml.tpl")
2018
g.Expect(err).To(o.Succeed())
2119
g.Expect(valuesTmplBytes).ToNot(o.BeEmpty())
2220
})

pkg/constants/constants.go

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,15 @@
11
package constants
22

3-
import "fmt"
4-
53
const (
6-
// AppName is the name of the application.
7-
AppName = "tssc"
8-
9-
// Namespace is the default namespace for the application.
10-
Namespace = "tssc"
11-
12-
// OrgName is the name of the organization.
13-
OrgName = "redhat-appstudio"
14-
15-
// Domain organization domain.
16-
Domain = "github.com"
17-
18-
// ConfigFilename is the name of the configuration file.
4+
// ConfigFilename is the installer configuration file (framework contract).
5+
// All installers using this framework must provide this file.
196
ConfigFilename = "config.yaml"
207

21-
// ValuesFilename is the name of the values template file.
8+
// ValuesFilename is the values template file (framework contract).
9+
// All installers using this framework must provide this file.
2210
ValuesFilename = "values.yaml.tpl"
2311

24-
// InstructionsFilename is the name of the instructions file.
12+
// InstructionsFilename is the MCP instructions file (framework convention).
13+
// This file provides instructions for the Model Context Protocol server.
2514
InstructionsFilename = "instructions.md"
2615
)
27-
28-
var (
29-
// RepoURI is the reverse repository URI for the application.
30-
RepoURI = fmt.Sprintf("%s.%s.%s", AppName, OrgName, Domain)
31-
)

pkg/framework/app.go

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@ import (
1515
"github.com/spf13/cobra"
1616
)
1717

18-
// App represents the installer application.
18+
// App represents the installer application runtime.
19+
// It holds runtime dependencies and coordinates the execution of commands.
20+
// Application metadata (name, version, etc.) is stored in AppCtx.
1921
type App struct {
20-
Name string // application name
21-
Version string // application version
22-
CommitID string // application commit ID
23-
Short string // short description
24-
Long string // long description
25-
ChartFS *chartfs.ChartFS // installer filesystem
22+
AppCtx *api.AppContext // application metadata (single source of truth)
23+
ChartFS *chartfs.ChartFS // installer filesystem
2624

2725
integrations []api.IntegrationModule // supported integrations
2826
integrationManager *integrations.Manager // integrations manager
@@ -47,15 +45,15 @@ func (a *App) Run() error {
4745
// setupRootCmd instantiates the Cobra Root command with subcommand, description,
4846
// Kubernetes API client instance and more.
4947
func (a *App) setupRootCmd() error {
50-
short := a.Short
48+
short := a.AppCtx.Short
5149
if short == "" {
52-
short = a.Name + " installer"
50+
short = a.AppCtx.Name + " installer"
5351
}
5452

5553
a.rootCmd = &cobra.Command{
56-
Use: a.Name,
54+
Use: a.AppCtx.Name,
5755
Short: short,
58-
Long: a.Long,
56+
Long: a.AppCtx.Long,
5957
SilenceUsage: true,
6058
}
6159

@@ -65,7 +63,8 @@ func (a *App) setupRootCmd() error {
6563
// Handle version flag and help.
6664
a.rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
6765
if a.flags.Version {
68-
a.flags.ShowVersion(a.Name, a.Version, a.CommitID)
66+
a.flags.ShowVersion(
67+
a.AppCtx.Name, a.AppCtx.Version, a.AppCtx.CommitID)
6968
return nil
7069
}
7170
return cmd.Help()
@@ -76,14 +75,14 @@ func (a *App) setupRootCmd() error {
7675
// Loading informed integrations into the manager.
7776
a.integrationManager = integrations.NewManager()
7877
if err := a.integrationManager.LoadModules(
79-
a.Name, logger, a.kube, a.integrations,
78+
a.AppCtx.Name, logger, a.kube, a.integrations,
8079
); err != nil {
8180
return fmt.Errorf("failed to load modules: %w", err)
8281
}
8382

8483
// Register standard subcommands.
8584
a.rootCmd.AddCommand(subcmd.NewIntegration(
86-
a.Name, logger, a.kube, a.ChartFS, a.integrationManager,
85+
a.AppCtx, logger, a.kube, a.ChartFS, a.integrationManager,
8786
))
8887

8988
// Use default builder if none provided.
@@ -100,32 +99,67 @@ func (a *App) setupRootCmd() error {
10099

101100
// Other subcommands via api.Runner.
102101
subs := []api.SubCommand{
103-
subcmd.NewConfig(logger, a.flags, a.ChartFS, a.kube),
104-
subcmd.NewDeploy(logger, a.flags, a.ChartFS, a.kube, a.integrationManager),
105-
subcmd.NewInstaller(a.flags),
102+
subcmd.NewConfig(
103+
a.AppCtx,
104+
logger,
105+
a.flags,
106+
a.ChartFS,
107+
a.kube,
108+
),
109+
subcmd.NewDeploy(
110+
a.AppCtx,
111+
logger,
112+
a.flags,
113+
a.ChartFS,
114+
a.kube,
115+
a.integrationManager,
116+
),
117+
subcmd.NewInstaller(
118+
a.AppCtx,
119+
a.flags,
120+
),
106121
subcmd.NewMCPServer(
107-
a.Name,
122+
a.AppCtx,
108123
a.flags,
109124
a.ChartFS,
110125
a.kube,
111126
a.integrationManager,
112127
mcpBuilder,
113128
a.mcpImage,
114129
),
115-
subcmd.NewTemplate(logger, a.flags, a.ChartFS, a.kube),
116-
subcmd.NewTopology(logger, a.ChartFS, a.kube),
130+
subcmd.NewTemplate(
131+
a.AppCtx,
132+
logger,
133+
a.flags,
134+
a.ChartFS,
135+
a.kube,
136+
),
137+
subcmd.NewTopology(
138+
a.AppCtx,
139+
logger,
140+
a.ChartFS,
141+
a.kube,
142+
),
117143
}
118144
for _, sub := range subs {
119145
a.rootCmd.AddCommand(api.NewRunner(sub).Cmd())
120146
}
121147
return nil
122148
}
123149

124-
// NewApp creates a new installer application. It automatically sets up the Cobra
125-
// Root Command and standard subcommands (Config, Integration, Deploy).
126-
func NewApp(name string, cfs *chartfs.ChartFS, opts ...Option) (*App, error) {
150+
// NewApp creates a new installer application runtime.
151+
// It automatically sets up the Cobra Root Command and standard subcommands.
152+
//
153+
// The appCtx parameter provides application metadata (name, version, etc.).
154+
// The cfs parameter provides access to the installer filesystem (charts, config).
155+
// Additional runtime options can be passed via functional options.
156+
func NewApp(
157+
appCtx *api.AppContext,
158+
cfs *chartfs.ChartFS,
159+
opts ...Option,
160+
) (*App, error) {
127161
app := &App{
128-
Name: name,
162+
AppCtx: appCtx,
129163
ChartFS: cfs,
130164
flags: flags.NewFlags(),
131165
}

pkg/framework/constants.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package framework
2+
3+
const (
4+
// ConfigFilename is the installer configuration file (framework contract).
5+
// All installers using this framework must provide this file.
6+
ConfigFilename = "config.yaml"
7+
8+
// ValuesFilename is the values template file (framework contract).
9+
// All installers using this framework must provide this file.
10+
ValuesFilename = "values.yaml.tpl"
11+
12+
// InstructionsFilename is the MCP instructions file (framework convention).
13+
// This file provides instructions for the Model Context Protocol server.
14+
InstructionsFilename = "instructions.md"
15+
)

0 commit comments

Comments
 (0)