policies/recipes/installrecipe.go (107 lines of code) (raw):
// Copyright 2019 Google Inc. All Rights Reserved.
//
// Licensed 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 recipes
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/GoogleCloudPlatform/osconfig/clog"
"cloud.google.com/go/osconfig/agentendpoint/apiv1beta/agentendpointpb"
)
// InstallRecipe installs a recipe.
func InstallRecipe(ctx context.Context, recipe *agentendpointpb.SoftwareRecipe) error {
ctx = clog.WithLabels(ctx, map[string]string{"recipe_name": recipe.GetName()})
steps := recipe.InstallSteps
recipeDB, err := newRecipeDB()
if err != nil {
return err
}
installedRecipe, ok := recipeDB.getRecipe(recipe.Name)
if ok {
clog.Debugf(ctx, "Currently installed version of software recipe %s with version %s.", recipe.GetName(), installedRecipe.Version)
if (installedRecipe.compare(recipe.Version)) && (recipe.DesiredState == agentendpointpb.DesiredState_UPDATED) {
clog.Infof(ctx, "Upgrading software recipe %s from version %s to %s.", recipe.Name, installedRecipe.Version, recipe.GetVersion())
steps = recipe.UpdateSteps
} else {
clog.Debugf(ctx, "Skipping software recipe %s.", recipe.GetName())
return nil
}
} else {
clog.Infof(ctx, "Installing software recipe %s.", recipe.GetName())
}
clog.Debugf(ctx, "Creating working directory for recipe %s.", recipe.GetName())
runID := fmt.Sprintf("run_%d", time.Now().UnixNano())
runDir, err := createBaseDir(recipe, runID)
if err != nil {
return fmt.Errorf("failed to create base directory: %v", err)
}
defer func() {
if err := os.RemoveAll(runDir); err != nil {
clog.Warningf(ctx, "Failed to remove recipe working directory at %q: %v", runDir, err)
}
}()
artifacts, err := fetchArtifacts(ctx, recipe.Artifacts, runDir)
if err != nil {
return fmt.Errorf("failed to obtain artifacts: %v", err)
}
runEnvs := []string{
fmt.Sprintf("RECIPE_NAME=%s", recipe.Name),
fmt.Sprintf("RECIPE_VERSION=%s", recipe.Version),
fmt.Sprintf("RUNID=%s", runID),
}
for artifactID, artifactPath := range artifacts {
runEnvs = append(runEnvs, fmt.Sprintf("%s=%s", artifactID, artifactPath))
}
for i, step := range steps {
clog.Debugf(ctx, "Running step %d: %q", i, step)
stepDir := filepath.Join(runDir, fmt.Sprintf("step%02d", i))
if err := os.MkdirAll(stepDir, 0755); err != nil {
return fmt.Errorf("failed to create recipe step dir %q: %s", stepDir, err)
}
var err error
var stepType string
switch {
case step.GetFileCopy() != nil:
stepType = "CopyFile"
err = stepCopyFile(step.GetFileCopy(), artifacts, runEnvs, stepDir)
case step.GetArchiveExtraction() != nil:
stepType = "ExtractArchive"
err = stepExtractArchive(ctx, step.GetArchiveExtraction(), artifacts, runEnvs, stepDir)
case step.GetMsiInstallation() != nil:
stepType = "InstallMsi"
err = stepInstallMsi(ctx, step.GetMsiInstallation(), artifacts, runEnvs, stepDir)
case step.GetFileExec() != nil:
stepType = "ExecFile"
err = stepExecFile(ctx, step.GetFileExec(), artifacts, runEnvs, stepDir)
case step.GetScriptRun() != nil:
stepType = "RunScript"
err = stepRunScript(ctx, step.GetScriptRun(), artifacts, runEnvs, stepDir)
case step.GetDpkgInstallation() != nil:
stepType = "InstallDpkg"
err = stepInstallDpkg(ctx, step.GetDpkgInstallation(), artifacts)
case step.GetRpmInstallation() != nil:
stepType = "InstallRpm"
err = stepInstallRpm(ctx, step.GetRpmInstallation(), artifacts)
}
if err != nil {
recipeDB.addRecipe(recipe.Name, recipe.Version, false)
if stepType == "" {
return fmt.Errorf("unknown step type for step %d", i)
}
return fmt.Errorf("error running step %d (%s): %v", i, stepType, err)
}
}
clog.Infof(ctx, "All steps completed successfully, marking recipe %s as installed.", recipe.Name)
return recipeDB.addRecipe(recipe.Name, recipe.Version, true)
}
func createBaseDir(recipe *agentendpointpb.SoftwareRecipe, runID string) (string, error) {
name := recipe.Name
if recipe.Version != "" {
name = fmt.Sprintf("%s_%s", name, recipe.Version)
}
dir, err := ioutil.TempDir("", fmt.Sprintf("%s_%s_", name, runID))
if err != nil {
return "", fmt.Errorf("failed to create working dir for recipe: %q %s", recipe.Name, err)
}
return dir, nil
}