fix: improved offline support

Closes coop-cloud/organising#471.
This commit is contained in:
2023-07-26 08:16:07 +02:00
parent ab64eb2e8d
commit 3dc5662821
49 changed files with 455 additions and 375 deletions

View File

@ -5,6 +5,7 @@ import (
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@ -27,7 +28,12 @@ func AppNameComplete(c *cli.Context) {
// RecipeNameComplete completes recipe names.
func RecipeNameComplete(c *cli.Context) {
catl, err := recipe.ReadRecipeCatalogue()
// defaults since we can't take arguments here... this means auto-completion
// of recipe names always access the network if e.g. the catalogue needs
// cloning / updating
conf := runtime.New()
catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil {
logrus.Warn(err)
}

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/pkg/config"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/runtime"
"github.com/go-git/go-git/v5"
"github.com/sirupsen/logrus"
)
@ -52,9 +53,13 @@ var CatalogueSkipList = map[string]bool{
}
// EnsureCatalogue ensures that the catalogue is cloned locally & present.
func EnsureCatalogue() error {
func EnsureCatalogue(conf *runtime.Config) error {
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) {
if conf.Offline {
return fmt.Errorf("no local copy of the catalogue available, network access required")
}
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME)
if err := gitPkg.Clone(catalogueDir, url); err != nil {
return err
@ -68,7 +73,7 @@ func EnsureCatalogue() error {
// EnsureUpToDate ensures that the local catalogue has no unstaged changes as
// is up to date. This is useful to run before doing catalogue generation.
func EnsureUpToDate() error {
func EnsureUpToDate(conf *runtime.Config) error {
isClean, err := gitPkg.IsClean(config.CATALOGUE_DIR)
if err != nil {
return err
@ -79,6 +84,11 @@ func EnsureUpToDate() error {
return fmt.Errorf(msg, config.CATALOGUE_DIR)
}
if conf.Offline {
logrus.Debug("attempting to use local catalogue without access network (\"--offline\")")
return nil
}
repo, err := git.PlainOpen(config.CATALOGUE_DIR)
if err != nil {
return err

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/tagcmp"
"github.com/docker/distribution/reference"
"github.com/go-git/go-git/v5"
@ -333,7 +334,11 @@ func LintImagePresent(recipe recipe.Recipe) (bool, error) {
}
func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) {
catl, err := recipePkg.ReadRecipeCatalogue()
// defaults since we can't take arguments here... this means this lint rule
// always access the network if e.g. the catalogue needs cloning / updating
conf := runtime.New()
catl, err := recipePkg.ReadRecipeCatalogue(conf)
if err != nil {
logrus.Fatal(err)
}

View File

@ -252,13 +252,13 @@ func Get(recipeName string, conf *runtime.Config) (Recipe, error) {
// EnsureExists ensures that a recipe is locally cloned
func EnsureExists(recipeName string, conf *runtime.Config) error {
if !conf.EnsureRecipeExists {
return nil
}
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
if conf.Offline {
return fmt.Errorf("no local copy of %s available, network access required", recipeName)
}
logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir)
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName)
if err := gitPkg.Clone(recipeDir, url); err != nil {
@ -339,10 +339,6 @@ func EnsureVersion(recipeName, version string) error {
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository
func EnsureLatest(recipeName string, conf *runtime.Config) error {
if !conf.EnsureRecipeLatest {
return nil
}
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeDir)
@ -588,10 +584,6 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err
// EnsureUpToDate ensures that the local repo is synced to the remote
func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
if !conf.EnsureRecipeLatest {
return nil
}
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeDir)
@ -604,6 +596,11 @@ func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
return fmt.Errorf(msg, recipeName, recipeDir)
}
if conf.Offline {
logrus.Debug("attempting to use local recipe without access network (\"--offline\")")
return nil
}
repo, err := git.PlainOpen(recipeDir)
if err != nil {
return fmt.Errorf("unable to open %s: %s", recipeDir, err)
@ -659,14 +656,14 @@ func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
}
// ReadRecipeCatalogue reads the recipe catalogue.
func ReadRecipeCatalogue() (RecipeCatalogue, error) {
func ReadRecipeCatalogue(conf *runtime.Config) (RecipeCatalogue, error) {
recipes := make(RecipeCatalogue)
if err := catalogue.EnsureCatalogue(); err != nil {
if err := catalogue.EnsureCatalogue(conf); err != nil {
return nil, err
}
if err := catalogue.EnsureUpToDate(); err != nil {
if err := catalogue.EnsureUpToDate(conf); err != nil {
return nil, err
}
@ -694,10 +691,10 @@ func readRecipeCatalogueFS(target interface{}) error {
}
// VersionsOfService lists the version of a service.
func VersionsOfService(recipe, serviceName string) ([]string, error) {
func VersionsOfService(recipe, serviceName string, conf *runtime.Config) ([]string, error) {
var versions []string
catalogue, err := ReadRecipeCatalogue()
catalogue, err := ReadRecipeCatalogue(conf)
if err != nil {
return nil, err
}
@ -724,7 +721,7 @@ func VersionsOfService(recipe, serviceName string) ([]string, error) {
// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
func GetRecipeMeta(recipeName string, conf *runtime.Config) (RecipeMeta, error) {
catl, err := ReadRecipeCatalogue()
catl, err := ReadRecipeCatalogue(conf)
if err != nil {
return RecipeMeta{}, err
}
@ -821,7 +818,11 @@ type InternalTracker struct {
type RepoCatalogue map[string]RepoMeta
// ReadReposMetadata retrieves coop-cloud/... repo metadata from Gitea.
func ReadReposMetadata() (RepoCatalogue, error) {
func ReadReposMetadata(conf *runtime.Config) (RepoCatalogue, error) {
if conf.Offline {
return nil, fmt.Errorf("network access required to query recipes metadata")
}
reposMeta := make(RepoCatalogue)
pageIdx := 1
@ -1002,6 +1003,10 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
// UpdateRepositories clones and updates all recipe repositories locally.
func UpdateRepositories(repos RepoCatalogue, recipeName string, conf *runtime.Config) error {
if conf.Offline {
return fmt.Errorf("network access required to update recipes")
}
var barLength int
if recipeName != "" {
barLength = 1

28
pkg/runtime/config.go Normal file
View File

@ -0,0 +1,28 @@
package runtime
import "github.com/sirupsen/logrus"
type Config struct {
Offline bool
}
type Option func(c *Config)
func New(opts ...Option) *Config {
conf := &Config{Offline: false}
for _, optFunc := range opts {
optFunc(conf)
}
return conf
}
func WithOffline(offline bool) Option {
return func(c *Config) {
if offline {
logrus.Debugf("runtime config: attempting to run in offline mode")
}
c.Offline = offline
}
}

View File

@ -1,62 +0,0 @@
package runtime
import "github.com/sirupsen/logrus"
// Config is an internal configuration modifier. It can be instantiated on a
// command call and can be changed on the fly to help make decisions further
// down in the internals, e.g. whether or not to clone the recipe locally or
// not.
type Config struct {
EnsureRecipeExists bool // ensure that the recipe is cloned locally
EnsureRecipeLatest bool // ensure the local recipe has latest changes
}
// Option modifies a Config. The convention for passing an Option to a function
// is so far, only used in internal/validate.go. A Config is then constructed
// and passed down further into the code. This may change in the future but
// this is at least the abstraction so far.
type Option func(c *Config)
// New instantiates a Config.
func New(opts ...Option) *Config {
conf := &Config{
EnsureRecipeExists: true,
EnsureRecipeLatest: true,
}
for _, optFunc := range opts {
optFunc(conf)
}
return conf
}
// WithEnsureRecipeExists determines whether or not we should be cloning the
// local recipe or not. This can be useful for being more adaptable to offline
// scenarios.
func WithEnsureRecipeExists(ensureRecipeExists bool) Option {
return func(c *Config) {
if ensureRecipeExists {
logrus.Debugf("runtime config: EnsureRecipeExists = %v, ensuring recipes are cloned", ensureRecipeExists)
} else {
logrus.Debugf("runtime config: EnsureRecipeExists = %v, not cloning recipes", ensureRecipeExists)
}
c.EnsureRecipeExists = ensureRecipeExists
}
}
// WithEnsureRecipeLatest determines whether we should update the local recipes
// remotely via Git. This can be useful when e.g. ensuring we have the latest
// changes before making new ones.
func WithEnsureRecipeLatest(ensureRecipeLatest bool) Option {
return func(c *Config) {
if ensureRecipeLatest {
logrus.Debugf("runtime config: EnsureRecipeLatest = %v, ensuring recipes have latest changes", ensureRecipeLatest)
} else {
logrus.Debugf("runtime config: EnsureRecipeLatest = %v, leaving recipes alone", ensureRecipeLatest)
}
c.EnsureRecipeLatest = ensureRecipeLatest
}
}