Skip to content

Commit d3f424c

Browse files
Merge pull request #895 from dustman9000/promote-rhobs
SREP-4889: Add promote rhobs subcommand
2 parents 3ff19c3 + 3b68e16 commit d3f424c

5 files changed

Lines changed: 370 additions & 0 deletions

File tree

cmd/promote/cmd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/openshift/osdctl/cmd/promote/blocked"
77
"github.com/openshift/osdctl/cmd/promote/dynatrace"
88
"github.com/openshift/osdctl/cmd/promote/managedscripts"
9+
"github.com/openshift/osdctl/cmd/promote/rhobs"
910
"github.com/openshift/osdctl/cmd/promote/saas"
1011
"github.com/spf13/cobra"
1112
)
@@ -23,6 +24,7 @@ func NewCmdPromote() *cobra.Command {
2324
promoteCmd.AddCommand(dynatrace.NewCmdDynatrace())
2425
promoteCmd.AddCommand(managedscripts.NewCmdManagedScripts())
2526
promoteCmd.AddCommand(blocked.NewCmdBlock())
27+
promoteCmd.AddCommand(rhobs.NewCmdRhobs())
2628

2729
return promoteCmd
2830
}

cmd/promote/rhobs/rhobs.go

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
package rhobs
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/openshift/osdctl/pkg/promote"
11+
"github.com/spf13/cobra"
12+
13+
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
14+
)
15+
16+
const (
17+
rhobsSaasDirPath = "data/services/rhobs/rhobs/cicd"
18+
rhobsProdNamespaceRef = "rhobs-production"
19+
)
20+
21+
// SRE-owned saas files for monitoring stack, collection, tenant rules,
22+
// dashboards, and synthetics. RHOBS infra saas files (alertmanager,
23+
// thanos, loki, gateway, cache, objstore, operator) are owned by the
24+
// RHOBS Platform Team and promoted separately.
25+
var sreOwnedServices = map[string]bool{
26+
"saas-hcp-rules": true,
27+
"saas-sc-rules": true,
28+
"saas-hcp-loki-alerts": true,
29+
"saas-hcp-loki-recording-rules": true,
30+
"saas-metric-collection-integration": true,
31+
"saas-metric-collection-stage": true,
32+
"saas-metric-collection-production": true,
33+
"saas-log-forwarder-integration": true,
34+
"saas-log-forwarder-stage": true,
35+
"saas-log-forwarder-production": true,
36+
"saas-log-event-collector-integration": true,
37+
"saas-log-event-collector-stage": true,
38+
"saas-log-event-collector-production": true,
39+
"saas-log-token-refresher-integration": true,
40+
"saas-log-token-refresher-stage": true,
41+
"saas-log-token-refresher-production": true,
42+
"saas-dashboards": true,
43+
"saas-servicemonitors": true,
44+
"saas-synthetics-agent": true,
45+
"saas-synthetics-api": true,
46+
"saas-ocm-log-collection": true,
47+
"saas-ocm-metric-collection": true,
48+
}
49+
50+
type rhobsOptions struct {
51+
list bool
52+
53+
appInterfaceProvidedPath string
54+
configRepoPath string
55+
serviceId string
56+
gitHash string
57+
}
58+
59+
func validateRhobsServiceFilePath(filePath string) string {
60+
base := filepath.Base(filePath)
61+
if !strings.HasPrefix(base, "saas-") || !strings.HasSuffix(base, ".yaml") {
62+
return ""
63+
}
64+
serviceId := strings.TrimSuffix(base, ".yaml")
65+
if !sreOwnedServices[serviceId] {
66+
return ""
67+
}
68+
return filePath
69+
}
70+
71+
func resolveConfigRepoPath(provided string) (string, error) {
72+
if provided != "" {
73+
if _, err := os.Stat(filepath.Join(provided, ".git")); err != nil {
74+
return "", fmt.Errorf("invalid --configRepoDir %q: not a git repository", provided)
75+
}
76+
return provided, nil
77+
}
78+
candidates := []string{
79+
filepath.Join(os.Getenv("HOME"), "src", "configuration"),
80+
filepath.Join(os.Getenv("HOME"), "src", "rhobs-configuration"),
81+
filepath.Join(os.Getenv("HOME"), "src", "rhobs-configuration-gitlab"),
82+
}
83+
for _, p := range candidates {
84+
if _, err := os.Stat(filepath.Join(p, ".git")); err == nil {
85+
return p, nil
86+
}
87+
}
88+
return "", nil
89+
}
90+
91+
type rhobsPromoteCallbacks struct {
92+
promote.DefaultPromoteCallbacks
93+
94+
localRepoPath string
95+
httpsURL string
96+
}
97+
98+
func (c *rhobsPromoteCallbacks) GetResourceTemplateRepoUrl(resourceTemplateNode *kyaml.RNode) (string, error) {
99+
url, err := c.DefaultPromoteCallbacks.GetResourceTemplateRepoUrl(resourceTemplateNode)
100+
if err != nil {
101+
return "", err
102+
}
103+
c.httpsURL = url
104+
if c.localRepoPath != "" {
105+
return c.localRepoPath, nil
106+
}
107+
return url, nil
108+
}
109+
110+
func (c *rhobsPromoteCallbacks) FilterTargets(targetNodes []*kyaml.RNode) ([]*kyaml.RNode, error) {
111+
return promote.FilterTargetsContainingNamespaceRef(targetNodes, rhobsProdNamespaceRef)
112+
}
113+
114+
func (c *rhobsPromoteCallbacks) ComputeCommitMessage(resourceTemplateRepo *promote.Repo, resourceTemplatePath, oldHash, newHash string) (*promote.CommitMessage, error) {
115+
commitMessage, err := c.DefaultPromoteCallbacks.ComputeCommitMessage(resourceTemplateRepo, resourceTemplatePath, oldHash, newHash)
116+
if err != nil {
117+
return nil, err
118+
}
119+
if c.httpsURL != "" {
120+
commitMessage.ChangesURL = fmt.Sprintf("%s/-/compare/%s...%s", c.httpsURL, oldHash, newHash)
121+
}
122+
return commitMessage, nil
123+
}
124+
125+
func isPinnedSHA(ref string) bool {
126+
if len(ref) != 40 {
127+
return false
128+
}
129+
for _, c := range ref {
130+
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
131+
return false
132+
}
133+
}
134+
return true
135+
}
136+
137+
func shortHash(hash string) string {
138+
if len(hash) > 12 {
139+
return hash[:12]
140+
}
141+
return hash
142+
}
143+
144+
func promoteAllServices(appInterfaceClone *promote.AppInterfaceClone, servicesRegistry *promote.ServicesRegistry, gitHash string) error {
145+
branchName := fmt.Sprintf("promote-rhobs-%s", shortHash(gitHash))
146+
if err := appInterfaceClone.CheckoutNewBranch(branchName); err != nil {
147+
return err
148+
}
149+
150+
var promotedIds []string
151+
for _, id := range servicesRegistry.GetServicesIds() {
152+
service, err := servicesRegistry.GetService(id)
153+
if err != nil {
154+
continue
155+
}
156+
157+
updated, err := updateProductionTargets(service, gitHash)
158+
if err != nil {
159+
fmt.Printf("Skipping %s: %v\n", id, err)
160+
continue
161+
}
162+
if !updated {
163+
continue
164+
}
165+
166+
commitMsg := fmt.Sprintf("Promote %s to %s", id, shortHash(gitHash))
167+
if err := appInterfaceClone.Commit(commitMsg); err != nil {
168+
return fmt.Errorf("failed to commit %s: %v", id, err)
169+
}
170+
promotedIds = append(promotedIds, id)
171+
fmt.Printf("Promoted %s\n", id)
172+
}
173+
174+
if len(promotedIds) == 0 {
175+
return errors.New("no RHOBS services had production targets to promote")
176+
}
177+
178+
fmt.Printf("\nPromoted %d service(s) on branch: %s\n", len(promotedIds), branchName)
179+
fmt.Printf("Push the branch and create a MR from: %s\n", appInterfaceClone.GetPath())
180+
return nil
181+
}
182+
183+
func updateProductionTargets(service *promote.Service, newHash string) (bool, error) {
184+
updated := false
185+
err := service.GetResourceTemplatesSequenceNode().VisitElements(func(rtNode *kyaml.RNode) error {
186+
targetsNode, err := kyaml.Lookup("targets").Filter(rtNode)
187+
if err != nil || targetsNode == nil {
188+
return nil
189+
}
190+
return targetsNode.VisitElements(func(targetNode *kyaml.RNode) error {
191+
nsRef, _ := targetNode.GetString("namespace.$ref")
192+
if !strings.Contains(nsRef, rhobsProdNamespaceRef) {
193+
return nil
194+
}
195+
currentRef, err := targetNode.GetString("ref")
196+
if err != nil || currentRef == "" {
197+
return nil
198+
}
199+
if !isPinnedSHA(currentRef) || currentRef == newHash {
200+
return nil
201+
}
202+
_, err = kyaml.SetField("ref", kyaml.NewStringRNode(newHash)).Filter(targetNode)
203+
if err != nil {
204+
return err
205+
}
206+
updated = true
207+
return nil
208+
})
209+
})
210+
if err != nil {
211+
return false, err
212+
}
213+
if updated {
214+
if err := service.Save(); err != nil {
215+
return false, err
216+
}
217+
}
218+
return updated, nil
219+
}
220+
221+
func NewCmdRhobs() *cobra.Command {
222+
ops := &rhobsOptions{}
223+
rhobsCmd := &cobra.Command{
224+
Use: "rhobs",
225+
Short: "Promote RHOBS configuration to production",
226+
Args: cobra.NoArgs,
227+
DisableAutoGenTag: true,
228+
Example: `
229+
# List all RHOBS services
230+
osdctl promote rhobs --list
231+
232+
# Promote all RHOBS services to a specific git hash
233+
osdctl promote rhobs --gitHash <git-hash>
234+
235+
# Promote a single RHOBS service
236+
osdctl promote rhobs --serviceId saas-hcp-rules --gitHash <git-hash>`,
237+
RunE: func(cmd *cobra.Command, args []string) error {
238+
appInterfaceClone, err := promote.FindAppInterfaceClone(ops.appInterfaceProvidedPath)
239+
if err != nil {
240+
return err
241+
}
242+
243+
servicesRegistry, err := promote.NewServicesRegistry(
244+
appInterfaceClone,
245+
validateRhobsServiceFilePath,
246+
rhobsSaasDirPath,
247+
)
248+
if err != nil {
249+
return err
250+
}
251+
252+
if ops.list {
253+
if ops.serviceId != "" || ops.gitHash != "" {
254+
return errors.New("--list cannot be used with --serviceId or --gitHash")
255+
}
256+
257+
fmt.Println("### Available RHOBS services ###")
258+
for _, serviceId := range servicesRegistry.GetServicesIds() {
259+
fmt.Println(serviceId)
260+
}
261+
return nil
262+
}
263+
264+
cmd.SilenceUsage = true
265+
266+
localRepoPath, err := resolveConfigRepoPath(ops.configRepoPath)
267+
if err != nil {
268+
return err
269+
}
270+
if localRepoPath != "" {
271+
fmt.Printf("Using local rhobs/configuration checkout: %s\n\n", localRepoPath)
272+
}
273+
274+
if ops.serviceId != "" {
275+
service, err := servicesRegistry.GetService(ops.serviceId)
276+
if err != nil {
277+
return err
278+
}
279+
return service.Promote(&rhobsPromoteCallbacks{
280+
DefaultPromoteCallbacks: promote.DefaultPromoteCallbacks{Service: service},
281+
localRepoPath: localRepoPath,
282+
}, ops.gitHash)
283+
}
284+
285+
if ops.gitHash == "" {
286+
return errors.New("--gitHash is required when promoting all services")
287+
}
288+
if !isPinnedSHA(ops.gitHash) {
289+
return errors.New("--gitHash must be a 40-character lowercase commit SHA when promoting all services")
290+
}
291+
292+
return promoteAllServices(appInterfaceClone, servicesRegistry, ops.gitHash)
293+
},
294+
}
295+
296+
rhobsCmd.Flags().BoolVarP(&ops.list, "list", "l", false, "List all RHOBS SaaS file names")
297+
rhobsCmd.Flags().StringVarP(&ops.serviceId, "serviceId", "", "", "Name of the SaaS file (without extension)")
298+
rhobsCmd.Flags().StringVarP(&ops.gitHash, "gitHash", "g", "", "Git hash of rhobs/configuration to promote to (required for bulk promotion; defaults to HEAD for --serviceId)")
299+
rhobsCmd.Flags().StringVarP(&ops.appInterfaceProvidedPath, "appInterfaceDir", "", "", "Location of app-interface checkout")
300+
rhobsCmd.Flags().StringVarP(&ops.configRepoPath, "configRepoDir", "", "", "Location of rhobs/configuration checkout (auto-detected from ~/src/)")
301+
302+
return rhobsCmd
303+
}

