prereq.go (195 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package openserverless
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/apache/openserverless-cli/tools"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3"
)
var PrereqSeenMap = map[string]string{}
// execute prereq task
func execPrereqTask(bindir string, name string) error {
me, err := os.Executable()
if err != nil {
return err
}
args := []string{
"-task",
"-d", bindir,
"-t", PREREQ,
name,
}
if taskDryRun {
fmt.Printf("invoking prereq for %s\n", name)
return nil
}
trace("Exec:", me, args)
err = exec.Command(me, args...).Run()
if err != nil {
return err
}
return nil
}
// load prerequisites in current dir
func loadPrereq(dir string) (tasks []string, versions []string, err error) {
err = nil
tasks = []string{}
versions = []string{}
if !exists(dir, PREREQ) {
return
}
trace("found prereq.yml in ", dir)
data, err := os.ReadFile(joinpath(dir, PREREQ))
if err != nil {
return
}
/*
You have tasks = []strings and versions = []strings
and dat a string with a YAML in format:
```yaml
something:
tasks:
task:
vars:
VERSION: "123"
anothertask:
```
I want to parse the file, find the entries under `tasks` vith a var with VERSION
and append, in order, to tasks the name of the task
and to version the version found
I have to skip the entries without a version and everyhing not in tasks
I wnato just the plain code no procedures
*/
var root yaml.Node
if err = yaml.Unmarshal([]byte(data), &root); err != nil {
return
}
for i := 0; i < len(root.Content[0].Content); i += 2 {
if root.Content[0].Content[i].Value == "tasks" {
tasksNode := root.Content[0].Content[i+1]
for j := 0; j < len(tasksNode.Content); j += 2 {
taskName := tasksNode.Content[j].Value
taskVars := tasksNode.Content[j+1]
for k := 0; k < len(taskVars.Content); k += 2 {
if taskVars.Content[k].Value == "vars" {
varsNode := taskVars.Content[k+1]
for l := 0; l < len(varsNode.Content); l += 2 {
if varsNode.Content[l].Value == "VERSION" {
version := varsNode.Content[l+1].Value
tasks = append(tasks, taskName)
versions = append(versions, version)
}
}
}
}
}
}
}
return
}
func binDir() (string, error) {
var err error
bindir := os.Getenv("OPS_BIN")
if bindir == "" {
bindir, err = homedir.Expand(fmt.Sprintf("~/.ops/%s-%s/bin", tools.GetOS(), tools.GetARCH()))
if err != nil {
return "", err
}
}
os.Setenv("OPS_BIN", bindir)
return bindir, nil
}
func addExeExt(name string) string {
if tools.GetOS() == "windows" {
return name + ".exe"
}
return name
}
// ensure there is a bindir for downloading prerequisites
// read it from OPS_BIN and create it
// otherwise setup one in ~/ops/<os>-<arch>/bin
// and sets OPS_BIN
func EnsureBindir() (string, error) {
var err error = nil
bindir, err := binDir()
if err != nil {
return "", err
}
err = os.MkdirAll(bindir, 0755)
if err != nil {
return "", err
}
trace("bindir", bindir)
return bindir, nil
}
// create a mark of current version touching <name>-<version> and remove all the other files starting with <name>-
func touchAndClean(dir string, name string, version string) error {
// Walk through the directory
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check if the file starts with the prefix
if !info.IsDir() && strings.HasPrefix(info.Name(), name+"-") {
trace("Removing file:", path)
err := os.Remove(path)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
err = touch(dir, name+"-"+version)
if err != nil {
return err
}
return nil
}
// download a prerequisite
func downloadPrereq(name string, version string) error {
// names and version
// xname = executeable name
// vname = versioned executable name
xname := addExeExt(name)
vname := xname + "-" + version
// ensure bindir
bindir, err := EnsureBindir()
if err != nil {
return err
}
// check if file and version exists
trace("checking", vname, version)
if exists(bindir, vname) {
trace("already downloaded", vname)
return nil
}
// checking different versions of the same file
oldver, seen := PrereqSeenMap[name]
if seen {
if oldver == version {
trace("same version again", vname)
return nil
}
return fmt.Errorf("WARNING: %s prerequisite found twice with different versions!\nPrevious version: %s, ignoring %s", name, oldver, version)
}
PrereqSeenMap[name] = version
if taskDryRun {
fmt.Printf("downloading %s %s\n", name, version)
touch(bindir, name)
} else {
fmt.Printf("ensuring prerequisite %s %s\n", name, version)
execPrereqTask(bindir, name)
// check if file and version exists
if !exists(bindir, xname) {
return fmt.Errorf("failed to download %s version %s", name, version)
}
// check if a file is zero length and remove in this case
fileInfo, err := os.Stat(joinpath(bindir, xname))
if err != nil {
return fmt.Errorf("failed to download %s version %s", name, version)
}
if fileInfo.Size() == 0 {
trace("removing the empty file ", xname)
err := os.Remove(joinpath(bindir, xname))
if err != nil {
return fmt.Errorf("cannot remove empty %s ", xname)
}
}
}
return touchAndClean(bindir, xname, version)
}
// ensure prereq are satified looking at the prereq.yml
func ensurePrereq(root string) error {
// skip prereq - useful for tests
if os.Getenv("OPS_NO_PREREQ") != "" {
return nil
}
err := os.Chdir(root)
if err != nil {
return err
}
trace("ensurePrereq in", root)
tasks, versions, err := loadPrereq(root)
if err != nil {
return err
}
trace(tasks, versions, err)
for i, task := range tasks {
version := versions[i]
trace("prereq", task, version)
err = downloadPrereq(task, version)
if err != nil {
fmt.Printf("error in prereq %s: %v\n", task, err)
}
}
return nil
}