diff --git a/cmd/bundle.go b/cmd/bundle.go index abea76de..57c266a0 100644 --- a/cmd/bundle.go +++ b/cmd/bundle.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "time" "github.com/automationbroker/apb/pkg/config" @@ -249,6 +250,23 @@ func executeBundle(action string, args []string) (podName string) { log.Errorf("Failed to execute bundle [%v]: %v", args[0], err) return "" } + id := strings.Split(pn, "bundle-")[1] + id = strings.TrimPrefix(id, "provision-") + id = strings.TrimPrefix(id, "deprovision-") + switch action { + case "provision": + // Add instance to ProvisionedInstances + err = addInstance(args[0], bundleNamespace, id) + if err != nil { + log.Errorf("Failed to add instance ID to list of provisioned instances") + } + case "deprovision": + // Remove instance from ProvisionedInstances + err = removeInstance(args[0], bundleNamespace, id) + if err != nil { + log.Errorf("Failed to remove instance ID from list of provisioned instances") + } + } return pn } @@ -479,3 +497,65 @@ func processLineBreaks(text []byte, lineBreakText []byte, breakAfter int) []byte } return newText } + +func addInstance(name, namespace, id string) error { + var instanceConfigs []config.ProvisionedInstance + err := config.ProvisionedInstances.UnmarshalKey("ProvisionedInstances", &instanceConfigs) + if err != nil { + return err + } + + for i, instance := range instanceConfigs { + if instance.BundleName == name { + log.Debugf("Adding instance") + instanceConfigs[i].InstanceIDs[namespace] = append(instance.InstanceIDs[namespace], id) + err = config.UpdateCachedInstances(config.ProvisionedInstances, instanceConfigs) + if err != nil { + return err + } + return nil + } + } + + idMap := make(map[string][]string) + idMap[namespace] = []string{id} + instance := config.ProvisionedInstance{ + BundleName: name, + InstanceIDs: idMap, + } + + instanceConfigs = append(instanceConfigs, instance) + err = config.UpdateCachedInstances(config.ProvisionedInstances, instanceConfigs) + if err != nil { + return err + } + return nil +} + +func removeInstance(name, namespace, id string) error { + var instanceConfigs []config.ProvisionedInstance + var foundBundle = false + err := config.ProvisionedInstances.UnmarshalKey("ProvisionedInstances", &instanceConfigs) + if err != nil { + return err + } + for i, instance := range instanceConfigs { + if instance.BundleName == name { + foundBundle = true + for j, instanceID := range instance.InstanceIDs[namespace] { + if instanceID == id { + // Remove instance + instanceConfigs[i].InstanceIDs[namespace] = append(instance.InstanceIDs[namespace][:j], instance.InstanceIDs[namespace][j+1:]...) + err = config.UpdateCachedInstances(config.ProvisionedInstances, instanceConfigs) + if err != nil { + return err + } + } + } + } + } + if foundBundle == false { + log.Errorf("Found no provisioned instances of [%v]", name) + } + return nil +} diff --git a/cmd/root.go b/cmd/root.go index ca62ca72..5d2c4a4f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -57,6 +57,8 @@ func initConfig() { // Load or create registries.json config.Registries, _ = config.InitJSONConfig(cfgDir, "registries") + // Load or create instances.json + config.ProvisionedInstances, _ = config.InitJSONConfig(cfgDir, "instances") // Load or create defaults.json config.Defaults, isNewDefaultsConfig = config.InitJSONConfig(cfgDir, "defaults") if isNewDefaultsConfig { diff --git a/pkg/config/config.go b/pkg/config/config.go index 43f8a023..5b451a1f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -32,6 +32,9 @@ var LoadedDefaults DefaultSettings // Registries stores APB registry and spec data var Registries *viper.Viper +// ProvisionedInstances stores APB instances +var ProvisionedInstances *viper.Viper + // InitJSONConfig will load (or create if needed) a JSON config at ~/home/.apb/configName.json or configDir/configName.json func InitJSONConfig(configDir string, configName string) (config *viper.Viper, isNewConfig bool) { var configPath string @@ -70,17 +73,33 @@ func InitJSONConfig(configDir string, configName string) (config *viper.Viper, i return viperConfig, isNewConfig } +// UpdateCachedInstances saves the contents of instanceList to a configuration file +func UpdateCachedInstances(viperConfig *viper.Viper, instanceList []ProvisionedInstance) error { + viperConfig.Set("ProvisionedInstances", instanceList) + err := viperConfig.WriteConfig() + if err != nil { + return err + } + return nil +} + // UpdateCachedRegistries saves the contents of regList to a configuration file func UpdateCachedRegistries(viperConfig *viper.Viper, regList []Registry) error { viperConfig.Set("Registries", regList) - viperConfig.WriteConfig() + err := viperConfig.WriteConfig() + if err != nil { + return err + } return nil } // UpdateCachedDefaults saves the contents of defaults to a configuration file func UpdateCachedDefaults(viperConfig *viper.Viper, defaults *DefaultSettings) error { viperConfig.Set("Defaults", defaults) - viperConfig.WriteConfig() + err := viperConfig.WriteConfig() + if err != nil { + return err + } return nil } diff --git a/pkg/config/types.go b/pkg/config/types.go index 6b620415..760988d9 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -5,6 +5,12 @@ import ( "github.com/automationbroker/bundle-lib/registries" ) +// ProvisionedInstance stores a list of provisioned uuids associated with a bundle +type ProvisionedInstance struct { + BundleName string + InstanceIDs map[string][]string +} + // Registry stores a single registry config and references all associated bundle specs type Registry struct { Config registries.Config diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 1b9abba5..92b7934b 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -42,9 +42,20 @@ import ( // RunBundle will run the bundle's action in the given namespace func RunBundle(action string, ns string, bundleName string, sandboxRole string, bundleRegistry string, printLogs bool, skipParams bool, args []string) (podName string, err error) { reg := []config.Registry{} + var id string var targetSpec *bundle.Spec var candidateSpecs []*bundle.Spec - podName = fmt.Sprintf("bundle-%s", uuid.New()) + + if action == "deprovision" { + id, err = getProvisionedInstanceId(bundleName, ns) + if err != nil { + return "", err + } + } else { + id = uuid.New() + } + + podName = fmt.Sprintf("bundle-%s-%s", action, id) config.Registries.UnmarshalKey("Registries", ®) for _, r := range reg { if len(bundleRegistry) > 0 && r.Config.Name != bundleRegistry { @@ -88,7 +99,7 @@ func RunBundle(action string, ns string, bundleName string, sandboxRole string, } } - extraVars, err := createExtraVars(ns, ¶ms, plan) + extraVars, err := createExtraVars(id, ns, ¶ms, plan) if err != nil { return "", err } @@ -107,6 +118,7 @@ func RunBundle(action string, ns string, bundleName string, sandboxRole string, serviceAccount, namespace, err := runtime.Provider.CreateSandbox(podName, ns, targets, sandboxRole, labels) if err != nil { fmt.Printf("\nProblem creating sandbox [%s] to run APB. Did you run `oc new-project %s` first?\n\n", podName, ns) + log.Errorf("error creating sandbox: %v", err) os.Exit(-1) } @@ -343,7 +355,7 @@ func createPodEnv(executionContext runtime.ExecutionContext) []v1.EnvVar { return podEnv } -func createExtraVars(targetNamespace string, parameters *bundle.Parameters, plan bundle.Plan) (string, error) { +func createExtraVars(id string, targetNamespace string, parameters *bundle.Parameters, plan bundle.Plan) (string, error) { var paramsCopy bundle.Parameters if parameters != nil && *parameters != nil { paramsCopy = *parameters @@ -357,8 +369,8 @@ func createExtraVars(targetNamespace string, parameters *bundle.Parameters, plan paramsCopy["cluster"] = "openshift" paramsCopy["_apb_plan_id"] = plan.Name - paramsCopy["_apb_service_instance_id"] = "1234" - paramsCopy["_apb_service_class_id"] = "1234" + paramsCopy["_apb_service_instance_id"] = id + paramsCopy["_apb_service_class_id"] = id extraVars, err := json.Marshal(paramsCopy) return string(extraVars), err } @@ -400,3 +412,46 @@ func contains(s []string, t string) bool { } return false } + +func getProvisionedInstanceId(name, namespace string) (string, error) { + var instanceConfigs []config.ProvisionedInstance + err := config.ProvisionedInstances.UnmarshalKey("ProvisionedInstances", &instanceConfigs) + if err != nil { + return "", err + } + for _, instance := range instanceConfigs { + if instance.BundleName == name { + if len(instance.InstanceIDs[namespace]) == 1 { + return instance.InstanceIDs[namespace][0], nil + } else if len(instance.InstanceIDs[namespace]) == 0 { + return "", fmt.Errorf("found no available instances in namespace [%v]", namespace) + } else { + // Select instance + fmt.Printf("Found more than one service instance for bundle [%v]:\n", name) + for i, instance := range instance.InstanceIDs[namespace] { + fmt.Printf("[%v] - %v\n", i, instance) + } + var inputValid = false + for !inputValid { + var input string + fmt.Printf("Enter the number of the instance ID you would wish to deprovision: ") + fmt.Scanln(&input) + if input == "" { + continue + } + intInput, err := strconv.Atoi(input) + if err != nil { + fmt.Printf("Input was not a valid integer, please enter again.\n") + continue + } + if intInput >= len(instance.InstanceIDs[namespace]) || intInput < 0 { + fmt.Printf("Input is out of range. Please select an integer from 0-%v\n", len(instance.InstanceIDs[namespace])-1) + continue + } + return instance.InstanceIDs[namespace][intInput], nil + } + } + } + } + return "", fmt.Errorf("No provisioned instances for bundle [%v] in namespace [%v]", name, namespace) +}