docs/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
- `block` - Add a blocked version to a component in app.yaml
139139
- `dynatrace` - Utilities to promote dynatrace
140140
- `managedscripts` - Promote https://github.com/openshift/managed-scripts
141+
- `rhobs` - Promote RHOBS configuration to production
141142
- `saas` - Utilities to promote SaaS services/operators
142143
- `rhobs` - RHOBS.next related utilities
143144
- `cell` - Get the RHOBS cell for a given cluster
@@ -4261,6 +4262,26 @@ osdctl promote managedscripts [flags]
42614262
-S, --skip-version-check skip checking to see if this is the most recent release
42624263
```
42634264

4265+
### osdctl promote rhobs
4266+
4267+
Promote RHOBS configuration to production
4268+
4269+
```
4270+
osdctl promote rhobs [flags]
4271+
```
4272+
4273+
#### Flags
4274+
4275+
```
4276+
--appInterfaceDir string Location of app-interface checkout
4277+
--configRepoDir string Location of rhobs/configuration checkout (auto-detected from ~/src/)
4278+
-g, --gitHash string Git hash of rhobs/configuration to promote to (required for bulk promotion; defaults to HEAD for --serviceId)
4279+
-h, --help help for rhobs
4280+
-l, --list List all RHOBS SaaS file names
4281+
--serviceId string Name of the SaaS file (without extension)
4282+
-S, --skip-version-check skip checking to see if this is the most recent release
4283+
```
4284+
42644285
### osdctl promote saas
42654286

42664287
Utilities to promote SaaS services/operators

docs/osdctl_promote.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ Utilities to promote services/operators
2020
* [osdctl promote block](osdctl_promote_block.md) - Add a blocked version to a component in app.yaml
2121
* [osdctl promote dynatrace](osdctl_promote_dynatrace.md) - Utilities to promote dynatrace
2222
* [osdctl promote managedscripts](osdctl_promote_managedscripts.md) - Promote https://github.com/openshift/managed-scripts
23+
* [osdctl promote rhobs](osdctl_promote_rhobs.md) - Promote RHOBS configuration to production
2324
* [osdctl promote saas](osdctl_promote_saas.md) - Utilities to promote SaaS services/operators
2425

0 commit comments

Comments
 (0)