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 }