blob: 1c7b06eab9e56cd4623c93af956082014d993d1a [file]
// 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"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/Masterminds/semver"
git "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/mitchellh/go-homedir"
)
func downloadTasksFromGitHub(force bool, silent bool) (string, error) {
debug("Download tasks from github")
repoURL := getOpsRepo()
branch := getOpsBranch()
opsDir, err := homedir.Expand("~/.ops")
if err != nil {
return "", err
}
if err := os.MkdirAll(opsDir, 0755); err != nil {
return "", err
}
opsBranchDir := joinpath(opsDir, branch)
tasksDir := findTasksDir(opsBranchDir)
alreadyCloned := tasksDir != ""
if !alreadyCloned {
tasksDir = TASKS_DIR
}
localDir, err := homedir.Expand(joinpath(opsBranchDir, tasksDir))
if err != nil {
return "", err
}
debug("localDir", localDir)
// Updating existing tools
if alreadyCloned {
trace("Updating", tasksDir, "in", opsBranchDir)
fmt.Println("Updating tasks...")
r, err := git.PlainOpen(localDir)
if err != nil {
return "", err
}
// Get the working directory for the repository
w, err := r.Worktree()
if err != nil {
return "", err
}
// Pull the latest changes from the origin remote and merge into the current branch
// Clone the repo if not existing
ref := plumbing.NewBranchReferenceName(branch)
err = w.Pull(&git.PullOptions{
RemoteName: "origin",
ReferenceName: ref,
SingleBranch: true,
})
if err != nil {
if err.Error() == "already up-to-date" {
fmt.Println("Tasks are already up to date!")
return localDir, nil
}
return "", err
}
fmt.Println("Tasks updated successfully")
touchLatestCheckFile(joinpath(opsBranchDir, LATESTCHECK))
return localDir, nil
}
// Clone the repo if not existing
ref := plumbing.NewBranchReferenceName(branch)
cloneOpts := &git.CloneOptions{
URL: repoURL,
Progress: os.Stderr,
ReferenceName: ref, // Specify the branch to clone
}
fmt.Println("Cloning tasks...")
_, err = git.PlainClone(localDir, false, cloneOpts)
if err != nil {
os.RemoveAll(opsBranchDir)
warn(fmt.Sprintf("failed to clone %s on branch '%s'", tasksDir, branch))
return "", err
}
fmt.Println("Tasks downloaded successfully")
createLatestCheckFile(opsBranchDir)
// clone
return localDir, nil
}
func pullTasks(force, silent bool) (string, error) {
// download from github
localDir, err := downloadTasksFromGitHub(force, silent)
debug("localDir", localDir)
if err != nil {
return "", fmt.Errorf("cannot update tasks because: %s\nremove the folder ~/.ops and run ops -update", err.Error())
}
err = ensurePrereq(localDir)
if err != nil {
log.Fatalf("cannot download prerequisites: %v", err)
}
// validate OpsVersion semver against opsroot.json
opsRoot, err := readOpsRootFile(localDir)
if err != nil {
return "", err
}
// check if the version is up to date
opsVersion, err := semver.NewVersion(OpsVersion)
if err != nil {
// in development mode, we don't have a valid semver version
warn("Unable to validate ops version", OpsVersion, ":", err)
return localDir, nil
}
opsRootVersion, err := semver.NewVersion(opsRoot.Version)
if err != nil {
warn("Unable to validate opsroot.json version", opsRoot.Version, ":", err)
return localDir, nil
}
// check if the version is up to date, if not warn the user
if opsVersion.LessThan(opsRootVersion) {
fmt.Println()
fmt.Printf("Your ops version (%v) is older than the required version (%v).\n", opsVersion, opsRootVersion)
if err := autoCLIUpdate(); err != nil {
return "", err
}
}
err = checkOperatorVersion(opsRoot.Config)
if err == nil {
fmt.Println()
fmt.Println("New operator version detected!")
fmt.Println("Current deployed operator can be updated with: ops update operator")
}
return localDir, nil
}
// locateOpsRoot locate the folder where starts execution
// it can be a parent folder of the current folder or it can be downloaded
// from github - it should contain a file opsfile.yml and a file opstools.yml in the root
func locateOpsRoot(cur string) (string, error) {
cur, err := filepath.Abs(cur)
if err != nil {
return "", err
}
// search the root from here
search := locateOpsRootSearch(cur)
if search != "" {
trace("found searching up:", search)
return search, nil
}
// is there a tasks folder (olaris or oplugins)?
for _, name := range TASKS_DIRS {
tasks := joinpath(cur, name)
if exists(cur, name) && exists(tasks, OPSFILE) && exists(tasks, OPSROOT) {
trace("found sub tasks:", tasks)
return tasks, nil
}
}
// is there a tasks folder in ~/.ops ?
for _, name := range TASKS_DIRS {
opsTasksDir := fmt.Sprintf("~/.ops/%s/%s", getOpsBranch(), name)
tasks, err := homedir.Expand(opsTasksDir)
if err == nil && exists(tasks, OPSFILE) && exists(tasks, OPSROOT) {
trace("found sub", opsTasksDir, ":", tasks)
return tasks, nil
}
}
return "", fmt.Errorf("cannot find opsfiles, download them with ops -update")
}
// locateOpsRootSearch search for `opsfiles.yml`
// and goes up looking for a folder with also `opsroot.json`
func locateOpsRootSearch(cur string) string {
debug("locateOpsRootSearch:", cur)
// exits opsfile.yml? if not, go up until you find it
if !exists(cur, OPSFILE) {
return ""
}
if exists(cur, OPSROOT) {
return cur
}
parent := parent(cur)
if parent == "" {
return ""
}
return locateOpsRootSearch(parent)
}
func autoCLIUpdate() error {
cli := os.Getenv("OPS_CMD")
trace("autoCLIUpdate", cli)
cmd := exec.Command(cli, "util", "update-cli")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func checkOperatorVersion(opsRootConfig map[string]interface{}) error {
trace("checkOperatorVersion")
// --- NEW: Check for active operator installation ---
// Execute "ops debug operator:version" to see if an operator is currently deployed.
debugCmd := exec.Command(os.Getenv("OPS_CMD"), "debug", "operator:version")
output, err := debugCmd.Output()
if err != nil || strings.TrimSpace(string(output)) == "" {
// No operator installed or command failed, so suppress the update suggestion.
debug("No active operator installation detected or command failed:", err, "output:", strings.TrimSpace(string(output)))
return fmt.Errorf("no active operator installation detected") // Return an error to prevent printing the update message
}
debug("Active operator version detected:", strings.TrimSpace(string(output)))
// --- END NEW ---
images := opsRootConfig["images"].(map[string]interface{})
operator := images["operator"].(string)
opVer := strings.Split(operator, ":")[1]
cmd := exec.Command(os.Getenv("OPS_CMD"), "util", "check-operator-version", opVer)
return cmd.Run()
}
func setOpsOlarisHash(tasksDir string) error {
trace("setOpsOlarisHash", tasksDir)
r, err := git.PlainOpen(tasksDir)
if err != nil {
return err
}
h, err := r.Head()
if err != nil {
return err
}
debug("tasks hash", h.Hash().String())
os.Setenv("OPS_TASKS", h.Hash().String())
trace("OPS_TASKS", os.Getenv("OPS_TASKS"))
return nil
}
// findTasksDir returns the name of the tasks folder (olaris or oplugins)
// existing under base, or "" if none is found
func findTasksDir(base string) string {
for _, name := range TASKS_DIRS {
if isDir(joinpath(base, name)) {
return name
}
}
return ""
}