github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/runtime/store/store.go (about)

     1  package store
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/ActiveState/cli/internal/constants"
    10  	"github.com/ActiveState/cli/internal/errs"
    11  	"github.com/ActiveState/cli/internal/fileutils"
    12  	"github.com/ActiveState/cli/internal/locale"
    13  	"github.com/ActiveState/cli/internal/logging"
    14  	"github.com/ActiveState/cli/pkg/buildplan"
    15  	"github.com/ActiveState/cli/pkg/platform/api/buildplanner/types"
    16  	"github.com/ActiveState/cli/pkg/platform/api/inventory/inventory_models"
    17  	"github.com/ActiveState/cli/pkg/platform/model/buildplanner"
    18  	"github.com/ActiveState/cli/pkg/platform/runtime/buildscript"
    19  	"github.com/ActiveState/cli/pkg/platform/runtime/envdef"
    20  	"github.com/go-openapi/strfmt"
    21  )
    22  
    23  // Store manages the storing and loading of persistable information about the runtime
    24  type Store struct {
    25  	installPath string
    26  	storagePath string
    27  }
    28  
    29  type StoredArtifact struct {
    30  	ArtifactID strfmt.UUID                   `json:"artifactID"`
    31  	Files      []string                      `json:"files"`
    32  	Dirs       []string                      `json:"dirs"`
    33  	EnvDef     *envdef.EnvironmentDefinition `json:"envdef"`
    34  }
    35  
    36  func NewStoredArtifact(artifactID strfmt.UUID, files []string, dirs []string, envDef *envdef.EnvironmentDefinition) StoredArtifact {
    37  	return StoredArtifact{
    38  		ArtifactID: artifactID,
    39  		Files:      files,
    40  		Dirs:       dirs,
    41  		EnvDef:     envDef,
    42  	}
    43  }
    44  
    45  type StoredArtifactMap = map[strfmt.UUID]StoredArtifact
    46  
    47  func New(installPath string) *Store {
    48  	return &Store{
    49  		installPath,
    50  		filepath.Join(installPath, constants.LocalRuntimeEnvironmentDirectory),
    51  	}
    52  }
    53  
    54  func (s *Store) buildEngineFile() string {
    55  	return filepath.Join(s.storagePath, constants.RuntimeBuildEngineStore)
    56  }
    57  
    58  func (s *Store) recipeFile() string {
    59  	return filepath.Join(s.storagePath, constants.RuntimeRecipeStore)
    60  }
    61  
    62  func (s *Store) buildPlanFile() string {
    63  	return filepath.Join(s.storagePath, constants.RuntimeBuildPlanStore)
    64  }
    65  
    66  func (s *Store) buildScriptFile() string {
    67  	return filepath.Join(s.storagePath, constants.BuildScriptStore)
    68  }
    69  
    70  // BuildEngine returns the runtime build engine value stored in the runtime directory
    71  func (s *Store) BuildEngine() (types.BuildEngine, error) {
    72  	storeFile := s.buildEngineFile()
    73  
    74  	data, err := fileutils.ReadFile(storeFile)
    75  	if err != nil {
    76  		return types.UnknownEngine, errs.Wrap(err, "Could not read build engine cache store.")
    77  	}
    78  
    79  	return buildplanner.ParseBuildEngine(string(data)), nil
    80  }
    81  
    82  // StoreBuildEngine stores the build engine value in the runtime directory
    83  func (s *Store) StoreBuildEngine(buildEngine types.BuildEngine) error {
    84  	storeFile := s.buildEngineFile()
    85  	storeDir := filepath.Dir(storeFile)
    86  	logging.Debug("Storing build engine %s at %s", buildEngine.String(), storeFile)
    87  	err := fileutils.MkdirUnlessExists(storeDir)
    88  	if err != nil {
    89  		return errs.Wrap(err, "Could not create completion marker directory.")
    90  	}
    91  	err = fileutils.WriteFile(storeFile, []byte(buildEngine.String()))
    92  	if err != nil {
    93  		return errs.Wrap(err, "Could not store build engine string.")
    94  	}
    95  	return nil
    96  }
    97  
    98  // Recipe returns the recipe the stored runtime has been built with
    99  func (s *Store) Recipe() (*inventory_models.Recipe, error) {
   100  	data, err := fileutils.ReadFile(s.recipeFile())
   101  	if err != nil {
   102  		return nil, errs.Wrap(err, "Could not read recipe file.")
   103  	}
   104  
   105  	var recipe inventory_models.Recipe
   106  	err = json.Unmarshal(data, &recipe)
   107  	if err != nil {
   108  		return nil, errs.Wrap(err, "Could not parse recipe file.")
   109  	}
   110  	return &recipe, err
   111  }
   112  
   113  // StoreRecipe stores a along side the stored runtime
   114  func (s *Store) StoreRecipe(recipe *inventory_models.Recipe) error {
   115  	data, err := json.Marshal(recipe)
   116  	if err != nil {
   117  		return errs.Wrap(err, "Could not marshal recipe.")
   118  	}
   119  	err = fileutils.WriteFile(s.recipeFile(), data)
   120  	if err != nil {
   121  		return errs.Wrap(err, "Could not write recipe file.")
   122  	}
   123  	return nil
   124  }
   125  
   126  // Artifacts loads artifact information collected during the installation.
   127  // It includes the environment definition configuration and files installed for this artifact.
   128  func (s *Store) Artifacts() (StoredArtifactMap, error) {
   129  	stored := make(StoredArtifactMap)
   130  	jsonDir := filepath.Join(s.storagePath, constants.ArtifactMetaDir)
   131  	if !fileutils.DirExists(jsonDir) {
   132  		return stored, nil
   133  	}
   134  
   135  	files, err := os.ReadDir(jsonDir)
   136  	if err != nil {
   137  		return stored, errs.Wrap(err, "Readdir %s failed", jsonDir)
   138  	}
   139  
   140  	for _, file := range files {
   141  		if file.IsDir() || !strings.HasSuffix(file.Name(), ".json") {
   142  			continue
   143  		}
   144  
   145  		var artifactStore StoredArtifact
   146  		jsonBlob, err := fileutils.ReadFile(filepath.Join(jsonDir, file.Name()))
   147  		if err != nil {
   148  			return stored, errs.Wrap(err, "Could not read artifact meta file")
   149  		}
   150  		if err := json.Unmarshal(jsonBlob, &artifactStore); err != nil {
   151  			return stored, errs.Wrap(err, "Could not unmarshal artifact meta file")
   152  		}
   153  
   154  		stored[artifactStore.ArtifactID] = artifactStore
   155  	}
   156  
   157  	return stored, nil
   158  }
   159  
   160  // DeleteArtifactStore deletes the stored information for a specific artifact from the store
   161  func (s *Store) DeleteArtifactStore(id strfmt.UUID) error {
   162  	jsonFile := filepath.Join(s.storagePath, constants.ArtifactMetaDir, id.String()+".json")
   163  	if !fileutils.FileExists(jsonFile) {
   164  		return nil
   165  	}
   166  	return os.Remove(jsonFile)
   167  }
   168  
   169  func (s *Store) StoreArtifact(artf StoredArtifact) error {
   170  	// Save artifact cache information
   171  	jsonBlob, err := json.Marshal(artf)
   172  	if err != nil {
   173  		return errs.Wrap(err, "Failed to marshal artifact cache information")
   174  	}
   175  	jsonFile := filepath.Join(s.storagePath, constants.ArtifactMetaDir, artf.ArtifactID.String()+".json")
   176  	if err := fileutils.WriteFile(jsonFile, jsonBlob); err != nil {
   177  		return errs.Wrap(err, "Failed to write artifact cache information")
   178  	}
   179  	return nil
   180  }
   181  
   182  func (s *Store) EnvDef() (*envdef.EnvironmentDefinition, error) {
   183  	mergedRuntimeDefinitionFile := filepath.Join(s.storagePath, constants.RuntimeDefinitionFilename)
   184  	envDef, err := envdef.NewEnvironmentDefinition(mergedRuntimeDefinitionFile)
   185  	if err != nil {
   186  		return nil, locale.WrapError(
   187  			err, "err_no_environment_definition",
   188  			"Your installation seems corrupted.\nPlease try to re-run this command, as it may fix the problem.  If the problem persists, please report it in our forum: {{.V0}}",
   189  			constants.ForumsURL,
   190  		)
   191  	}
   192  	return envDef, nil
   193  }
   194  
   195  func (s *Store) Environ(inherit bool) (map[string]string, error) {
   196  	envDef, err := s.EnvDef()
   197  	if err != nil {
   198  		return nil, errs.Wrap(err, "Could not grab EnvDef")
   199  	}
   200  	return envDef.GetEnv(inherit), nil
   201  }
   202  
   203  func (s *Store) UpdateEnviron(orderedArtifacts []strfmt.UUID) (*envdef.EnvironmentDefinition, error) {
   204  	artifacts, err := s.Artifacts()
   205  	if err != nil {
   206  		return nil, errs.Wrap(err, "Could not retrieve stored artifacts")
   207  	}
   208  
   209  	rtGlobal, err := s.updateEnviron(orderedArtifacts, artifacts)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	return rtGlobal, rtGlobal.WriteFile(filepath.Join(s.storagePath, constants.RuntimeDefinitionFilename))
   215  }
   216  
   217  func (s *Store) updateEnviron(orderedArtifacts []strfmt.UUID, artifacts StoredArtifactMap) (*envdef.EnvironmentDefinition, error) {
   218  	if len(orderedArtifacts) == 0 {
   219  		return nil, errs.New("Environment cannot be updated if no artifacts were installed")
   220  	}
   221  
   222  	var rtGlobal *envdef.EnvironmentDefinition
   223  	// use artifact order as returned by the build status response form the HC for merging artifacts
   224  	for _, artID := range orderedArtifacts {
   225  		a, ok := artifacts[artID]
   226  		if !ok {
   227  			continue
   228  		}
   229  
   230  		if rtGlobal == nil {
   231  			rtGlobal = a.EnvDef
   232  			continue
   233  		}
   234  		var err error
   235  		rtGlobal, err = rtGlobal.Merge(a.EnvDef)
   236  		if err != nil {
   237  			return nil, errs.Wrap(err, "Could not merge envdef")
   238  		}
   239  	}
   240  
   241  	if rtGlobal == nil {
   242  		// Returning nil will end up causing a nil-pointer-exception panic in setup.Update().
   243  		// There is additional logging of the buildplan there that may help diagnose why this is happening.
   244  		logging.Error("There were artifacts returned, but none of them ended up being stored/installed.")
   245  		logging.Error("Artifacts returned: %v", orderedArtifacts)
   246  		logging.Error("Artifacts stored: %v", artifacts)
   247  	}
   248  
   249  	return rtGlobal, nil
   250  }
   251  
   252  // InstallPath returns the installation path of the runtime
   253  func (s *Store) InstallPath() string {
   254  	return s.installPath
   255  }
   256  
   257  var ErrNoBuildPlanFile = errs.New("no build plan file")
   258  
   259  func (s *Store) BuildPlanRaw() ([]byte, error) {
   260  	if !fileutils.FileExists(s.buildPlanFile()) {
   261  		return nil, ErrNoBuildPlanFile
   262  	}
   263  	data, err := fileutils.ReadFile(s.buildPlanFile())
   264  	if err != nil {
   265  		return nil, errs.Wrap(err, "Could not read build plan file.")
   266  	}
   267  
   268  	return data, nil
   269  }
   270  
   271  type ErrVersionMarker struct {
   272  	*locale.LocalizedError
   273  }
   274  
   275  func (s *Store) BuildPlan() (*buildplan.BuildPlan, error) {
   276  	if !s.VersionMarkerIsValid() {
   277  		return nil, &ErrVersionMarker{locale.NewInputError("err_runtime_needs_refresh")}
   278  	}
   279  
   280  	data, err := s.BuildPlanRaw()
   281  	if err != nil {
   282  		return nil, errs.Wrap(err, "Could not get build plan file.")
   283  	}
   284  
   285  	return buildplan.Unmarshal(data)
   286  }
   287  
   288  func (s *Store) StoreBuildPlan(bp *buildplan.BuildPlan) error {
   289  	data, err := bp.Marshal()
   290  	if err != nil {
   291  		return errs.Wrap(err, "Could not marshal buildPlan.")
   292  	}
   293  	err = fileutils.WriteFile(s.buildPlanFile(), data)
   294  	if err != nil {
   295  		return errs.Wrap(err, "Could not write recipe file.")
   296  	}
   297  	return nil
   298  }
   299  
   300  var ErrNoBuildScriptFile = errs.New("no buildscript file")
   301  
   302  func (s *Store) BuildScript() (*buildscript.Script, error) {
   303  	if !fileutils.FileExists(s.buildScriptFile()) {
   304  		return nil, ErrNoBuildScriptFile
   305  	}
   306  	bytes, err := fileutils.ReadFile(s.buildScriptFile())
   307  	if err != nil {
   308  		return nil, errs.Wrap(err, "Could not read buildscript file")
   309  	}
   310  	return buildscript.New(bytes)
   311  }
   312  
   313  func (s *Store) StoreBuildScript(script *buildscript.Script) error {
   314  	return fileutils.WriteFile(s.buildScriptFile(), []byte(script.String()))
   315  }