diff --git a/.github/workflows/check-pull-request.yaml b/.github/workflows/check-pull-request.yaml index 4901e5a..686178b 100644 --- a/.github/workflows/check-pull-request.yaml +++ b/.github/workflows/check-pull-request.yaml @@ -31,7 +31,7 @@ jobs: uses: golangci/golangci-lint-action@v2 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.52.2 + version: v1.61.0 # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/cmd/ocm-support/root.go b/cmd/ocm-support/root.go index 6980f19..6fdfdd5 100644 --- a/cmd/ocm-support/root.go +++ b/cmd/ocm-support/root.go @@ -28,6 +28,7 @@ import ( "github.com/openshift-online/ocm-support-cli/cmd/ocm-support/delete" "github.com/openshift-online/ocm-support-cli/cmd/ocm-support/get" "github.com/openshift-online/ocm-support-cli/cmd/ocm-support/patch" + "github.com/openshift-online/ocm-support-cli/cmd/ocm-support/sync-cloud-resources" "github.com/openshift-online/ocm-support-cli/cmd/ocm-support/version" ) @@ -86,6 +87,7 @@ func init() { rootCmd.AddCommand(get.Cmd) rootCmd.AddCommand(delete.Cmd) rootCmd.AddCommand(patch.Cmd) + rootCmd.AddCommand(sync_cloud_resources.Cmd) // Set the log level before each command runs. cobra.OnInitialize(initLogLevel) diff --git a/cmd/ocm-support/sync-cloud-resources/cmd.go b/cmd/ocm-support/sync-cloud-resources/cmd.go new file mode 100644 index 0000000..16da150 --- /dev/null +++ b/cmd/ocm-support/sync-cloud-resources/cmd.go @@ -0,0 +1,208 @@ +package sync_cloud_resources + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "os/exec" + + "github.com/spf13/cobra" +) + +var Cmd = &cobra.Command{ + Use: "sync-cloud-resources [branch-name] [csv-path]", + Short: "Syncs cloud resources in AMS and generates quota rules for them", + Long: "Syncs cloud resources in AMS and generates quota rules for them", + RunE: syncCloudResources, + Args: cobra.ExactArgs(2), +} + +var args struct { + dryRun bool +} + +func init() { + flags := Cmd.Flags() + flags.BoolVar(&args.dryRun, "dry-run", true, "If false, it commits the generated cloud resources and quota rule changes to the remote branch at https://gitlab.cee.redhat.com/service/uhc-account-manager") +} + +func syncCloudResources(cmd *cobra.Command, argv []string) error { + branchName := argv[0] + csvPath := argv[1] + if branchName == "" { + return fmt.Errorf("branch name cannot be empty") + } + if csvPath == "" { + return fmt.Errorf("csv path cannot be empty") + } + + fmt.Println("Validating branch name", branchName) + err := CheckRefFormat(branchName) + if err != nil { + return fmt.Errorf("incorrect branch name: %v", err) + } + + fmt.Println("Validating csv path", csvPath) + err = CheckIfFileExists(csvPath) + if err != nil { + return fmt.Errorf("an error occurred while checking if the csv files exists: %v", err) + } + + fmt.Println("Creating temporary directory") + tempDir, err := os.MkdirTemp(os.TempDir(), branchName) + if err != nil { + return fmt.Errorf("an error occurred while creating temporary directory: %v", err) + } + + amsUpstreamRepo := "git@gitlab.cee.redhat.com:service/uhc-account-manager.git" + amsRepo := gitRepo{ + repoUrl: amsUpstreamRepo, + localPath: tempDir, + } + + fmt.Println("Cloning AMS repo at:", tempDir) + err = amsRepo.Clone() + if err != nil { + return fmt.Errorf("an error occurred while cloning AMS repo: %v", err) + } + + fmt.Println("Creating a new branch") + err = amsRepo.Branch(branchName) + if err != nil { + return fmt.Errorf("an error occurred while creating a new branch: %v", err) + } + + fmt.Println("Replacing cloud resources file") + err = ReplaceFileContent(fmt.Sprintf("%s/config/quota-cloud-resources.csv", tempDir), csvPath) + if err != nil { + return fmt.Errorf("an error occurred while getting the head of the reference: %v", err) + } + + fmt.Println("Generating quota rules") + _, err = ExecuteCmd(fmt.Sprintf("cd %s && make generate-quota", tempDir)) + if err != nil { + return fmt.Errorf("an error occurred while generating quota rules: %v", err) + } + + fmt.Println("Staging changes") + err = amsRepo.StageAllFiles() + if err != nil { + return fmt.Errorf("an error occurred while staging the files: %v", err) + } + + fmt.Println("Committing changes") + err = amsRepo.Commit(fmt.Sprintf("Syncing cloud resources and quota rules for %s", branchName)) + if err != nil { + return fmt.Errorf("an error occurred committing the changes: %v", err) + } + + fmt.Println("Pushing changes to remote branch") + if args.dryRun { + fmt.Println("DRY RUN: Would push the changes to remote branch:", branchName) + return nil + } + err = amsRepo.Push("origin", branchName) + if err != nil { + return fmt.Errorf("an error occurred while pushing the changes: %v", err) + } + return nil +} + +func ReplaceFileContent(originalFilePath, filePathWithUpdatedText string) error { + originalFile, err := os.OpenFile(originalFilePath, os.O_WRONLY|os.O_TRUNC, 0666) + if err != nil { + return err + } + defer originalFile.Close() + + newFile, err := os.Open(filePathWithUpdatedText) + if err != nil { + return err + } + defer newFile.Close() + _, err = io.Copy(originalFile, newFile) + if err != nil { + return err + } + err = originalFile.Sync() + return err +} + +func CheckIfFileExists(filepath string) error { + if _, err := os.Stat(filepath); errors.Is(err, os.ErrNotExist) { + return err + } + return nil +} + +type gitRepo struct { + repoUrl string + localPath string +} + +func ExecuteCmd(command string) (string, error) { + fmt.Println(command) + app := "bash" + arg1 := "-c" + cmd := exec.Command(app, arg1, command) + var stdBuffer bytes.Buffer + mw := io.MultiWriter(os.Stdout, &stdBuffer) + cmd.Stdout = mw + cmd.Stderr = mw + err := cmd.Run() + if err != nil { + err := fmt.Errorf("%v : %s", err, stdBuffer.String()) + return "", err + } + return stdBuffer.String(), nil +} + +func CheckRefFormat(branchName string) error { + _, err := ExecuteCmd(fmt.Sprintf("git check-ref-format --branch %s", branchName)) + if err != nil { + return err + } + return nil +} + +func (g gitRepo) Clone() error { + _, err := ExecuteCmd(fmt.Sprintf("git clone %s %s", g.repoUrl, g.localPath)) + return err +} + +func (g gitRepo) RemoteAdd(remoteName, remoteUrl string) error { + _, err := ExecuteCmd(fmt.Sprintf("git -C %s remote add %s %s", g.localPath, remoteName, remoteUrl)) + return err +} + +func (g gitRepo) Branch(branchName string) error { + _, err := ExecuteCmd(fmt.Sprintf("git -C %s checkout -b %s", g.localPath, branchName)) + return err +} + +func (g gitRepo) StageFiles(files *[]string) error { + for _, file := range *files { + _, err := ExecuteCmd(fmt.Sprintf("git -C %s stage %s", g.localPath, file)) + if err != nil { + return err + } + } + return nil +} + +func (g gitRepo) StageAllFiles() error { + _, err := ExecuteCmd(fmt.Sprintf("git -C %s stage .", g.localPath)) + return err +} + +func (g gitRepo) Commit(message string) error { + _, err := ExecuteCmd(fmt.Sprintf("git -C %s commit -m \"%s\"", g.localPath, message)) + return err +} + +func (g gitRepo) Push(remote, remoteUrl string) error { + _, err := ExecuteCmd(fmt.Sprintf("git -C %s push %s %s", g.localPath, remote, remoteUrl)) + return err +}