Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions cmd/promote/blocked/blocked.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package blocked

import (
"fmt"
"path/filepath"
"strings"

"github.com/openshift/osdctl/pkg/promote"
"github.com/spf13/cobra"
)

type blockedOptions struct {
list bool
all bool

appInterfaceProvidedPath string
serviceId string
componentName string
gitHash string
}

// NewCmdBlock implements the block command to add a blocked version to a component in app.yaml
func NewCmdBlock() *cobra.Command {
ops := &blockedOptions{}
blockedCmd := &cobra.Command{
Use: "block",
Short: "Add a blocked version to a component in app.yaml",
Long: `Add a SHA commit hash to the blockedVersions list for a code component
in the application's app.yaml file. This prevents the specified version
from being promoted through progressive delivery.

The command locates the app.yaml through the SaaS service file, finds
the specified component by name, and appends the git hash to its
codeComponents[].blockedVersions array. If the blockedVersions field
does not yet exist, it will be created.

Duplicate entries are rejected with an error.`,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
Example: `
# List all services and their components
osdctl promote block --list

# Block a specific version for a single component
osdctl promote block --serviceId <service> --component <component-name> --gitHash <sha>

# Block a specific version for all components of a service
osdctl promote block --serviceId <service> --all --gitHash <sha>

# With explicit app-interface path
osdctl promote block --serviceId <service> --component <component-name> --gitHash <sha> --appInterfaceDir /path/to/app-interface`,
RunE: func(cmd *cobra.Command, args []string) error {
if ops.list {
if ops.serviceId != "" || ops.componentName != "" || ops.gitHash != "" || ops.all {
return fmt.Errorf("--list cannot be used with --serviceId, --component, --all or --gitHash")
}
} else {
if ops.serviceId == "" {
return fmt.Errorf("--serviceId is required (use --list to see available services and components)")
}
if ops.all && ops.componentName != "" {
return fmt.Errorf("--all and --component are mutually exclusive")
}
if !ops.all && ops.componentName == "" {
return fmt.Errorf("--component or --all is required (use --list to see available services and components)")
}
if ops.gitHash == "" {
return fmt.Errorf("--gitHash is required")
}
}

cmd.SilenceUsage = true

appInterfaceClone, err := promote.FindAppInterfaceClone(ops.appInterfaceProvidedPath)
if err != nil {
return err
}

servicesRegistry, err := promote.NewServicesRegistry(
appInterfaceClone,
func(filePath string) string { return filePath },
"data/services/osd-operators/cicd/saas",
"data/services/backplane/cicd/saas",
"data/services/configuration-anomaly-detection/cicd",
)
if err != nil {
return err
}

if ops.list {
fmt.Println("### Services and their components ###")
for _, serviceId := range servicesRegistry.GetServicesIds() {
service, err := servicesRegistry.GetService(serviceId)
if err != nil {
fmt.Printf(" %s (error: %v)\n", serviceId, err)
continue
}
componentNames, err := service.GetApplication().GetComponentNames()
if err != nil {
fmt.Printf(" %s (error reading components: %v)\n", serviceId, err)
continue
}
fmt.Printf(" %s\n", serviceId)
for _, name := range componentNames {
fmt.Printf(" - %s\n", name)
}
}
return nil
}

service, err := servicesRegistry.GetService(ops.serviceId)
if err != nil {
return err
}

application := service.GetApplication()

isClean, err := appInterfaceClone.IsClean()
if err != nil {
return err
}
if !isClean {
return fmt.Errorf("app-interface clone in '%s' has uncommitted changes, please commit or stash them before proceeding", appInterfaceClone.GetPath())
}

branchName := fmt.Sprintf("block-%s-%s", ops.serviceId, ops.gitHash)
err = appInterfaceClone.CheckoutNewBranch(branchName)
if err != nil {
return err
}

var components []*promote.CodeComponent

if ops.all {
components, err = application.GetAllComponents()
if err != nil {
return err
}
} else {
component, err := application.GetComponentByName(ops.componentName)
if err != nil {
return err
}
components = []*promote.CodeComponent{component}
}

var blockedNames []string
for _, component := range components {
err = component.AddBlockedVersion(ops.gitHash)
if err != nil {
return err
}
blockedNames = append(blockedNames, component.GetName())
}

err = application.Save()
if err != nil {
return fmt.Errorf("failed to save application '%s': %v", application.GetFilePath(), err)
}

targetLabel := strings.Join(blockedNames, ", ")

var commitMessage string
if ops.all {
commitMessage = fmt.Sprintf("Block version %s for all components of %s\n\nAdd %s to blockedVersions for components [%s] in '%s'.",
ops.gitHash,
ops.serviceId,
ops.gitHash,
targetLabel,
filepath.Base(application.GetFilePath()),
)
} else {
commitMessage = fmt.Sprintf("Block version %s for %s\n\nAdd %s to blockedVersions for component '%s' in '%s'.",
ops.gitHash,
ops.componentName,
ops.gitHash,
ops.componentName,
filepath.Base(application.GetFilePath()),
)
}

err = appInterfaceClone.Commit(commitMessage)
if err != nil {
return err
}

fmt.Println("SUCCESS!")
fmt.Printf("Blocked version %s for: %s\n", ops.gitHash, targetLabel)
fmt.Printf("Application file: %s\n", application.GetFilePath())
fmt.Println("")
fmt.Println("------------- Commit message -------------")
fmt.Println(commitMessage)
fmt.Println("------------- End of commit message -------------")
fmt.Println("")
fmt.Printf("Push the following branch on your fork and create a MR from it: %s\n", branchName)

appInterfacePath := appInterfaceClone.GetPath()
if strings.Contains(appInterfacePath, "app-interface") {
fmt.Printf("\n(reminder: the push has to be run from the following Git clone: %s)\n", appInterfacePath)
}

return nil
},
}

blockedCmd.Flags().BoolVarP(&ops.list, "list", "l", false, "List all services and their components")
blockedCmd.Flags().BoolVarP(&ops.all, "all", "a", false, "Block the version for all components of the service (mutually exclusive with --component)")
blockedCmd.Flags().StringVarP(&ops.serviceId, "serviceId", "", "", "Name of the SaaS service file (without extension)")
blockedCmd.Flags().StringVarP(&ops.componentName, "component", "c", "", "Name of the code component in app.yaml")
blockedCmd.Flags().StringVarP(&ops.gitHash, "gitHash", "g", "", "SHA commit hash to add to blockedVersions")
blockedCmd.Flags().StringVarP(&ops.appInterfaceProvidedPath, "appInterfaceDir", "", "", "Location of app-interface checkout. Falls back to the current working directory")
blockedCmd.MarkFlagsMutuallyExclusive("all", "component")

return blockedCmd
}
2 changes: 2 additions & 0 deletions cmd/promote/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package promote
import (
"fmt"

"github.com/openshift/osdctl/cmd/promote/blocked"
"github.com/openshift/osdctl/cmd/promote/dynatrace"
"github.com/openshift/osdctl/cmd/promote/managedscripts"
"github.com/openshift/osdctl/cmd/promote/saas"
Expand All @@ -21,6 +22,7 @@ func NewCmdPromote() *cobra.Command {
promoteCmd.AddCommand(saas.NewCmdSaas())
promoteCmd.AddCommand(dynatrace.NewCmdDynatrace())
promoteCmd.AddCommand(managedscripts.NewCmdManagedScripts())
promoteCmd.AddCommand(blocked.NewCmdBlock())

return promoteCmd
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/promote/dynatrace/dt_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"strings"

"github.com/openshift/osdctl/cmd/promote/iexec"
"github.com/openshift/osdctl/cmd/promote/utils"
"github.com/openshift/osdctl/pkg/promote"

kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
)
Expand All @@ -35,7 +35,7 @@ func validateDynatraceServiceFilePath(filePath string) string {
return filePath
}

func getResourceTemplatesPaths(serviceRegistry *utils.ServicesRegistry, serviceId string) string {
func getResourceTemplatesPaths(serviceRegistry *promote.ServicesRegistry, serviceId string) string {
service, err := serviceRegistry.GetService(serviceId)
if err != nil {
return ""
Expand All @@ -58,7 +58,7 @@ func getResourceTemplatesPaths(serviceRegistry *utils.ServicesRegistry, serviceI
return strings.Join(paths, ", ")
}

func listServiceIds(serviceRegistry *utils.ServicesRegistry) error {
func listServiceIds(serviceRegistry *promote.ServicesRegistry) error {
serviceIds := serviceRegistry.GetServicesIds()

fmt.Println("### Available Dynatrace components ###")
Expand Down
8 changes: 4 additions & 4 deletions cmd/promote/dynatrace/dynatrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"errors"
"fmt"

"github.com/openshift/osdctl/cmd/promote/utils"
"github.com/openshift/osdctl/pkg/promote"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -89,12 +89,12 @@ TERRAFORM MODULES:
} else {
ops.validateSaasFlow()

appInterfaceClone, err := utils.FindAppInterfaceClone(ops.appInterfaceProvidedPath)
appInterfaceClone, err := promote.FindAppInterfaceClone(ops.appInterfaceProvidedPath)
if err != nil {
return err
}

servicesRegistry, err := utils.NewServicesRegistry(
servicesRegistry, err := promote.NewServicesRegistry(
appInterfaceClone,
validateDynatraceServiceFilePath,
saasDynatraceDir,
Expand Down Expand Up @@ -122,7 +122,7 @@ TERRAFORM MODULES:
if err != nil {
return err
}
err = service.Promote(&utils.DefaultPromoteCallbacks{Service: service}, ops.gitHash)
err = service.Promote(&promote.DefaultPromoteCallbacks{Service: service}, ops.gitHash)

if err != nil {
return fmt.Errorf("error while promoting service: %v", err)
Expand Down
12 changes: 6 additions & 6 deletions cmd/promote/managedscripts/managed_scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"path/filepath"

"github.com/openshift/osdctl/cmd/promote/utils"
"github.com/openshift/osdctl/pkg/promote"
"github.com/spf13/cobra"

kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
Expand All @@ -23,11 +23,11 @@ type managedScriptsOptions struct {
}

type promoteCallbacks struct {
utils.DefaultPromoteCallbacks
promote.DefaultPromoteCallbacks
}

func (c *promoteCallbacks) FilterTargets(targetNodes []*kyaml.RNode) ([]*kyaml.RNode, error) {
return utils.FilterTargetsContainingNamespaceRef(targetNodes, prodNamespaceRef)
return promote.FilterTargetsContainingNamespaceRef(targetNodes, prodNamespaceRef)
}

func (*promoteCallbacks) GetResourceTemplateRepoUrl(*kyaml.RNode) (string, error) {
Expand Down Expand Up @@ -68,22 +68,22 @@ func NewCmdManagedScripts() *cobra.Command {
# Promote managed-scripts repo
osdctl promote managedscripts --gitHash <git-hash>`,
RunE: func(cmd *cobra.Command, args []string) error {
appInterfaceClone, err := utils.FindAppInterfaceClone(ops.appInterfaceProvidedPath)
appInterfaceClone, err := promote.FindAppInterfaceClone(ops.appInterfaceProvidedPath)
if err != nil {
return err
}

cmd.SilenceUsage = true

service, err := utils.ReadServiceFromFile(
service, err := promote.ReadServiceFromFile(
appInterfaceClone,
filepath.Join(appInterfaceClone.GetPath(), serviceRelPath))
if err != nil {
return err
}

return service.Promote(&promoteCallbacks{
DefaultPromoteCallbacks: utils.DefaultPromoteCallbacks{Service: service},
DefaultPromoteCallbacks: promote.DefaultPromoteCallbacks{Service: service},
}, ops.gitHash)
},
}
Expand Down
Loading