diff --git a/cli/COVERAGE b/cli/COVERAGE index 4e2e4372ed..6d6d0c7862 100644 --- a/cli/COVERAGE +++ b/cli/COVERAGE @@ -1 +1 @@ -22.94 \ No newline at end of file +22.65 \ No newline at end of file diff --git a/cli/cmd/init.go b/cli/cmd/init.go index 856423e5d1..2c5f954f2f 100644 --- a/cli/cmd/init.go +++ b/cli/cmd/init.go @@ -301,49 +301,50 @@ func validateConfigurationIntegirty(ctx context.Context, slice string, happyClie logrus.Debug("Scheduling validateConfigurationIntegirty()") return func() error { logrus.Debug("Running validateConfigurationIntegirty()") - // These services are configured in docker-compose.yml, and have their containers built - availableServices, err := happyClient.ArtifactBuilder.GetServices(ctx) + + // Happy configuration is spread across these files: + // * .happy/config.json defines environments, specifies services, slices, features and tasks + // * docker-compose.yml defines services and their build configuration + // * terraform code from .happy/terraform/envs//*.tf references services and their settings + + // All services referenced through TF code must be present in config.json. All services listed in config.json + // must be declared in docker-compose.yml and have a build section. + + // These services are configured in docker-compose.yml + composeServices, err := happyClient.ArtifactBuilder.GetAllServices(ctx) if err != nil { return errors.Wrap(err, "unable to get available services") } - // NOTE: availableServices will only contain the services that are a part of the slice. - // That means we have to iterate over those first to make sure they are all in the config.json and - // not the other way around. + // ConfigServices are configured in config.json, and are a subset of the services in docker-compose.yml. + // Every service from config.json must be present in docker-compose.yml and must have a build section. configServices := happyClient.HappyConfig.GetServices() ss := sets.NewStringSet().Add(configServices...) - for serviceName, service := range availableServices { - // ignore services that don't have a build section - // as these are not deployable services - if service.Build == nil { - continue - } - ok := ss.ContainsElement(serviceName) + for _, serviceName := range configServices { + service, ok := composeServices[serviceName] if !ok { - return errors.Errorf("service %s was referenced in docker-compose.yml, but not referenced in .happy/config.json services array", serviceName) + return errors.Errorf("service '%s' is not configured in docker-compose.yml, but referenced in your .happy/config.json services array", serviceName) + } + if service.Build == nil { + return errors.Errorf("service '%s' is not configured to be built in docker-compose.yml, but referenced in your .happy/config.json services array", serviceName) } } - // These services are referenced in terraform code for the environment + // These services are referenced in terraform code for the environment, and must be present in config.json + // (and docker-compose.yml as well -- see the check above). srcDir := filepath.Join(happyClient.HappyConfig.GetProjectRoot(), happyClient.HappyConfig.TerraformDirectory()) deployedServices, err := tf.NewTfParser().ParseServices(srcDir) if err != nil { return errors.Wrap(err, "unable to parse terraform code") } - for service := range deployedServices { - if _, ok := availableServices[service]; !ok { - return errors.Errorf("service %s is not configured in docker-compose.yml, but referenced in your terraform code", service) - } - found := false - for _, configuredService := range happyClient.HappyConfig.GetServices() { - if service == configuredService { - found = true - break - } + for serviceName := range deployedServices { + if _, ok := composeServices[serviceName]; !ok { + return errors.Errorf("service '%s' is not configured in docker-compose.yml, but referenced in your terraform code", serviceName) } + found := ss.ContainsElement(serviceName) if !found { - return errors.Errorf("service %s is not configured in ./happy/config.json, but referenced in your terraform code", service) + return errors.Errorf("service %s is not configured in ./happy/config.json, but referenced in your terraform code", serviceName) } } diff --git a/cli/pkg/artifact_builder/COVERAGE b/cli/pkg/artifact_builder/COVERAGE index 32ccc6d5b4..7388c4b58c 100644 --- a/cli/pkg/artifact_builder/COVERAGE +++ b/cli/pkg/artifact_builder/COVERAGE @@ -1 +1 @@ -58.8 \ No newline at end of file +56.7 \ No newline at end of file diff --git a/cli/pkg/artifact_builder/artifact_builder.go b/cli/pkg/artifact_builder/artifact_builder.go index fedf08d4ee..212f405639 100644 --- a/cli/pkg/artifact_builder/artifact_builder.go +++ b/cli/pkg/artifact_builder/artifact_builder.go @@ -595,3 +595,14 @@ func (ab ArtifactBuilder) GetServices(ctx context.Context) (map[string]ServiceCo } return config.Services, nil } + +func (ab ArtifactBuilder) GetAllServices(ctx context.Context) (map[string]ServiceConfig, error) { + bc := ab.config.Clone() + bc.configData = nil + bc.Profile = nil + config, err := bc.GetConfigData(ctx) + if err != nil { + return nil, errors.Wrap(err, "unable to get config data") + } + return config.Services, nil +} diff --git a/cli/pkg/artifact_builder/artifact_builder_factory.go b/cli/pkg/artifact_builder/artifact_builder_factory.go index 2a8eca0bb0..5d441d84ba 100644 --- a/cli/pkg/artifact_builder/artifact_builder_factory.go +++ b/cli/pkg/artifact_builder/artifact_builder_factory.go @@ -31,6 +31,7 @@ type ArtifactBuilderIface interface { Pull(ctx context.Context, stackName, tag string) (map[string]string, error) BuildAndPush(ctx context.Context) error GetServices(ctx context.Context) (map[string]ServiceConfig, error) + GetAllServices(ctx context.Context) (map[string]ServiceConfig, error) } func CreateArtifactBuilder(ctx context.Context) ArtifactBuilderIface { diff --git a/cli/pkg/artifact_builder/builder_config.go b/cli/pkg/artifact_builder/builder_config.go index 125ccb201d..fe7ca598ec 100644 --- a/cli/pkg/artifact_builder/builder_config.go +++ b/cli/pkg/artifact_builder/builder_config.go @@ -46,6 +46,20 @@ func NewBuilderConfig() *BuilderConfig { } } +func (b *BuilderConfig) Clone() *BuilderConfig { + return &BuilderConfig{ + composeFile: b.composeFile, + composeEnvFile: b.composeEnvFile, + dockerRepo: b.dockerRepo, + env: b.env, + StackName: b.StackName, + Profile: b.Profile, + configData: b.configData, + Executor: b.Executor, + DryRun: b.DryRun, + } +} + func (b *BuilderConfig) WithBootstrap(bootstrap *config.Bootstrap) *BuilderConfig { b.composeFile = bootstrap.DockerComposeConfigPath return b diff --git a/cli/pkg/artifact_builder/dry_run_artifact_builder.go b/cli/pkg/artifact_builder/dry_run_artifact_builder.go index 01fbd4e164..5c4ce95b21 100644 --- a/cli/pkg/artifact_builder/dry_run_artifact_builder.go +++ b/cli/pkg/artifact_builder/dry_run_artifact_builder.go @@ -96,3 +96,14 @@ func (ab *DryRunArtifactBuilder) GetServices(ctx context.Context) (map[string]Se } return config.Services, nil } + +func (ab *DryRunArtifactBuilder) GetAllServices(ctx context.Context) (map[string]ServiceConfig, error) { + bc := ab.config.Clone() + bc.configData = nil + bc.Profile = nil + config, err := bc.GetConfigData(ctx) + if err != nil { + return nil, errors.Wrap(err, "unable to get config data") + } + return config.Services, nil +}