diff --git a/cmd/enterprise-portal/service/service.go b/cmd/enterprise-portal/service/service.go index 8bbfba51429..3eb8db3c1c8 100644 --- a/cmd/enterprise-portal/service/service.go +++ b/cmd/enterprise-portal/service/service.go @@ -37,7 +37,7 @@ var _ runtime.Service[Config] = (*Service)(nil) func (Service) Name() string { return "enterprise-portal" } func (Service) Version() string { return version.Version() } -func (Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.Contract, config Config) (background.Routine, error) { +func (Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.ServiceContract, config Config) (background.Routine, error) { // We use Sourcegraph tracing code, so explicitly configure a trace policy policy.SetTracePolicy(policy.TraceAll) @@ -46,7 +46,7 @@ func (Service) Initialize(ctx context.Context, logger log.Logger, contract runti return nil, errors.Wrap(err, "initialize Redis client") } - dbHandle, err := database.NewHandle(ctx, logger, contract, redisClient, version.Version()) + dbHandle, err := database.NewHandle(ctx, logger, contract.Contract, redisClient, version.Version()) if err != nil { return nil, errors.Wrap(err, "initialize database handle") } @@ -76,7 +76,7 @@ func (Service) Initialize(ctx context.Context, logger log.Logger, contract runti return nil, errors.Wrap(err, "create Sourcegraph Accounts client") } - iamClient, closeIAMClient, err := newIAMClient(ctx, logger, contract, redisClient) + iamClient, closeIAMClient, err := newIAMClient(ctx, logger, contract.Contract, redisClient) if err != nil { return nil, errors.Wrap(err, "initialize IAM client") } diff --git a/cmd/msp-example/service/service.go b/cmd/msp-example/service/service.go index 40ed1e36c7b..7d35085faae 100644 --- a/cmd/msp-example/service/service.go +++ b/cmd/msp-example/service/service.go @@ -29,7 +29,7 @@ func (s Service) Version() string { return version.Version() } func (s Service) Initialize( ctx context.Context, logger log.Logger, - contract runtime.Contract, + contract runtime.ServiceContract, config Config, ) (background.Routine, error) { logger.Info("starting service") @@ -42,7 +42,7 @@ func (s Service) Initialize( if !config.StatelessMode { var err error - if bq, err = bigquery.NewClient(ctx, contract); err != nil { + if bq, err = bigquery.NewClient(ctx, contract.Contract); err != nil { return nil, errors.Wrap(err, "bigquery") } if err := bq.Write(ctx, "service.initialized"); err != nil { @@ -50,7 +50,7 @@ func (s Service) Initialize( } logger.Info("bigquery connection success") - if rd, err = redis.NewClient(ctx, contract); err != nil { + if rd, err = redis.NewClient(ctx, contract.Contract); err != nil { return nil, errors.Wrap(err, "redis") } if err := rd.Ping(ctx); err != nil { @@ -58,18 +58,17 @@ func (s Service) Initialize( } logger.Info("redis connection success") - if pg, err = postgresql.NewClient(ctx, contract); err != nil { + if pg, err = postgresql.NewClient(ctx, contract.Contract); err != nil { return nil, errors.Wrap(err, "postgresl") } if err := pg.Ping(ctx); err != nil { return nil, errors.Wrap(err, "postgresql.Ping") } logger.Info("postgresql connection success") - } h := http.NewServeMux() - if err := httpapi.Register(h, contract, config.HTTPAPI); err != nil { + if err := httpapi.Register(h, contract.Contract, config.HTTPAPI); err != nil { return nil, errors.Wrap(err, "httpapi.Register") } diff --git a/cmd/pings/service/service.go b/cmd/pings/service/service.go index ceb3b810122..1c436586283 100644 --- a/cmd/pings/service/service.go +++ b/cmd/pings/service/service.go @@ -29,7 +29,7 @@ var _ runtime.Service[Config] = (*Service)(nil) func (Service) Name() string { return "pings" } func (Service) Version() string { return version.Version() } -func (Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.Contract, config Config) (background.Routine, error) { +func (Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.ServiceContract, config Config) (background.Routine, error) { pubsubClient, err := pubsub.NewTopicClient(config.PubSub.ProjectID, config.PubSub.TopicID) if err != nil { return nil, errors.Errorf("create Pub/Sub client: %v", err) diff --git a/cmd/telemetry-gateway/service/service.go b/cmd/telemetry-gateway/service/service.go index 2b2ac702437..5eb9e0a0232 100644 --- a/cmd/telemetry-gateway/service/service.go +++ b/cmd/telemetry-gateway/service/service.go @@ -42,7 +42,7 @@ var _ runtime.Service[Config] = (*Service)(nil) func (Service) Name() string { return "telemetry-gateway" } func (Service) Version() string { return version.Version() } -func (Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.Contract, config Config) (background.Routine, error) { +func (Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.ServiceContract, config Config) (background.Routine, error) { // We use Sourcegraph tracing code, so explicitly configure a trace policy policy.SetTracePolicy(policy.TraceAll) diff --git a/dev/build-tracker/main.go b/dev/build-tracker/main.go index e0db66162a6..d7cc838d16f 100644 --- a/dev/build-tracker/main.go +++ b/dev/build-tracker/main.go @@ -415,7 +415,7 @@ func main() { type Service struct{} // Initialize implements runtime.Service. -func (s Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.Contract, config config.Config) (background.Routine, error) { +func (s Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.ServiceContract, config config.Config) (background.Routine, error) { logger.Info("config loaded from environment", log.Object("config", log.String("SlackChannel", config.SlackChannel), log.Bool("Production", config.Production))) bqWriter, err := contract.BigQuery.GetTableWriter(context.Background(), "agent_status") diff --git a/dev/linearhooks/internal/service/service.go b/dev/linearhooks/internal/service/service.go index 32727986796..e88349be92e 100644 --- a/dev/linearhooks/internal/service/service.go +++ b/dev/linearhooks/internal/service/service.go @@ -39,7 +39,7 @@ func (s Service) Version() string { return version.Version() } func (s Service) Initialize( ctx context.Context, logger log.Logger, - contract runtime.Contract, + contract runtime.ServiceContract, config Config, ) (background.Routine, error) { logger.Info("starting service") diff --git a/lib/managedservicesplatform/runtime/contract.go b/lib/managedservicesplatform/runtime/contract.go index 6331eb3dbbb..a2ec0683cd9 100644 --- a/lib/managedservicesplatform/runtime/contract.go +++ b/lib/managedservicesplatform/runtime/contract.go @@ -8,6 +8,14 @@ import ( // configuration. type Contract = contract.Contract +// ServiceContract loads standardized MSP-provisioned (Managed Services Platform) +// configuration. +type ServiceContract = contract.ServiceContract + +// JobContract loads standardized MSP-provisioned (Managed Services Platform) +// configuration. +type JobContract = contract.JobContract + // Env carries pre-parsed environment variables and variables requested and // errors encountered. type Env = contract.Env diff --git a/lib/managedservicesplatform/runtime/contract/contract.go b/lib/managedservicesplatform/runtime/contract/contract.go index 2dd9d1552bd..f4ee92cfd19 100644 --- a/lib/managedservicesplatform/runtime/contract/contract.go +++ b/lib/managedservicesplatform/runtime/contract/contract.go @@ -65,10 +65,6 @@ type Contract struct { // in. In local development, this should be 'unknown' if ENVIRONMENT_ID is // not set. EnvironmentID string - // Port is the port the service must listen on. - Port int - // ExternalDNSName is the DNS name the service uses, if one is configured. - ExternalDNSName *string // RedisEndpoint is the full connection string of a MSP Redis instance if // provisioned, including any prerequisite authentication. @@ -90,6 +86,23 @@ type Contract struct { internal internalContract } +// ServiceContract loads standardized MSP-provisioned (Managed Services Platform) +// configuration for a service. +type ServiceContract struct { + // Port is the port the service must listen on. + Port int + // ExternalDNSName is the DNS name the service uses, if one is configured. + ExternalDNSName *string + + Contract +} + +// JobContract loads standardized MSP-provisioned (Managed Services Platform) +// configuration for a job. +type JobContract struct { + Contract +} + type ServiceMetadataProvider interface { // Name is the service name, typically the all-lowercase, dash-delimited, // machine-friendly 'id' of the service in its corresponding MSP service @@ -112,9 +125,25 @@ type internalContract struct { environmentID string } -// New returns a new Contract instance from configuration parsed from the Env +// NewService returns a new Contract instance from configuration parsed from the Env // instance. Values are expected per the 'MSP contract'. -func New(logger log.Logger, service ServiceMetadataProvider, env *Env) Contract { +func NewService(logger log.Logger, service ServiceMetadataProvider, env *Env) ServiceContract { + return ServiceContract{ + Port: env.GetInt("PORT", "", "service port"), + ExternalDNSName: env.GetOptional("EXTERNAL_DNS_NAME", "external DNS name provisioned for the service"), + Contract: newBase(logger, service, env), + } +} + +// NewJob returns a new Contract instance from configuration parsed from the Env +// instance. Values are expected per the 'MSP contract'. +func NewJob(logger log.Logger, service ServiceMetadataProvider, env *Env) JobContract { + return JobContract{ + Contract: newBase(logger, service, env), + } +} + +func newBase(logger log.Logger, service ServiceMetadataProvider, env *Env) Contract { defaultGCPProjectID := pointers.Deref(env.GetOptional("GOOGLE_CLOUD_PROJECT", "GCP project ID"), "") internal := internalContract{ logger: logger, @@ -124,11 +153,9 @@ func New(logger log.Logger, service ServiceMetadataProvider, env *Env) Contract isMSP := env.GetBool("MSP", "false", "indicates if we are running in a MSP environment") return Contract{ - MSP: isMSP, - EnvironmentID: internal.environmentID, - Port: env.GetInt("PORT", "", "service port"), - ExternalDNSName: env.GetOptional("EXTERNAL_DNS_NAME", "external DNS name provisioned for the service"), - RedisEndpoint: env.GetOptional("REDIS_ENDPOINT", "full Redis address, including any prerequisite authentication"), + MSP: isMSP, + EnvironmentID: internal.environmentID, + RedisEndpoint: env.GetOptional("REDIS_ENDPOINT", "full Redis address, including any prerequisite authentication"), PostgreSQL: loadPostgreSQLContract(env, isMSP), BigQuery: loadBigQueryContract(env), diff --git a/lib/managedservicesplatform/runtime/contract/contract_test.go b/lib/managedservicesplatform/runtime/contract/contract_test.go index 191b5d34a53..5553cc7a5a9 100644 --- a/lib/managedservicesplatform/runtime/contract/contract_test.go +++ b/lib/managedservicesplatform/runtime/contract/contract_test.go @@ -17,11 +17,11 @@ func (m mockServiceMetadata) Name() string { return "mock-name" } func (m mockServiceMetadata) Version() string { return "mock-version" } func TestNewContract(t *testing.T) { - t.Run("sanity check", func(t *testing.T) { + t.Run("service sanity check", func(t *testing.T) { e, err := contract.ParseEnv([]string{"MSP=true"}) require.NoError(t, err) - c := contract.New(logtest.Scoped(t), mockServiceMetadata{}, e) + c := contract.NewService(logtest.Scoped(t), mockServiceMetadata{}, e) assert.NotZero(t, c) assert.True(t, c.MSP) @@ -30,5 +30,16 @@ func TestNewContract(t *testing.T) { assert.Error(t, err) }) + t.Run("job sanity check", func(t *testing.T) { + e, err := contract.ParseEnv([]string{"MSP=true"}) + require.NoError(t, err) + c := contract.NewJob(logtest.Scoped(t), mockServiceMetadata{}, e) + assert.NotZero(t, c) + assert.True(t, c.MSP) + + // Not expected to error, as there are no required env vars. + err = e.Validate() + assert.NoError(t, err) + }) // TODO: Add more validation tests } diff --git a/lib/managedservicesplatform/runtime/job.go b/lib/managedservicesplatform/runtime/job.go index 87fb2e1d0a2..fb96d36f6a0 100644 --- a/lib/managedservicesplatform/runtime/job.go +++ b/lib/managedservicesplatform/runtime/job.go @@ -19,7 +19,7 @@ type Job[ConfigT any] interface { Execute( ctx context.Context, logger log.Logger, - contract Contract, + contract JobContract, config ConfigT, ) error } @@ -66,7 +66,7 @@ func ExecuteJob[ // Load configuration variables from environment config.Load(env) - ctr := contract.New(log.Scoped("msp.contract"), job, env) + ctr := contract.NewJob(log.Scoped("msp.contract"), job, env) // Fast-exit with configuration facts if requested if *showHelp { diff --git a/lib/managedservicesplatform/runtime/service.go b/lib/managedservicesplatform/runtime/service.go index a5782be2f89..4d5ec04bc0a 100644 --- a/lib/managedservicesplatform/runtime/service.go +++ b/lib/managedservicesplatform/runtime/service.go @@ -21,7 +21,7 @@ type Service[ConfigT any] interface { Initialize( ctx context.Context, logger log.Logger, - contract Contract, + contract ServiceContract, config ConfigT, ) (background.Routine, error) } @@ -69,7 +69,7 @@ func Start[ // Load configuration variables from environment config.Load(env) - ctr := contract.New(log.Scoped("msp.contract"), service, env) + ctr := contract.NewService(log.Scoped("msp.contract"), service, env) // Fast-exit with configuration facts if requested if *showHelp {