package recipe import ( "fmt" "os" "path/filepath" "strconv" "coopcloud.tech/abra/cli/formatter" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/client/stack" loader "coopcloud.tech/abra/pkg/client/stack" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/tagcmp" "github.com/docker/distribution/reference" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) var recipeLintCommand = &cli.Command{ Name: "lint", Usage: "Lint a recipe", Aliases: []string{"l"}, ArgsUsage: "", Action: func(c *cli.Context) error { recipe := internal.ValidateRecipeArg(c) pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, recipe) composeFiles, err := filepath.Glob(pattern) if err != nil { logrus.Fatal(err) } opts := stack.Deploy{Composefiles: composeFiles} compose, err := loader.LoadComposefile(opts, make(map[string]string)) if err != nil { logrus.Fatal(err) } expectedVersion := false if compose.Version == "3.8" { expectedVersion = true } envSampleProvided := false envSample := fmt.Sprintf("%s/%s/.env.sample", config.APPS_DIR, recipe) if _, err := os.Stat(envSample); !os.IsNotExist(err) { envSampleProvided = true } if err != nil { logrus.Fatal(err) } serviceNamedApp := false traefikEnabled := false healthChecksForAllServices := true allImagesTagged := true noUnstableTags := true semverLikeTags := true for _, service := range compose.Services { if service.Name == "app" { serviceNamedApp = true } for label := range service.Deploy.Labels { if label == "traefik.enable" { if service.Deploy.Labels[label] == "true" { traefikEnabled = true } } } img, err := reference.ParseNormalizedNamed(service.Image) if err != nil { logrus.Fatal(err) } if reference.IsNameOnly(img) { allImagesTagged = false } tag := img.(reference.NamedTagged).Tag() if tag == "latest" { noUnstableTags = false } if !tagcmp.IsParsable(tag) { semverLikeTags = false } if service.HealthCheck == nil { healthChecksForAllServices = false } } tableCol := []string{"Rule", "Satisfied"} table := formatter.CreateTable(tableCol) table.Append([]string{"Compose files have the expected version", strconv.FormatBool(expectedVersion)}) table.Append([]string{"Environment configuration is provided", strconv.FormatBool(envSampleProvided)}) table.Append([]string{"Recipe contains a service named 'app'", strconv.FormatBool(serviceNamedApp)}) table.Append([]string{"Traefik routing enabled on at least one service", strconv.FormatBool(traefikEnabled)}) table.Append([]string{"All services have a healthcheck enabled", strconv.FormatBool(healthChecksForAllServices)}) table.Append([]string{"All images are using a tag", strconv.FormatBool(allImagesTagged)}) table.Append([]string{"No usage of unstable 'latest' tags", strconv.FormatBool(noUnstableTags)}) table.Append([]string{"All tags are using a semver-like format", strconv.FormatBool(semverLikeTags)}) table.Render() return nil }, }