├── .golangciversion ├── OWNERS ├── .gitignore ├── pkg ├── output │ ├── tables │ │ ├── wifconfigs.yaml │ │ ├── addons.yaml │ │ ├── idps.yaml │ │ ├── plugins.yaml │ │ ├── orgs.yaml │ │ ├── endpoints.yaml │ │ ├── ingresses.yaml │ │ └── clusters.yaml │ ├── main_test.go │ └── terminal.go ├── urls │ ├── main_test.go │ └── well_known.go ├── gcp │ ├── helpers.go │ ├── error_handlers.go │ └── policy.go ├── utils │ ├── main_test.go │ ├── slices.go │ ├── strings_test.go │ ├── strings.go │ ├── slices_test.go │ ├── jwks.go │ └── jwks_test.go ├── ingress │ ├── main_test.go │ ├── describe_test.go │ └── describe.go ├── info │ └── info.go ├── properties │ └── properties.go ├── config │ ├── main_test.go │ └── token.go ├── ocm │ ├── cluster.go │ ├── connection.go │ └── mock_cluster.go ├── cluster │ ├── describe_test.go │ └── versions.go ├── debug │ └── flag.go ├── billing │ └── billing.go ├── provider │ ├── regions.go │ ├── machine_types.go │ ├── wif_configs.go │ ├── subnetworks.go │ └── encryption.go ├── account │ └── account.go ├── plugin │ └── plugin.go └── dump │ └── dump.go ├── .github ├── dependabot.yml └── workflows │ ├── requirements.txt │ └── check-pull-request.yaml ├── testdata └── test-ownertrust-gpg.txt ├── cmd └── ocm │ ├── gcp │ ├── gcp_suite_test.go │ ├── flag_descriptions.go │ ├── describe-wif-config.go │ ├── verify-wif-config.go │ ├── get-wif-config.go │ ├── helpers.go │ └── list-wif-config.go │ ├── edit │ ├── ingress │ │ ├── main_test.go │ │ └── cmd_test.go │ └── cmd.go │ ├── create │ ├── machinepool │ │ ├── machinepool_suite_test.go │ │ └── helpers.go │ ├── cmd.go │ └── idp │ │ ├── htpasswd.go │ │ ├── google.go │ │ └── ldap.go │ ├── resume │ ├── cmd.go │ └── cluster │ │ └── cmd.go │ ├── hibernate │ ├── cmd.go │ └── cluster │ │ └── cmd.go │ ├── pop │ ├── cmd.go │ └── job │ │ └── job.go │ ├── push │ ├── cmd.go │ └── job │ │ └── job.go │ ├── fail │ ├── cmd.go │ └── job │ │ └── job.go │ ├── success │ ├── cmd.go │ └── job │ │ └── job.go │ ├── plugin │ └── cmd.go │ ├── describe │ └── cmd.go │ ├── version │ └── cmd.go │ ├── cluster │ ├── cmd.go │ └── status │ │ └── status.go │ ├── account │ ├── cmd.go │ ├── status │ │ └── cmd.go │ └── roles │ │ └── cmd.go │ ├── list │ ├── rhRegion │ │ └── cmd.go │ ├── cmd.go │ ├── version │ │ └── cmd.go │ ├── user │ │ └── cmd.go │ ├── upgradepolicy │ │ └── cmd.go │ └── quota │ │ └── cmd.go │ ├── logout │ └── cmd.go │ ├── whoami │ └── cmd.go │ ├── config │ ├── get │ │ └── get.go │ ├── set │ │ └── set.go │ └── cmd.go │ ├── completion │ └── cmd.go │ ├── delete │ ├── machinepool │ │ └── cmd.go │ ├── upgradepolicy │ │ └── cmd.go │ └── user │ │ └── cmd.go │ ├── patch │ └── cmd.go │ ├── post │ └── cmd.go │ └── get │ └── cmd.go ├── tests ├── mocks.go ├── version_test.go ├── token_test.go ├── whoami_test.go └── logout_test.go ├── .golangci.yml ├── hack ├── build_release_images.sh ├── README.md ├── tag-release.sh ├── build_image.sh └── commit-release.sh ├── docker └── Dockerfile ├── CONTRIBUTING.md └── Makefile /.golangciversion: -------------------------------------------------------------------------------- 1 | 1.59.1 2 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - rcampos2029 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | *.patch 3 | *.rej 4 | /.vscode 5 | /ocm 6 | /ocm-* 7 | /.idea 8 | -------------------------------------------------------------------------------- /pkg/output/tables/wifconfigs.yaml: -------------------------------------------------------------------------------- 1 | columns: 2 | - name: id 3 | header: ID 4 | - name: displayName 5 | header: NAME 6 | - name: gcp.project_id 7 | header: PROJECT ID -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 20 8 | -------------------------------------------------------------------------------- /testdata/test-ownertrust-gpg.txt: -------------------------------------------------------------------------------- 1 | # List of assigned trustvalues, created Tue Feb 20 11:49:38 2024 EST 2 | # (Use "gpg --import-ownertrust" to restore them) 3 | D0F5D0EC5CF01F1EEE2389619BA9864A24FB1FA0:6: 4 | -------------------------------------------------------------------------------- /pkg/urls/main_test.go: -------------------------------------------------------------------------------- 1 | package urls 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestURLs(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "URLs") 13 | } 14 | -------------------------------------------------------------------------------- /pkg/gcp/helpers.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func FmtSaResourceId(accountId, projectId string) string { 8 | return fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", projectId, accountId, projectId) 9 | } 10 | -------------------------------------------------------------------------------- /cmd/ocm/gcp/gcp_suite_test.go: -------------------------------------------------------------------------------- 1 | package gcp_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestGcp(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Gcp Suite") 13 | } 14 | -------------------------------------------------------------------------------- /pkg/utils/main_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestEditCluster(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Utils suite") 13 | } 14 | -------------------------------------------------------------------------------- /pkg/ingress/main_test.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestEditCluster(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Describe ingress suite") 13 | } 14 | -------------------------------------------------------------------------------- /cmd/ocm/edit/ingress/main_test.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestEditCluster(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Edit ingress suite") 13 | } 14 | -------------------------------------------------------------------------------- /pkg/utils/slices.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "reflect" 4 | 5 | func Contains[T comparable](slice []T, element T) bool { 6 | for _, sliceElement := range slice { 7 | if reflect.DeepEqual(sliceElement, element) { 8 | return true 9 | } 10 | } 11 | 12 | return false 13 | } 14 | -------------------------------------------------------------------------------- /tests/mocks.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "time" 5 | 6 | . "github.com/openshift-online/ocm-sdk-go/testing" // nolint 7 | ) 8 | 9 | func InitiateAuthCodeMock(clientID string) (string, error) { 10 | accessToken := MakeTokenString("Bearer", 15*time.Minute) 11 | return accessToken, nil 12 | } 13 | -------------------------------------------------------------------------------- /cmd/ocm/create/machinepool/machinepool_suite_test.go: -------------------------------------------------------------------------------- 1 | package machinepool_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestMachinepool(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Machinepool Suite") 13 | } 14 | -------------------------------------------------------------------------------- /cmd/ocm/resume/cmd.go: -------------------------------------------------------------------------------- 1 | package resume 2 | 3 | import ( 4 | "github.com/openshift-online/ocm-cli/cmd/ocm/resume/cluster" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var Cmd = &cobra.Command{ 9 | Use: "resume [flags] RESOURCE", 10 | Short: "Resumes a hibernating resource (currently only supported for clusters)", 11 | Long: "Resumes a hibernating resource (currently only supported for clusters)", 12 | } 13 | 14 | func init() { 15 | Cmd.AddCommand(cluster.Cmd) 16 | } 17 | -------------------------------------------------------------------------------- /cmd/ocm/hibernate/cmd.go: -------------------------------------------------------------------------------- 1 | package hibernate 2 | 3 | import ( 4 | "github.com/openshift-online/ocm-cli/cmd/ocm/hibernate/cluster" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var Cmd = &cobra.Command{ 9 | Use: "hibernate [flags] RESOURCE", 10 | Short: "Hibernate a specific resource (currently only supported for clusters)", 11 | Long: "Hibernate a specific resource (currently only supported for clusters)", 12 | } 13 | 14 | func init() { 15 | Cmd.AddCommand(cluster.Cmd) 16 | } 17 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 15m 3 | issues-exit-code: 1 4 | modules-download-mode: readonly 5 | issues: 6 | max-same-issues: 0 7 | 8 | linters: 9 | disable-all: true 10 | enable: 11 | - gosec 12 | - goconst 13 | - gofmt 14 | - govet 15 | - ineffassign 16 | - lll 17 | - gosimple 18 | - staticcheck 19 | - unused 20 | - misspell 21 | - staticcheck 22 | - unconvert 23 | - unused 24 | linters-settings: 25 | goconst: 26 | min-len: 4 # ignore "gcp" and "aws" -------------------------------------------------------------------------------- /pkg/utils/strings_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Validates SliceToSortedString", func() { 9 | It("Empty when slice is empty", func() { 10 | s := SliceToSortedString([]string{}) 11 | Expect("").To(Equal(s)) 12 | }) 13 | 14 | It("Sorted when slice is filled", func() { 15 | s := SliceToSortedString([]string{"b", "a", "c", "a10", "a1", "a20", "a2", "1", "2", "10", "20"}) 16 | Expect("[1, 2, a, b, c, 10, 20, a1, a2, a10, a20]").To(Equal(s)) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /pkg/utils/strings.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | ) 7 | 8 | func SliceToSortedString(s []string) string { 9 | if len(s) == 0 { 10 | return "" 11 | } 12 | SortStringRespectLength(s) 13 | return "[" + strings.Join(s, ", ") + "]" 14 | } 15 | 16 | func SortStringRespectLength(s []string) { 17 | sort.Slice(s, func(i, j int) bool { 18 | l1, l2 := len(s[i]), len(s[j]) 19 | if l1 != l2 { 20 | return l1 < l2 21 | } 22 | return s[i] < s[j] 23 | }) 24 | } 25 | 26 | func MapKeys[K comparable, V any](m map[K]V) []K { 27 | r := make([]K, 0, len(m)) 28 | for k := range m { 29 | r = append(r, k) 30 | } 31 | return r 32 | } 33 | -------------------------------------------------------------------------------- /pkg/utils/slices_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Slices", func() { 9 | Context("Validates Contains", func() { 10 | It("Return false when input is empty", func() { 11 | Expect(false).To(Equal(Contains([]string{}, "any"))) 12 | }) 13 | 14 | It("Return true when input is populated and present", func() { 15 | Expect(true).To(Equal(Contains([]string{"test", "any"}, "any"))) 16 | }) 17 | 18 | It("Return false when input is populated and not present", func() { 19 | Expect(false).To(Equal(Contains([]string{"test", "any"}, "none"))) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /pkg/output/tables/addons.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Empty on purpose. -------------------------------------------------------------------------------- /pkg/output/tables/idps.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Empty on purpose. 18 | -------------------------------------------------------------------------------- /pkg/output/tables/plugins.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Empty on purpose. 18 | -------------------------------------------------------------------------------- /cmd/ocm/gcp/flag_descriptions.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | const ( 4 | modeFlagDescription = `How to perform the operation. Valid options are: 5 | auto (default): Resource changes will be automatically applied using the 6 | current GCP account. 7 | manual: Commands necessary to modify GCP resources will be output 8 | as a script to be run manually. 9 | ` 10 | 11 | targetDirFlagDescription = `Directory to place generated files (defaults to current directory)` 12 | versionFlagDescription = `Version of OpenShift to configure the WIF resources for` 13 | federatedProjectFlagDescription = `ID of the Google cloud project that will host the WIF pool` 14 | ) 15 | -------------------------------------------------------------------------------- /pkg/info/info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This file contains information about the tool. 18 | 19 | package info 20 | 21 | const Version = "1.0.10" 22 | -------------------------------------------------------------------------------- /pkg/utils/jwks.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | 7 | "github.com/MicahParks/jwkset" 8 | ) 9 | 10 | // Checks if two string parameters represent equal json web key sets. false is 11 | // returned if the two jwks do not have equivalent values, or if there is an 12 | // error processing the expected fields of either parameter. 13 | func JwksEqual( 14 | jwksStrA string, 15 | jwksStrB string, 16 | ) bool { 17 | var jwksA, jwksB jwkset.JWKSMarshal 18 | 19 | if err := json.Unmarshal(json.RawMessage(jwksStrA), &jwksA); err != nil { 20 | return false 21 | } 22 | if err := json.Unmarshal(json.RawMessage(jwksStrB), &jwksB); err != nil { 23 | return false 24 | } 25 | 26 | return reflect.DeepEqual(jwksA, jwksB) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/properties/properties.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package properties 18 | 19 | const ( 20 | KeyringEnvKey = "OCM_KEYRING" 21 | URLEnvKey = "OCM_URL" 22 | ) 23 | -------------------------------------------------------------------------------- /.github/workflows/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # This file lists the Python dependencies used by the GitHub actions. 18 | 19 | requests 20 | -------------------------------------------------------------------------------- /pkg/output/tables/orgs.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | columns: 18 | - name: id 19 | header: ID 20 | width: 27 21 | - name: name 22 | header: NAME 23 | width: 64 24 | -------------------------------------------------------------------------------- /pkg/output/tables/endpoints.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | columns: 18 | - name: id 19 | header: ID 20 | - name: api.url 21 | header: API ENDPOINT 22 | - name: api.listening 23 | header: PRIVATE -------------------------------------------------------------------------------- /hack/build_release_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #This script invoked via a make target by the Dockerfile 3 | #which builds a cli wrapper container that contains all release images 4 | 5 | #Keeping it similar to ROSA official releases which only publish amd64 to mirror 6 | #This list can be modified as needed if additional os or arch support is needed 7 | archs=(amd64) 8 | oses=(darwin linux windows) 9 | 10 | mkdir -p releases 11 | 12 | build_release() { 13 | for os in ${oses[@]} 14 | do 15 | for arch in ${archs[@]} 16 | do 17 | if [[ $os == "windows" ]]; then 18 | extension=".exe" 19 | fi 20 | GOOS=${os} GOARCH=${arch} go build -o /tmp/ocm_${os}_${arch} ./cmd/ocm 21 | mv /tmp/ocm_${os}_${arch} ocm${extension} 22 | zip releases/ocm_${os}_${arch}.zip ocm${extension} 23 | rm ocm${extension} 24 | done 25 | done 26 | cd releases 27 | } 28 | 29 | build_release 30 | -------------------------------------------------------------------------------- /pkg/config/main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo/v2" // nolint 23 | . "github.com/onsi/gomega" // nolint 24 | ) 25 | 26 | func TestConfig(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Config") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/output/main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package output 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo/v2" // nolint 23 | . "github.com/onsi/gomega" // nolint 24 | ) 25 | 26 | func TestOutput(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Output") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/output/tables/ingresses.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | columns: 18 | - name: id 19 | header: ID 20 | - name: application_router 21 | header: APPLICATION ROUTER 22 | - name: listening 23 | header: PRIVATE 24 | - name: default 25 | header: DEFAULT 26 | - name: route_selectors 27 | header: ROUTE SELECTORS -------------------------------------------------------------------------------- /pkg/ocm/cluster.go: -------------------------------------------------------------------------------- 1 | // This file defines a wrapper to the sdk's cluster object for easy mocking 2 | // 3 | //go:generate mockgen -source=cluster.go -package=ocm -destination=mock_cluster.go 4 | package ocm 5 | 6 | import ( 7 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 8 | ) 9 | 10 | type Cluster interface { 11 | Id() string 12 | State() cmv1.ClusterState 13 | CloudProviderId() string 14 | CcsEnabled() bool 15 | } 16 | 17 | type cluster struct { 18 | data *cmv1.Cluster 19 | } 20 | 21 | func NewCluster(data *cmv1.Cluster) Cluster { 22 | return &cluster{ 23 | data: data, 24 | } 25 | } 26 | 27 | func (c *cluster) Id() string { return c.data.ID() } 28 | func (c *cluster) State() cmv1.ClusterState { return c.data.State() } 29 | func (c *cluster) CloudProviderId() string { return c.data.CloudProvider().ID() } 30 | func (c *cluster) CcsEnabled() bool { return c.data.CCS().Enabled() } 31 | -------------------------------------------------------------------------------- /cmd/ocm/pop/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package pop 15 | 16 | import ( 17 | "github.com/spf13/cobra" 18 | 19 | "github.com/openshift-online/ocm-cli/cmd/ocm/pop/job" 20 | ) 21 | 22 | var Cmd = &cobra.Command{ 23 | Use: "pop [flags] RESOURCE", 24 | Short: "Fetch resource", 25 | Long: "Fetch resource", 26 | } 27 | 28 | func init() { 29 | Cmd.AddCommand(job.Cmd) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/ocm/push/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package push 15 | 16 | import ( 17 | "github.com/spf13/cobra" 18 | 19 | "github.com/openshift-online/ocm-cli/cmd/ocm/push/job" 20 | ) 21 | 22 | var Cmd = &cobra.Command{ 23 | Use: "push [flags] RESOURCE", 24 | Short: "Push resource", 25 | Long: "Push resource", 26 | } 27 | 28 | func init() { 29 | Cmd.AddCommand(job.Cmd) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/ocm/fail/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package fail 15 | 16 | import ( 17 | "github.com/spf13/cobra" 18 | 19 | "github.com/openshift-online/ocm-cli/cmd/ocm/fail/job" 20 | ) 21 | 22 | var Cmd = &cobra.Command{ 23 | Use: "fail [flags] RESOURCE", 24 | Short: "Failure resource", 25 | Long: "Mark resource as failure", 26 | } 27 | 28 | func init() { 29 | Cmd.AddCommand(job.Cmd) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/ocm/success/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package success 15 | 16 | import ( 17 | "github.com/spf13/cobra" 18 | 19 | "github.com/openshift-online/ocm-cli/cmd/ocm/success/job" 20 | ) 21 | 22 | var Cmd = &cobra.Command{ 23 | Use: "success [flags] RESOURCE", 24 | Short: "Success resource", 25 | Long: "Mark resource as a success", 26 | } 27 | 28 | func init() { 29 | Cmd.AddCommand(job.Cmd) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/output/terminal.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package output 18 | 19 | import ( 20 | "io" 21 | "os" 22 | 23 | "golang.org/x/term" 24 | ) 25 | 26 | // IsTerminal determines if the given writer is a terminal 27 | func IsTerminal(writer io.Writer) bool { 28 | file, ok := writer.(*os.File) 29 | if !ok { 30 | return false 31 | } 32 | fd := int(file.Fd()) 33 | return term.IsTerminal(fd) 34 | } 35 | -------------------------------------------------------------------------------- /hack/README.md: -------------------------------------------------------------------------------- 1 | # OCM Helper Scripts 2 | 3 | These scripts help in the process of releasing new versions of `ocm`. 4 | 5 | Once all changes for a specific release are in `main`, the next step is to 6 | create a release commit: 7 | 8 | ./hack/commit-release.sh 9 | 10 | This creates a new branch, updates the OCM build version and changelog file 11 | then commits and pushes to GitHub. Any potentially destructing action has a 12 | confirmation prompt. 13 | 14 | Once this new branch is pushed, someone has to merge it. Once merged, make sure 15 | to update your local copy. Then you can tag the actual release: 16 | 17 | ./hack/tag-release.sh 18 | 19 | This will create a new annotated tag and push it to the upstream OCM 20 | repository. 21 | 22 | Now that the tag is in place, you will go to the 23 | [tags page](https://github.com/openshift/ocm/tags) and edit the latest one. In 24 | there make sure that the release title and description match the release tag 25 | annotation. 26 | 27 | Publish the release and you're done. 28 | -------------------------------------------------------------------------------- /cmd/ocm/plugin/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package plugin 18 | 19 | import ( 20 | plugin "github.com/openshift-online/ocm-cli/cmd/ocm/plugin/list" 21 | 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | var Cmd = &cobra.Command{ 26 | Use: "plugin COMMAND", 27 | Aliases: []string{"plugins"}, 28 | Short: "Get information about plugins", 29 | Long: "Get information about installed ocm plugins", 30 | Args: cobra.MinimumNArgs(1), 31 | } 32 | 33 | func init() { 34 | Cmd.AddCommand(plugin.Cmd) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/ocm/describe/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package describe 15 | 16 | import ( 17 | "github.com/openshift-online/ocm-cli/cmd/ocm/describe/cluster" 18 | "github.com/openshift-online/ocm-cli/cmd/ocm/describe/ingress" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var Cmd = &cobra.Command{ 23 | Use: "describe [flags] RESOURCE", 24 | Short: "Show details of a specific resource", 25 | Long: "Show details of a specific resource", 26 | } 27 | 28 | func init() { 29 | Cmd.AddCommand(cluster.Cmd) 30 | Cmd.AddCommand(ingress.Cmd) 31 | } 32 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi9/go-toolset:latest AS builder 2 | COPY . . 3 | 4 | # For hermetic builds, The hermeto file must be sourced. 5 | # https://hermetoproject.github.io/hermeto/usage/#write-the-dockerfile 6 | ENV GOFLAGS=-buildvcs=false 7 | RUN git config --global --add safe.directory /opt/app-root/src && \ 8 | ( [ -f /tmp/hermeto.env ] && source /tmp/hermeto.env && make build_release_images ) || \ 9 | ( echo "No hermetic configuration found, building non-hermatically" && make build_release_images ) 10 | 11 | FROM registry.access.redhat.com/ubi9/ubi-micro:latest 12 | LABEL description="A CLI tool for working with OCM API" 13 | LABEL io.k8s.description="A CLI tool for working with OCM API" 14 | LABEL io.k8s.display-name="OCM CLI" 15 | LABEL io.openshift.tags="ocm" 16 | LABEL summary="Provides the ocm CLI binary" 17 | LABEL com.redhat.component="ocm" 18 | LABEL name="ocm" 19 | 20 | 21 | COPY LICENSE.txt /licenses 22 | COPY --from=builder /opt/app-root/src/releases /releases 23 | COPY --from=builder /opt/app-root/src/releases /usr/local/bin 24 | COPY --from=builder /usr/bin/make /usr/local/bin 25 | -------------------------------------------------------------------------------- /cmd/ocm/version/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package version 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/info" 26 | ) 27 | 28 | var Cmd = &cobra.Command{ 29 | Use: "version", 30 | Short: "Prints the version", 31 | Long: "Prints the version number of the client.", 32 | Args: cobra.NoArgs, 33 | RunE: run, 34 | } 35 | 36 | func run(cmd *cobra.Command, argv []string) error { 37 | // Print the version: 38 | fmt.Fprintf(os.Stdout, "%s\n", info.Version) 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /cmd/ocm/cluster/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cluster 18 | 19 | import ( 20 | "github.com/openshift-online/ocm-cli/cmd/ocm/cluster/login" 21 | "github.com/openshift-online/ocm-cli/cmd/ocm/cluster/status" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | var Cmd = &cobra.Command{ 26 | Use: "cluster COMMAND", 27 | Short: "Get information about clusters", 28 | Long: "Get status or information about a single cluster, or a list of clusters", 29 | Args: cobra.MinimumNArgs(1), 30 | } 31 | 32 | func init() { 33 | Cmd.AddCommand(login.Cmd) 34 | Cmd.AddCommand(status.Cmd) 35 | } 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Releasing a new OCM CLI Version 2 | 3 | Releasing a new version requires submitting an MR for review/merge with an 4 | update to the `Version` constant in [pkg/info/info.go](pkg/info/info.go). 5 | Additionally, update the [CHANGES.md](CHANGES.md) file to include the new 6 | version and describe all changes included. 7 | 8 | Below is an example CHANGES.md update: 9 | 10 | ``` 11 | ## 0.1.36 Feb 14 2020 12 | 13 | - Add `state` to list of default columns for cluster list. 14 | - Preserve order of attributes in JSON output. 15 | ``` 16 | 17 | Submit an MR for review/merge with the CHANGES.md and Makefile update. 18 | 19 | Finally, create and submit a new tag with the new version following the below 20 | example: 21 | 22 | ``` 23 | git checkout main 24 | git pull 25 | git tag -a -m 'Release 0.1.38' v0.1.38 26 | git push origin v0.1.38 27 | ``` 28 | 29 | Note that a repository administrator may need to push the tag to the repository 30 | due to access restrictions. 31 | 32 | After submitting a tag, a release will be automatically published to the 33 | releases page by the [release action](.github/workflows/publish-release.yaml), 34 | including the binaries for the supported platforms. 35 | -------------------------------------------------------------------------------- /pkg/cluster/describe_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 5 | "testing" 6 | ) 7 | 8 | // newTestCluster assembles a *cmv1.Cluster while handling the error to help out with inline test-case generation 9 | func newTestCluster(t *testing.T, cb *cmv1.ClusterBuilder) *cmv1.Cluster { 10 | cluster, err := cb.Build() 11 | if err != nil { 12 | t.Fatalf("failed to build cluster: %s", err) 13 | } 14 | 15 | return cluster 16 | } 17 | 18 | func TestFindHyperShiftMgmtSvcClusters(t *testing.T) { 19 | tests := []struct { 20 | name string 21 | cluster *cmv1.Cluster 22 | expectedMgmt string 23 | expectedSvc string 24 | }{ 25 | { 26 | name: "Not HyperShift", 27 | cluster: newTestCluster(t, cmv1.NewCluster().Hypershift(cmv1.NewHypershift().Enabled(false))), 28 | }, 29 | } 30 | 31 | for _, test := range tests { 32 | mgmt, svc := findHyperShiftMgmtSvcClusters(nil, test.cluster) 33 | if test.expectedMgmt != mgmt { 34 | t.Errorf("expected %s, got %s", test.expectedMgmt, mgmt) 35 | } 36 | if test.expectedSvc != svc { 37 | t.Errorf("expected %s, got %s", test.expectedSvc, svc) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/ocm/edit/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package edit 15 | 16 | import ( 17 | "github.com/openshift-online/ocm-cli/cmd/ocm/edit/cluster" 18 | "github.com/openshift-online/ocm-cli/cmd/ocm/edit/ingress" 19 | "github.com/openshift-online/ocm-cli/cmd/ocm/edit/machinepool" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | var Cmd = &cobra.Command{ 24 | Use: "edit RESOURCE [flags]", 25 | Aliases: []string{"add"}, 26 | Short: "Edit a resource from stdin", 27 | Long: "Edit a resource from stdin", 28 | } 29 | 30 | func init() { 31 | Cmd.AddCommand(ingress.Cmd) 32 | Cmd.AddCommand(cluster.Cmd) 33 | Cmd.AddCommand(machinepool.Cmd) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/ocm/connection.go: -------------------------------------------------------------------------------- 1 | package ocm 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/openshift-online/ocm-cli/pkg/debug" 8 | "github.com/openshift-online/ocm-cli/pkg/info" 9 | conn "github.com/openshift-online/ocm-cli/pkg/ocm/connection-builder" 10 | "github.com/openshift-online/ocm-cli/pkg/properties" 11 | ) 12 | 13 | func NewConnection() *conn.ConnectionBuilder { 14 | connection := conn.NewConnection() 15 | connection = connection.AsAgent("OCM-CLI/" + info.Version) 16 | 17 | // overwrite the config URL if the environment variable is set 18 | if overrideUrl := os.Getenv(properties.URLEnvKey); overrideUrl != "" { 19 | if debug.Enabled() { 20 | fmt.Fprintf(os.Stderr, "INFO: %s is overridden via environment variable. This functionality is considered tech preview and may cause unexpected issues.\n", properties.URLEnvKey) //nolint:lll 21 | fmt.Fprintf(os.Stderr, " If you experience issues while %s is set, unset the %s environment variable and attempt to log in directly to the desired OCM environment.\n\n", properties.URLEnvKey, properties.URLEnvKey) //nolint:lll 22 | } 23 | connection = connection.WithApiUrl(overrideUrl) 24 | } 25 | 26 | return connection 27 | } 28 | -------------------------------------------------------------------------------- /pkg/debug/flag.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This file contains functions used to implement the '--debug' command line option. 18 | 19 | package debug 20 | 21 | import ( 22 | "github.com/spf13/pflag" 23 | ) 24 | 25 | // AddFlag adds the debug flag to the given set of command line flags. 26 | func AddFlag(flags *pflag.FlagSet) { 27 | flags.BoolVar( 28 | &enabled, 29 | "debug", 30 | false, 31 | "Enable debug mode.", 32 | ) 33 | } 34 | 35 | // Enabled retursn a boolean flag that indicates if the debug mode is enabled. 36 | func Enabled() bool { 37 | return enabled 38 | } 39 | 40 | // enabled is a boolean flag that indicates that the debug mode is enabled. 41 | var enabled bool 42 | -------------------------------------------------------------------------------- /tests/version_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package tests 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" // nolint 23 | . "github.com/onsi/gomega" // nolint 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/info" 26 | ) 27 | 28 | var _ = Describe("Version", func() { 29 | It("Prints the version", func() { 30 | // Create a context: 31 | ctx := context.Background() 32 | 33 | // Run the command: 34 | result := NewCommand().Args("version").Run(ctx) 35 | 36 | // Check the result: 37 | Expect(result.OutString()).To(Equal(info.Version + "\n")) 38 | Expect(result.ErrString()).To(BeEmpty()) 39 | Expect(result.ExitCode()).To(BeZero()) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /pkg/output/tables/clusters.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | columns: 18 | - name: id 19 | header: ID 20 | width: 32 21 | - name: name 22 | header: NAME 23 | width: 54 24 | - name: api.url 25 | header: API URL 26 | width: 58 27 | - name: openshift_version 28 | header: OPENSHIFT_VERSION 29 | width: 18 30 | - name: product.id 31 | header: PRODUCT ID 32 | width: 10 33 | - name: hypershift.enabled 34 | header: HCP 35 | width: 7 36 | - name: cloud_provider.id 37 | header: CLOUD_PROVIDER 38 | width: 14 39 | - name: region.id 40 | header: REGION ID 41 | width: 14 42 | - name: state 43 | header: STATE 44 | width: 13 45 | - name: external_id 46 | header: EXTERNAL ID 47 | width: 36 48 | -------------------------------------------------------------------------------- /pkg/billing/billing.go: -------------------------------------------------------------------------------- 1 | package billing 2 | 3 | import ( 4 | sdk "github.com/openshift-online/ocm-sdk-go" 5 | amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" 6 | ) 7 | 8 | const ( 9 | StandardSubscriptionType = "standard" 10 | MarketplaceGcpSubscriptionType = "marketplace-gcp" 11 | ) 12 | 13 | var ValidSubscriptionTypes = []string{ 14 | StandardSubscriptionType, 15 | MarketplaceGcpSubscriptionType, 16 | } 17 | 18 | func GetBillingModel(connection *sdk.Connection, billingModelID string) (*amv1.BillingModelItem, error) { 19 | bilingModel, err := connection.AccountsMgmt().V1().BillingModels().BillingModel(billingModelID).Get().Send() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return bilingModel.Body(), nil 24 | } 25 | 26 | func GetBillingModels(connection *sdk.Connection) ([]*amv1.BillingModelItem, error) { 27 | response, err := connection.AccountsMgmt().V1().BillingModels().List().Send() 28 | if err != nil { 29 | return nil, err 30 | } 31 | billingModels := response.Items().Slice() 32 | var validBillingModel []*amv1.BillingModelItem 33 | for _, billingModel := range billingModels { 34 | for _, validSubscriptionTypeId := range ValidSubscriptionTypes { 35 | if billingModel.ID() == validSubscriptionTypeId { 36 | validBillingModel = append(validBillingModel, billingModel) 37 | } 38 | } 39 | } 40 | return validBillingModel, nil 41 | } 42 | -------------------------------------------------------------------------------- /cmd/ocm/create/machinepool/helpers.go: -------------------------------------------------------------------------------- 1 | // Defines helper objects used by the machine pool creation command and unit tests. 2 | // 3 | //go:generate mockgen -source=helpers.go -package=machinepool -destination=mock_helpers.go 4 | package machinepool 5 | 6 | import ( 7 | "github.com/openshift-online/ocm-cli/pkg/arguments" 8 | "github.com/openshift-online/ocm-cli/pkg/ocm" 9 | "github.com/openshift-online/ocm-cli/pkg/provider" 10 | sdk "github.com/openshift-online/ocm-sdk-go" 11 | "github.com/spf13/pflag" 12 | ) 13 | 14 | type FlagSet interface { 15 | Changed(flagId string) bool 16 | CheckOneOf(flagName string, options []arguments.Option) error 17 | } 18 | 19 | type flagSet struct { 20 | data *pflag.FlagSet 21 | } 22 | 23 | func (f *flagSet) Changed(flagId string) bool { return f.data.Changed(flagId) } 24 | func (f *flagSet) CheckOneOf(flagId string, options []arguments.Option) error { 25 | return arguments.CheckOneOf(f.data, flagId, options) 26 | } 27 | 28 | type MachineTypeListGetter interface { 29 | GetMachineTypeOptions(ocm.Cluster) ([]arguments.Option, error) 30 | } 31 | 32 | type machineTypeListGetter struct { 33 | connection *sdk.Connection 34 | } 35 | 36 | func (m *machineTypeListGetter) GetMachineTypeOptions(cluster ocm.Cluster) ([]arguments.Option, error) { 37 | return provider.GetMachineTypeOptions( 38 | m.connection.ClustersMgmt().V1(), 39 | cluster.CloudProviderId(), 40 | cluster.CcsEnabled(), 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/ocm/account/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package account 18 | 19 | import ( 20 | "github.com/spf13/cobra" 21 | 22 | "github.com/openshift-online/ocm-cli/cmd/ocm/account/orgs" 23 | "github.com/openshift-online/ocm-cli/cmd/ocm/account/quota" 24 | "github.com/openshift-online/ocm-cli/cmd/ocm/account/roles" 25 | "github.com/openshift-online/ocm-cli/cmd/ocm/account/status" 26 | "github.com/openshift-online/ocm-cli/cmd/ocm/account/users" 27 | ) 28 | 29 | // Cmd ... 30 | var Cmd = &cobra.Command{ 31 | Use: "account COMMAND", 32 | Short: "Get information about users.", 33 | Long: "Get status or information about a single or list of users.", 34 | Args: cobra.MinimumNArgs(1), 35 | } 36 | 37 | func init() { 38 | Cmd.AddCommand(quota.Cmd) 39 | Cmd.AddCommand(orgs.Cmd) 40 | Cmd.AddCommand(status.Cmd) 41 | Cmd.AddCommand(roles.Cmd) 42 | Cmd.AddCommand(users.Cmd) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/gcp/error_handlers.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/googleapis/gax-go/v2/apierror" 8 | googleapi "google.golang.org/api/googleapi" 9 | "google.golang.org/grpc/codes" 10 | ) 11 | 12 | func (c *gcpClient) handleApiNotFoundError(err error) error { 13 | pApiError, ok := err.(*apierror.APIError) 14 | if !ok { 15 | return fmt.Errorf("Unexpected error") 16 | } 17 | if pApiError.GRPCStatus().Code() == codes.NotFound { 18 | return fmt.Errorf("Resource not found") 19 | } 20 | return errors.New(pApiError.Details().String()) 21 | } 22 | 23 | func (c *gcpClient) handleApiError(err error) error { 24 | pApiError, ok := err.(*apierror.APIError) 25 | if !ok { 26 | return fmt.Errorf("Unexpected error") 27 | } 28 | return errors.New(pApiError.Details().String()) 29 | } 30 | 31 | func (c *gcpClient) handleDeleteServiceAccountError(err error, allowMissing bool) error { 32 | pApiError, ok := err.(*apierror.APIError) 33 | if !ok { 34 | return fmt.Errorf("Unexpected error") 35 | } 36 | if pApiError.GRPCStatus().Code() == codes.NotFound && allowMissing { 37 | return nil 38 | } 39 | return errors.New(pApiError.Details().String()) 40 | } 41 | 42 | // Extracts the text from google api errors for simpler processing 43 | func (c *gcpClient) fmtGoogleApiError(err error) error { 44 | gError, ok := err.(*googleapi.Error) 45 | if !ok { 46 | return fmt.Errorf("Unexpected error") 47 | } 48 | return errors.New(gError.Error()) 49 | } 50 | -------------------------------------------------------------------------------- /cmd/ocm/create/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package create 15 | 16 | import ( 17 | "github.com/openshift-online/ocm-cli/cmd/ocm/create/cluster" 18 | "github.com/openshift-online/ocm-cli/cmd/ocm/create/idp" 19 | "github.com/openshift-online/ocm-cli/cmd/ocm/create/ingress" 20 | "github.com/openshift-online/ocm-cli/cmd/ocm/create/machinepool" 21 | "github.com/openshift-online/ocm-cli/cmd/ocm/create/upgradepolicy" 22 | "github.com/openshift-online/ocm-cli/cmd/ocm/create/user" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var Cmd = &cobra.Command{ 27 | Use: "create [flags] RESOURCE", 28 | Aliases: []string{"add"}, 29 | Short: "Create a resource from stdin", 30 | Long: "Create a resource from stdin", 31 | } 32 | 33 | func init() { 34 | Cmd.AddCommand(cluster.Cmd) 35 | Cmd.AddCommand(idp.Cmd) 36 | Cmd.AddCommand(ingress.Cmd) 37 | Cmd.AddCommand(machinepool.Cmd) 38 | Cmd.AddCommand(upgradepolicy.Cmd) 39 | Cmd.AddCommand(user.Cmd) 40 | } 41 | -------------------------------------------------------------------------------- /hack/tag-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2022 Red Hat, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This tags the latest release 19 | 20 | current=$(git tag --list --sort version:refname | tail -n1) 21 | next="v$(cat pkg/info/info.go | grep -o '[0-9.]*' | tail -n1)" 22 | echo "Tagging release $next" 23 | 24 | # Create git release tag 25 | log=$(git log $current..HEAD --oneline --no-merges --no-decorate --reverse | grep -v $next | sed "s/^\w*/-/") 26 | read -r -p "Create release tag '$next'? [y/N] " response 27 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]] 28 | then 29 | git tag --annotate --message "Release $next" --message "$log" $next 30 | else 31 | echo -e "\tgit tag --annotate $next" 32 | fi 33 | 34 | # Push git release tag to upstream GitHub repository 35 | upstream=$(git remote --verbose | grep "github\.com.openshift-online\/ocm-cli\.git" | tail -n1 | awk '{print $1}') 36 | read -r -p "Push tag '$next' to GitHub? [y/N] " response 37 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]] 38 | then 39 | git push $upstream $next 40 | else 41 | echo -e "\tgit push $upstream $next" 42 | fi 43 | -------------------------------------------------------------------------------- /pkg/ingress/describe_test.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 7 | v1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 8 | ) 9 | 10 | var _ = Describe("Get min width for output", func() { 11 | It("retrieves the min width", func() { 12 | minWidth := getMinWidth([]string{"a", "ab", "abc", "def"}) 13 | Expect(minWidth).To(Equal(3)) 14 | }) 15 | When("empty slice", func() { 16 | It("retrieves the min width as 0", func() { 17 | minWidth := getMinWidth([]string{}) 18 | Expect(minWidth).To(Equal(0)) 19 | }) 20 | }) 21 | }) 22 | 23 | var _ = Describe("Retrieve map of entries for output", func() { 24 | It("retrieves map", func() { 25 | cluster, err := cmv1.NewCluster().ID("123").Build() 26 | Expect(err).To(BeNil()) 27 | ingress, err := cmv1.NewIngress(). 28 | ID("123"). 29 | Default(true). 30 | Listening(cmv1.ListeningMethodExternal). 31 | LoadBalancerType(cmv1.LoadBalancerFlavorNlb). 32 | RouteWildcardPolicy(cmv1.WildcardPolicyWildcardsAllowed). 33 | RouteNamespaceOwnershipPolicy(cmv1.NamespaceOwnershipPolicyStrict). 34 | RouteSelectors(map[string]string{ 35 | "test-route": "test-selector", 36 | }). 37 | ExcludedNamespaces("test", "test2"). 38 | ComponentRoutes(map[string]*cmv1.ComponentRouteBuilder{ 39 | string(cmv1.ComponentRouteTypeOauth): v1.NewComponentRoute(). 40 | Hostname("oauth-hostname").TlsSecretRef("oauth-secret"), 41 | }). 42 | Build() 43 | Expect(err).To(BeNil()) 44 | mapOutput := generateEntriesOutput(cluster, ingress) 45 | Expect(mapOutput).To(HaveLen(10)) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /cmd/ocm/success/job/job.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package job 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/openshift-online/ocm-cli/pkg/ocm" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | // Cmd Constant: 27 | var Cmd = &cobra.Command{ 28 | Use: "job QUEUE_NAME JOB_ID RECEIPT_ID", 29 | Short: "Mark the Job as a success", 30 | Long: "Mark the Job as a success on the specified Job Queue.", 31 | Args: cobra.ExactArgs(3), 32 | RunE: run, 33 | } 34 | 35 | func run(_ *cobra.Command, argv []string) error { 36 | var err error 37 | 38 | // Create the client for the OCM API: 39 | connection, err := ocm.NewConnection().Build() 40 | if err != nil { 41 | return fmt.Errorf("failed to create OCM connection: %v", err) 42 | } 43 | defer connection.Close() 44 | 45 | // Get the client for the Job Queue management api 46 | client := connection.JobQueue().V1() 47 | 48 | // Send a request to Success a Job: 49 | _, err = client.Queues().Queue(argv[0]).Jobs().Job(argv[1]).Success().ReceiptId(argv[2]).Send() 50 | if err != nil { 51 | return fmt.Errorf("unable to success a job: %v", err) 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /cmd/ocm/fail/job/job.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package job 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/openshift-online/ocm-cli/pkg/ocm" 25 | ) 26 | 27 | // Cmd Constant: 28 | var Cmd = &cobra.Command{ 29 | Use: "job QUEUE_NAME JOB_ID RECEIPT_ID FAILURE_REASON", 30 | Short: "Mark the Job as a failure", 31 | Long: "Mark the Job as a failure with specific reason on the specified Job Queue.", 32 | Args: cobra.ExactArgs(4), 33 | RunE: run, 34 | } 35 | 36 | func run(_ *cobra.Command, argv []string) error { 37 | var err error 38 | 39 | // Create the client for the OCM API: 40 | connection, err := ocm.NewConnection().Build() 41 | if err != nil { 42 | return fmt.Errorf("failed to create OCM connection: %v", err) 43 | } 44 | defer connection.Close() 45 | 46 | // Get the client for the Job Queue management api 47 | client := connection.JobQueue().V1() 48 | 49 | // Send a request to Success a Job: 50 | _, err = client.Queues().Queue(argv[0]).Jobs().Job(argv[1]).Failure().ReceiptId(argv[2]).FailureReason(argv[3]).Send() 51 | if err != nil { 52 | return fmt.Errorf("unable to fail a Job: %v", err) 53 | } 54 | 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/ocm/list/rhRegion/cmd.go: -------------------------------------------------------------------------------- 1 | package rhRegion 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "text/tabwriter" 8 | 9 | sdk "github.com/openshift-online/ocm-sdk-go" 10 | 11 | "github.com/openshift-online/ocm-cli/pkg/config" 12 | "github.com/openshift-online/ocm-cli/pkg/urls" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var args struct { 17 | discoveryURL string 18 | } 19 | 20 | var Cmd = &cobra.Command{ 21 | Use: "rh-regions", 22 | Short: "List available OCM regions", 23 | Long: "List available OCM regions", 24 | Example: ` # List all supported OCM regions 25 | ocm list rh-regions`, 26 | RunE: run, 27 | Hidden: true, 28 | } 29 | 30 | func init() { 31 | flags := Cmd.Flags() 32 | flags.StringVar( 33 | &args.discoveryURL, 34 | "discovery-url", 35 | "", 36 | "URL of the OCM API gateway. If not provided, will reuse the URL from the configuration "+ 37 | "file or "+sdk.DefaultURL+" as a last resort. The value should be a complete URL "+ 38 | "or a valid URL alias: "+strings.Join(urls.ValidOCMUrlAliases(), ", "), 39 | ) 40 | } 41 | 42 | func run(cmd *cobra.Command, argv []string) error { 43 | 44 | cfg, _ := config.Load() 45 | 46 | gatewayURL, err := urls.ResolveGatewayURL(args.discoveryURL, cfg) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | fmt.Fprintf(os.Stdout, "Discovery URL: %s\n\n", gatewayURL) 52 | regions, err := sdk.GetRhRegions(gatewayURL) 53 | if err != nil { 54 | return fmt.Errorf("Failed to get OCM regions: %w", err) 55 | } 56 | 57 | writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.TabIndent) 58 | fmt.Fprintf(writer, "RH Region\t\tGateway URL\n") 59 | for regionName, region := range regions { 60 | fmt.Fprintf(writer, "%s\t\t%v\n", regionName, region.URL) 61 | } 62 | 63 | err = writer.Flush() 64 | return err 65 | 66 | } 67 | -------------------------------------------------------------------------------- /cmd/ocm/gcp/describe-wif-config.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "text/tabwriter" 7 | 8 | "github.com/openshift-online/ocm-cli/pkg/ocm" 9 | "github.com/pkg/errors" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // NewDescribeWorkloadIdentityConfiguration provides the "gcp describe wif-config" subcommand 14 | func NewDescribeWorkloadIdentityConfiguration() *cobra.Command { 15 | describeWorkloadIdentityPoolCmd := &cobra.Command{ 16 | Use: "wif-config [ID|Name]", 17 | Short: "Show details of a wif-config.", 18 | RunE: describeWorkloadIdentityConfigurationCmd, 19 | } 20 | 21 | return describeWorkloadIdentityPoolCmd 22 | } 23 | 24 | func describeWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { 25 | key, err := wifKeyFromArgs(argv) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | // Create the client for the OCM API: 31 | connection, err := ocm.NewConnection().Build() 32 | if err != nil { 33 | return errors.Wrapf(err, "Failed to create OCM connection") 34 | } 35 | defer connection.Close() 36 | 37 | // Verify the WIF configuration exists 38 | wifConfig, err := findWifConfig(connection.ClustersMgmt().V1(), key) 39 | if err != nil { 40 | return errors.Wrapf(err, "failed to get wif-config") 41 | } 42 | 43 | // Print output 44 | w := tabwriter.NewWriter(os.Stdout, 8, 0, 2, ' ', 0) 45 | 46 | fmt.Fprintf(w, "ID:\t%s\n", wifConfig.ID()) 47 | fmt.Fprintf(w, "Display Name:\t%s\n", wifConfig.DisplayName()) 48 | fmt.Fprintf(w, "Project:\t%s\n", wifConfig.Gcp().ProjectId()) 49 | fmt.Fprintf(w, "Federated Project:\t%s\n", getFederatedProjectId(wifConfig)) 50 | fmt.Fprintf(w, "Issuer URL:\t%s\n", wifConfig.Gcp().WorkloadIdentityPool().IdentityProvider().IssuerUrl()) 51 | fmt.Fprintf(w, "Versions:\t%v\n", wifConfig.WifTemplates()) 52 | return w.Flush() 53 | } 54 | -------------------------------------------------------------------------------- /cmd/ocm/logout/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package logout 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/openshift-online/ocm-cli/pkg/config" 25 | "github.com/openshift-online/ocm-sdk-go/authentication/securestore" 26 | ) 27 | 28 | var Cmd = &cobra.Command{ 29 | Use: "logout", 30 | Short: "Log out", 31 | Long: "Log out, removing connection related variables from the config file.", 32 | Args: cobra.NoArgs, 33 | RunE: run, 34 | } 35 | 36 | func run(cmd *cobra.Command, argv []string) error { 37 | 38 | if keyring, ok := config.IsKeyringManaged(); ok { 39 | err := securestore.RemoveConfigFromKeyring(keyring) 40 | if err != nil { 41 | return fmt.Errorf("can't remove configuration from keyring: %w", err) 42 | } 43 | return nil 44 | } 45 | 46 | // Load the configuration file: 47 | cfg, err := config.Load() 48 | if err != nil { 49 | return fmt.Errorf("can't load configuration file: %w", err) 50 | } 51 | 52 | // Remove all the login related settings from the configuration file: 53 | cfg.Disarm() 54 | 55 | // Save the configuration file: 56 | err = config.Save(cfg) 57 | if err != nil { 58 | return fmt.Errorf("can't save configuration file: %w", err) 59 | } 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /cmd/ocm/list/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package list 15 | 16 | import ( 17 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/addon" 18 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/cluster" 19 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/idp" 20 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/ingress" 21 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/machinepool" 22 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/org" 23 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/quota" 24 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/region" 25 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/rhRegion" 26 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/upgradepolicy" 27 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/user" 28 | "github.com/openshift-online/ocm-cli/cmd/ocm/list/version" 29 | "github.com/spf13/cobra" 30 | ) 31 | 32 | var Cmd = &cobra.Command{ 33 | Use: "list RESOURCE", 34 | Short: "List all resources of a specific type", 35 | Long: "List all resources of a specific type", 36 | } 37 | 38 | func init() { 39 | Cmd.AddCommand(addon.Cmd) 40 | Cmd.AddCommand(cluster.Cmd) 41 | Cmd.AddCommand(idp.Cmd) 42 | Cmd.AddCommand(ingress.Cmd) 43 | Cmd.AddCommand(org.Cmd) 44 | Cmd.AddCommand(machinepool.Cmd) 45 | Cmd.AddCommand(quota.Cmd) 46 | Cmd.AddCommand(region.Cmd) 47 | Cmd.AddCommand(upgradepolicy.Cmd) 48 | Cmd.AddCommand(user.Cmd) 49 | Cmd.AddCommand(version.Cmd) 50 | Cmd.AddCommand(rhRegion.Cmd) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/ocm/resume/cluster/cmd.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 8 | "github.com/openshift-online/ocm-cli/pkg/ocm" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var Cmd = &cobra.Command{ 14 | Use: "cluster {NAME|ID|EXTERNAL_ID}", 15 | Short: "Resume a cluster from hibernation", 16 | Long: "Resumes cluster hibernation. The cluster will return to a `Ready` state, and all actions will be enabled.", 17 | RunE: run, 18 | } 19 | 20 | func run(cmd *cobra.Command, argv []string) error { 21 | // Check that there is exactly one cluster name, identifir or external identifier in the 22 | // command line arguments: 23 | if len(argv) != 1 { 24 | fmt.Fprintf( 25 | os.Stderr, 26 | "Expected exactly one cluster name, identifier or external identifier "+ 27 | "is required\n", 28 | ) 29 | os.Exit(1) 30 | } 31 | 32 | // Check that the cluster key (name, identifier or external identifier) given by the user 33 | // is reasonably safe so that there is no risk of SQL injection: 34 | clusterKey := argv[0] 35 | if !c.IsValidClusterKey(clusterKey) { 36 | return fmt.Errorf( 37 | "Cluster name, identifier or external identifier '%s' isn't valid: it "+ 38 | "must contain only letters, digits, dashes and underscores", 39 | clusterKey, 40 | ) 41 | } 42 | 43 | // Create the client for the OCM API: 44 | connection, err := ocm.NewConnection().Build() 45 | if err != nil { 46 | return fmt.Errorf("Failed to create OCM connection: %v", err) 47 | } 48 | defer connection.Close() 49 | 50 | // Get the client for the cluster management api 51 | clusterCollection := connection.ClustersMgmt().V1().Clusters() 52 | 53 | // Verify the cluster exists in OCM. 54 | cluster, err := c.GetCluster(connection, clusterKey) 55 | if err != nil { 56 | return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err) 57 | } 58 | _, err = clusterCollection.Cluster(cluster.ID()).Resume().Send() 59 | if err != nil { 60 | return err 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/provider/regions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package provider 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/openshift-online/ocm-cli/pkg/cluster" 23 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 24 | ) 25 | 26 | // GetRegions queries either `aws/available_regions` or `regions` depending on CCS flags. 27 | // Does not filter by .Enabled() flag; whether caller should filter depends on CCS. 28 | func GetRegions(client *cmv1.Client, provider string, ccs cluster.CCS) (regions []*cmv1.CloudRegion, err error) { 29 | if ccs.Enabled && provider == "aws" { 30 | // Build cmv1.AWS object to get list of available regions: 31 | awsCredentials, err := cmv1.NewAWS(). 32 | AccessKeyID(ccs.AWS.AccessKeyID). 33 | SecretAccessKey(ccs.AWS.SecretAccessKey). 34 | Build() 35 | if err != nil { 36 | return nil, fmt.Errorf("Failed to build AWS credentials: %v", err) 37 | } 38 | 39 | response, err := client.CloudProviders().CloudProvider(provider).AvailableRegions().Search(). 40 | Page(1). 41 | Size(-1). 42 | Body(awsCredentials). 43 | Send() 44 | if err != nil { 45 | return nil, err 46 | } 47 | regions = response.Items().Slice() 48 | } else { 49 | response, err := client.CloudProviders().CloudProvider(provider).Regions().List(). 50 | Page(1). 51 | Size(-1). 52 | Send() 53 | if err != nil { 54 | return nil, err 55 | } 56 | regions = response.Items().Slice() 57 | } 58 | return regions, nil 59 | } 60 | -------------------------------------------------------------------------------- /cmd/ocm/hibernate/cluster/cmd.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 8 | "github.com/openshift-online/ocm-cli/pkg/ocm" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var Cmd = &cobra.Command{ 14 | Use: "cluster {NAME|ID|EXTERNAL_ID}", 15 | Short: "Initiate cluster hibernation", 16 | Long: "Initiates cluster hibernation. While hibernating a cluster will not consume any cloud provider infrastructure" + 17 | "but will be counted for quota.", 18 | RunE: run, 19 | } 20 | 21 | func run(cmd *cobra.Command, argv []string) error { 22 | // Check that there is exactly one cluster name, identifir or external identifier in the 23 | // command line arguments: 24 | if len(argv) != 1 { 25 | fmt.Fprintf( 26 | os.Stderr, 27 | "Expected exactly one cluster name, identifier or external identifier "+ 28 | "is required\n", 29 | ) 30 | os.Exit(1) 31 | } 32 | 33 | // Check that the cluster key (name, identifier or external identifier) given by the user 34 | // is reasonably safe so that there is no risk of SQL injection: 35 | clusterKey := argv[0] 36 | if !c.IsValidClusterKey(clusterKey) { 37 | return fmt.Errorf( 38 | "Cluster name, identifier or external identifier '%s' isn't valid: it "+ 39 | "must contain only letters, digits, dashes and underscores", 40 | clusterKey, 41 | ) 42 | } 43 | 44 | // Create the client for the OCM API: 45 | connection, err := ocm.NewConnection().Build() 46 | if err != nil { 47 | return fmt.Errorf("Failed to create OCM connection: %v", err) 48 | } 49 | defer connection.Close() 50 | 51 | // Get the client for the cluster management api 52 | clusterCollection := connection.ClustersMgmt().V1().Clusters() 53 | 54 | // Verify the cluster exists in OCM. 55 | cluster, err := c.GetCluster(connection, clusterKey) 56 | if err != nil { 57 | return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err) 58 | } 59 | 60 | _, err = clusterCollection.Cluster(cluster.ID()).Hibernate().Send() 61 | if err != nil { 62 | return err 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/provider/machine_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package provider 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/openshift-online/ocm-cli/pkg/arguments" 23 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 24 | ) 25 | 26 | func getMachineTypes(client *cmv1.Client, provider string) (machineTypes []*cmv1.MachineType, err error) { 27 | collection := client.MachineTypes() 28 | page := 1 29 | size := 100 30 | for { 31 | var response *cmv1.MachineTypesListResponse 32 | response, err = collection.List(). 33 | Search(fmt.Sprintf("cloud_provider.id = '%s'", provider)). 34 | Order("size desc"). 35 | Page(page). 36 | Size(size). 37 | Send() 38 | if err != nil { 39 | return 40 | } 41 | machineTypes = append(machineTypes, response.Items().Slice()...) 42 | if response.Size() < size { 43 | break 44 | } 45 | page++ 46 | } 47 | 48 | if len(machineTypes) == 0 { 49 | return nil, fmt.Errorf("No machine types for provider %v", err) 50 | } 51 | return 52 | } 53 | 54 | func GetMachineTypeOptions(client *cmv1.Client, provider string, ccs bool) (options []arguments.Option, err error) { 55 | machineTypes, err := getMachineTypes(client, provider) 56 | if err != nil { 57 | err = fmt.Errorf("Failed to retrieve machine types: %s", err) 58 | return 59 | } 60 | 61 | for _, m := range machineTypes { 62 | if m.CCSOnly() && !ccs { 63 | continue 64 | } 65 | options = append(options, arguments.Option{ 66 | Value: m.ID(), 67 | Description: m.Name(), 68 | }) 69 | } 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /cmd/ocm/whoami/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package whoami 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "os" 23 | 24 | amsv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" 25 | "github.com/spf13/cobra" 26 | 27 | "github.com/openshift-online/ocm-cli/pkg/dump" 28 | "github.com/openshift-online/ocm-cli/pkg/ocm" 29 | ) 30 | 31 | var Cmd = &cobra.Command{ 32 | Use: "whoami", 33 | Short: "Prints user information", 34 | Long: "Prints user information.", 35 | Args: cobra.NoArgs, 36 | RunE: run, 37 | } 38 | 39 | func run(cmd *cobra.Command, argv []string) error { 40 | 41 | // Create the client for the OCM API: 42 | connection, err := ocm.NewConnection().Build() 43 | if err != nil { 44 | return fmt.Errorf("Failed to create OCM connection: %v", err) 45 | } 46 | defer connection.Close() 47 | 48 | // Send the request: 49 | response, err := connection.AccountsMgmt().V1().CurrentAccount().Get(). 50 | Send() 51 | if err != nil { 52 | return fmt.Errorf("Can't send request: %v", err) 53 | } 54 | 55 | // Buffer for pretty output: 56 | buf := new(bytes.Buffer) 57 | 58 | // Output account info. 59 | err = amsv1.MarshalAccount(response.Body(), buf) 60 | if err != nil { 61 | return fmt.Errorf("Failed to marshal account into JSON encoder: %v", err) 62 | } 63 | 64 | if response.Status() < 400 { 65 | err = dump.Pretty(os.Stdout, buf.Bytes()) 66 | } else { 67 | err = dump.Pretty(os.Stderr, buf.Bytes()) 68 | } 69 | if err != nil { 70 | return fmt.Errorf("Can't print body: %v", err) 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/gcp/policy.go: -------------------------------------------------------------------------------- 1 | // Defines a Policy type which wraps the iam.Policy object. This enables 2 | // callers of the gcp package to process iam policies without needing to make 3 | // additional imports. 4 | package gcp 5 | 6 | import ( 7 | "cloud.google.com/go/iam" 8 | ) 9 | 10 | // The resource name belonging to the policy. 11 | // 12 | // For service accounts, this would take the forms: 13 | // * `projects/{PROJECT_ID}/serviceAccounts/{EMAIL_ADDRESS}` 14 | // * `projects/{PROJECT_ID}/serviceAccounts/{UNIQUE_ID}` 15 | // * `projects/-/serviceAccounts/{EMAIL_ADDRESS}` 16 | // * `projects/-/serviceAccounts/{UNIQUE_ID}` 17 | // 18 | // It is recommended that wildcard `-` form is avoided due to the potential for 19 | // misleading error messages. The client helper FmtSaResourceId produces a 20 | // string that may be used as a policy member. 21 | type PolicyMember string 22 | 23 | // The name of the role belonging to the policy. 24 | // 25 | // Values of this type take two different forms, depending on whether it is 26 | // predefined. 27 | // 28 | // For predefined roles: 29 | // * `roles/{role_id}` 30 | // 31 | // For custom roles: 32 | // * `projects/{project}/roles/{role_id}` 33 | type RoleName string 34 | 35 | type Policy interface { 36 | HasRole(member PolicyMember, roleName RoleName) bool 37 | AddRole(member PolicyMember, roleName RoleName) 38 | RemoveRole(member PolicyMember, roleName RoleName) 39 | 40 | // Getters 41 | IamPolicy() *iam.Policy 42 | ResourceId() string 43 | } 44 | 45 | type policy struct { 46 | policy *iam.Policy 47 | resourceId string 48 | } 49 | 50 | func (p *policy) AddRole(member PolicyMember, roleName RoleName) { 51 | p.policy.Add(string(member), iam.RoleName(roleName)) 52 | } 53 | 54 | func (p *policy) HasRole(member PolicyMember, roleName RoleName) bool { 55 | return p.policy.HasRole(string(member), iam.RoleName(roleName)) 56 | } 57 | 58 | func (p *policy) RemoveRole(member PolicyMember, roleName RoleName) { 59 | p.policy.Remove(string(member), iam.RoleName(roleName)) 60 | } 61 | 62 | func (p *policy) IamPolicy() *iam.Policy { 63 | return p.policy 64 | } 65 | 66 | func (p *policy) ResourceId() string { 67 | return p.resourceId 68 | } 69 | -------------------------------------------------------------------------------- /cmd/ocm/gcp/verify-wif-config.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/openshift-online/ocm-cli/pkg/ocm" 8 | sdk "github.com/openshift-online/ocm-sdk-go" 9 | "github.com/pkg/errors" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // NewVerifyWorkloadIdentityConfiguration provides the "gcp verify wif-config" subcommand 14 | func NewVerifyWorkloadIdentityConfiguration() *cobra.Command { 15 | verifyWorkloadIdentityCmd := &cobra.Command{ 16 | Use: "wif-config [ID|Name]", 17 | Short: "Verify a workload identity federation configuration (wif-config) object.", 18 | RunE: verifyWorkloadIdentityConfigurationCmd, 19 | } 20 | 21 | return verifyWorkloadIdentityCmd 22 | } 23 | 24 | func verifyWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { 25 | log := log.Default() 26 | 27 | key, err := wifKeyFromArgs(argv) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | // Create the client for the OCM API: 33 | connection, err := ocm.NewConnection().Build() 34 | if err != nil { 35 | return errors.Wrapf(err, "Failed to create OCM connection") 36 | } 37 | defer connection.Close() 38 | 39 | // Verify the WIF configuration exists 40 | wif, err := findWifConfig(connection.ClustersMgmt().V1(), key) 41 | if err != nil { 42 | return errors.Wrapf(err, "failed to get wif-config") 43 | } 44 | 45 | if err := verifyWifConfig(connection, wif.ID()); err != nil { 46 | return errors.Wrapf(err, "failed to verify wif-config") 47 | } 48 | log.Println("wif-config is valid") 49 | return nil 50 | } 51 | 52 | func verifyWifConfig( 53 | connection *sdk.Connection, 54 | wifId string, 55 | ) error { 56 | // Verify the WIF configuration is valid 57 | response, err := connection. 58 | ClustersMgmt().V1(). 59 | GCP().WifConfigs().WifConfig(wifId).Status(). 60 | Get().Send() 61 | if err != nil { 62 | return fmt.Errorf("failed to call wif-config verification: %s", err.Error()) 63 | } 64 | 65 | if !response.Body().Configured() { 66 | return fmt.Errorf( 67 | "verification failed with error: %s\n"+ 68 | "Running 'ocm gcp update wif-config' will fix errors related to "+ 69 | "cloud resource misconfiguration.", 70 | response.Body().Description()) 71 | } 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /hack/build_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # build_image.sh - Build a standard container image for OCM CLI 4 | # 5 | # This script builds a container image using podman with network access enabled. 6 | # Unlike build_hermetic_image.sh, this allows network access during the build 7 | # process, enabling dependency downloads and standard build workflows. 8 | # 9 | # Usage: 10 | # ./build_image.sh 11 | # 12 | # Arguments: 13 | # IMAGE_REPOSITORY - Container registry repository (e.g., quay.io/openshift) 14 | # IMAGE_TAG - Tag for the image (e.g., v1.0.8, latest) 15 | # IMAGE_NAME - Name of the image (e.g., ocm-cli) 16 | # 17 | # Example: 18 | # ./build_image.sh quay.io/openshift v1.0.8 ocm-cli 19 | # # Results in: quay.io/openshift/ocm-cli:v1.0.8 20 | # 21 | # Prerequisites: 22 | # - podman must be installed and available in PATH 23 | # - docker/Dockerfile must exist in the project root 24 | # - Network access for downloading dependencies during build 25 | # 26 | # Build characteristics: 27 | # - Uses --no-cache to ensure fresh build 28 | # - Allows network access for dependency downloads 29 | # - Standard (non-hermetic) build process 30 | # - Suitable for development and testing builds 31 | # 32 | # Note: For reproducible/hermetic builds, use build_hermetic_image.sh instead 33 | # 34 | 35 | # Validate required arguments 36 | MISSING_PARAM=false; 37 | if [ -z "$1" ]; then 38 | echo "Error: IMAGE_REPOSITORY argument cannot be empty" 39 | MISSING_PARAM=true; 40 | fi 41 | 42 | if [ -z "$2" ]; then 43 | echo "Error: IMAGE_TAG argument cannot be empty" 44 | MISSING_PARAM=true; 45 | fi 46 | 47 | if [ -z "$3" ]; then 48 | echo "Error: IMAGE_NAME argument cannot be empty" 49 | MISSING_PARAM=true; 50 | fi 51 | 52 | # Exit if any required parameters are missing 53 | if $MISSING_PARAM; then 54 | exit 1 55 | fi 56 | 57 | # Store validated arguments in descriptive variables 58 | IMAGE_REPOSITORY=$1 59 | IMAGE_TAG=$2 60 | IMAGE_NAME=$3 61 | 62 | # Build the standard container image with network access 63 | echo "Creating image $IMAGE_REPOSITORY/$IMAGE_NAME:$IMAGE_TAG..." 64 | podman build . \ 65 | --file docker/Dockerfile \ 66 | --no-cache \ 67 | --tag $IMAGE_REPOSITORY/$IMAGE_NAME:$IMAGE_TAG 68 | -------------------------------------------------------------------------------- /pkg/provider/wif_configs.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/openshift-online/ocm-cli/pkg/arguments" 7 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 8 | ) 9 | 10 | func getWifConfigs(client *cmv1.Client, filter string) (wifConfigs []*cmv1.WifConfig, err error) { 11 | collection := client.GCP().WifConfigs() 12 | page := 1 13 | size := 100 14 | for { 15 | var response *cmv1.WifConfigsListResponse 16 | response, err = collection.List(). 17 | Page(page). 18 | Size(size). 19 | Search(filter). 20 | Send() 21 | if err != nil { 22 | return 23 | } 24 | wifConfigs = append(wifConfigs, response.Items().Slice()...) 25 | if response.Size() < size { 26 | break 27 | } 28 | page++ 29 | } 30 | 31 | if len(wifConfigs) == 0 { 32 | return nil, fmt.Errorf("no WIF configurations available") 33 | } 34 | return 35 | } 36 | 37 | func GetWifConfigs(client *cmv1.Client) (wifConfigs []*cmv1.WifConfig, err error) { 38 | return getWifConfigs(client, "") 39 | } 40 | 41 | // GetWifConfig returns the WIF configuration where the key is the wif config id or name 42 | func GetWifConfig(client *cmv1.Client, key string) (wifConfig *cmv1.WifConfig, err error) { 43 | query := fmt.Sprintf( 44 | "id = '%s' or display_name = '%s'", 45 | key, key, 46 | ) 47 | wifs, err := getWifConfigs(client, query) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | if len(wifs) == 0 { 53 | return nil, fmt.Errorf("WIF configuration with identifier or name '%s' not found", key) 54 | } 55 | if len(wifs) > 1 { 56 | return nil, fmt.Errorf("there are %d WIF configurations found with identifier or name '%s'", len(wifs), key) 57 | } 58 | return wifs[0], nil 59 | } 60 | 61 | // GetWifConfigNameOptions returns the wif config options for the cluster 62 | // with display name as the value and id as the description 63 | func GetWifConfigNameOptions(client *cmv1.Client) (options []arguments.Option, err error) { 64 | wifConfigs, err := getWifConfigs(client, "") 65 | if err != nil { 66 | err = fmt.Errorf("failed to retrieve WIF configurations: %s", err) 67 | return 68 | } 69 | 70 | for _, wc := range wifConfigs { 71 | options = append(options, arguments.Option{ 72 | Value: wc.DisplayName(), 73 | Description: wc.ID(), 74 | }) 75 | } 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /cmd/ocm/pop/job/job.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package job 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/openshift-online/ocm-cli/pkg/ocm" 25 | "github.com/openshift-online/ocm-sdk-go/jobqueue/v1" 26 | ) 27 | 28 | // Cmd Constant: 29 | var Cmd = &cobra.Command{ 30 | Use: "job QUEUE_NAME", 31 | Short: "Pop (i.e. fetch) a Job", 32 | Long: "Fetch a job from the specified Job Queue.", 33 | Args: cobra.ExactArgs(1), 34 | RunE: run, 35 | } 36 | 37 | func run(_ *cobra.Command, argv []string) error { 38 | var ( 39 | pop *v1.QueuePopResponse 40 | err error 41 | ) 42 | 43 | // Create the client for the OCM API: 44 | connection, err := ocm.NewConnection().Build() 45 | if err != nil { 46 | return fmt.Errorf("failed to create OCM connection: %v", err) 47 | } 48 | defer connection.Close() 49 | 50 | // Get the client for the Job Queue management api 51 | client := connection.JobQueue().V1() 52 | 53 | // Send a request to create the Job: 54 | pop, err = client.Queues().Queue(argv[0]).Pop().Send() 55 | if err != nil { 56 | if pop != nil && pop.Status() == 204 { 57 | // No Content 58 | fmt.Printf("No job found\n") 59 | return nil 60 | } 61 | return fmt.Errorf("unable to fetch a Job: %v", err) 62 | } 63 | fmt.Printf("{\n"+ 64 | " \"id\": \"%s\",\n"+ 65 | " \"kind\": \"Job\",\n"+ 66 | " \"href\": \"%s\",\n"+ 67 | " \"attempts\": \"%d\",\n"+ 68 | " \"abandoned_at\": \"%s\",\n"+ 69 | " \"created_at\": \"%s\",\n"+ 70 | " \"updated_at\": \"%s\",\n"+ 71 | " \"receipt_id\": \"%s\",\n"+ 72 | "}", 73 | pop.ID(), 74 | pop.HREF(), 75 | pop.Attempts(), 76 | pop.AbandonedAt(), 77 | pop.CreatedAt(), 78 | pop.UpdatedAt(), 79 | pop.ReceiptId(), 80 | ) 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /cmd/ocm/list/version/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package version 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/openshift-online/ocm-cli/pkg/cluster" 23 | "github.com/openshift-online/ocm-cli/pkg/ocm" 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | var args struct { 28 | defaultVersion bool 29 | channelGroup string 30 | marketplaceGcp string 31 | } 32 | 33 | var Cmd = &cobra.Command{ 34 | Use: "versions", 35 | Aliases: []string{"version"}, 36 | Short: "List available versions", 37 | Long: "List the versions available for provisioning a cluster", 38 | Example: ` # List all supported cluster versions 39 | ocm list versions`, 40 | Args: cobra.NoArgs, 41 | RunE: run, 42 | } 43 | 44 | func init() { 45 | fs := Cmd.Flags() 46 | fs.BoolVarP( 47 | &args.defaultVersion, 48 | "default", 49 | "d", 50 | false, 51 | "Show only the default version", 52 | ) 53 | fs.StringVar( 54 | &args.channelGroup, 55 | "channel-group", 56 | "stable", 57 | "List only versions from the specified channel group", 58 | ) 59 | 60 | fs.StringVar( 61 | &args.marketplaceGcp, 62 | "marketplace-gcp", 63 | "", 64 | "List only versions that support 'marketplace-gcp' subscription type", 65 | ) 66 | } 67 | 68 | func run(cmd *cobra.Command, argv []string) error { 69 | // Create the client for the OCM API: 70 | connection, err := ocm.NewConnection().Build() 71 | if err != nil { 72 | return fmt.Errorf("Failed to create OCM connection: %v", err) 73 | } 74 | defer connection.Close() 75 | 76 | client := connection.ClustersMgmt().V1() 77 | versions, defaultVersion, err := cluster.GetEnabledVersions(client, args.channelGroup, args.marketplaceGcp, "") 78 | if err != nil { 79 | return fmt.Errorf("Can't retrieve versions: %v", err) 80 | } 81 | 82 | if args.defaultVersion { 83 | fmt.Println(defaultVersion) 84 | } else { 85 | for _, v := range versions { 86 | fmt.Println(v) 87 | } 88 | } 89 | 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /cmd/ocm/push/job/job.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package job 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/arguments" 26 | "github.com/openshift-online/ocm-cli/pkg/ocm" 27 | "github.com/openshift-online/ocm-sdk-go/jobqueue/v1" 28 | ) 29 | 30 | var args struct { 31 | parameter []string 32 | } 33 | 34 | // Cmd Constant: 35 | var Cmd = &cobra.Command{ 36 | Use: "job QUEUE_NAME", 37 | Short: "Push (i.e. create) a new Job", 38 | Long: "Create a new Job on the specified Job Queue.", 39 | Args: cobra.ExactArgs(1), 40 | RunE: run, 41 | } 42 | 43 | func init() { 44 | // Add flags to rootCmd: 45 | flags := Cmd.Flags() 46 | arguments.AddParameterFlag(flags, &args.parameter) 47 | } 48 | 49 | func run(_ *cobra.Command, argv []string) error { 50 | var ( 51 | push *v1.QueuePushResponse 52 | err error 53 | ) 54 | 55 | // Create the client for the OCM API: 56 | connection, err := ocm.NewConnection().Build() 57 | if err != nil { 58 | return fmt.Errorf("failed to create OCM connection: %v", err) 59 | } 60 | defer connection.Close() 61 | 62 | // Get the client for the Job Queue management api 63 | client := connection.JobQueue().V1() 64 | 65 | // Send a request to create a Job: 66 | request := client.Queues().Queue(argv[0]).Push() 67 | for _, arg := range args.parameter { 68 | if strings.HasPrefix(arg, "Arguments") { 69 | params := strings.Split(arg, "=") 70 | // Apply parameters 71 | request.Arguments(params[1]) 72 | } 73 | } 74 | push, err = request.Send() 75 | if err != nil { 76 | return fmt.Errorf("unable to create Job: %v", err) 77 | } 78 | fmt.Printf("{\n"+ 79 | " \"id\": \"%s\",\n"+ 80 | " \"kind\": \"Job\",\n"+ 81 | " \"href\": \"%s\",\n"+ 82 | " \"arguments\": \"%s\",\n"+ 83 | " \"created_at\": \"%s\",\n"+ 84 | "}", 85 | push.ID(), 86 | push.HREF(), 87 | push.Arguments(), 88 | push.CreatedAt(), 89 | ) 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2018 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Ensure go modules are enabled: 18 | export GO111MODULE=on 19 | export GOPROXY=https://proxy.golang.org 20 | 21 | # Disable CGO so that we always generate static binaries: 22 | export CGO_ENABLED=0 23 | 24 | PROJECT_PATH := $(PWD) 25 | LOCAL_BIN_PATH := $(PROJECT_PATH)/bin 26 | GINKGO := $(LOCAL_BIN_PATH)/ginkgo 27 | 28 | # Allow overriding: `make lint container_runner=docker`. 29 | container_runner:=podman 30 | 31 | .PHONY: all 32 | all: cmds 33 | 34 | .PHONY: cmds 35 | cmds: 36 | for cmd in $$(ls cmd); do \ 37 | go build "./cmd/$${cmd}" || exit 1; \ 38 | done 39 | 40 | .PHONY: install 41 | install: 42 | go install ./cmd/ocm 43 | 44 | .PHONY: ginkgo-install 45 | ginkgo-install: 46 | @GOBIN=$(LOCAL_BIN_PATH) go install github.com/onsi/ginkgo/v2/ginkgo@v2.23.4 47 | 48 | .PHONY: tools 49 | tools: ginkgo-install 50 | 51 | .PHONY: test tests 52 | test tests: cmds tools 53 | $(GINKGO) run -r 54 | 55 | .PHONY: fmt 56 | fmt: 57 | gofmt -s -l -w cmd pkg tests 58 | 59 | .PHONY: lint 60 | lint: 61 | $(container_runner) run --rm --security-opt label=disable --volume="$(PWD):/app" --workdir=/app \ 62 | golangci/golangci-lint:v$(shell cat .golangciversion) \ 63 | golangci-lint run 64 | 65 | .PHONY: clean 66 | clean: 67 | rm -rf \ 68 | $$(ls cmd) \ 69 | *-darwin-amd64 \ 70 | *-linux-amd64 \ 71 | *-linux-arm64 \ 72 | *-linux-ppc64le \ 73 | *-linux-s390x \ 74 | *-windows-amd64 \ 75 | *.sha256 \ 76 | $(NULL) 77 | 78 | .PHONY: build_release_images 79 | build_release_images: 80 | bash ./hack/build_release_images.sh 81 | 82 | 83 | # NOTE: This requires the tool podman to be installed in the calling environment 84 | .PHONY: image 85 | image: 86 | bash ./hack/build_image.sh "${IMAGE_REPOSITORY}" "${IMAGE_TAG}" "${IMAGE_NAME}" 87 | 88 | # NOTE: This requires the tool hermeto v0.41.0+ to be installed in the calling environment. 89 | .PHONY: hermetic_image 90 | hermetic_image: 91 | bash ./hack/build_hermetic_image.sh "${IMAGE_REPOSITORY}" "${IMAGE_TAG}" "${IMAGE_NAME}" 92 | -------------------------------------------------------------------------------- /tests/token_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package tests 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | . "github.com/onsi/ginkgo/v2" // nolint 24 | . "github.com/onsi/gomega" // nolint 25 | 26 | . "github.com/openshift-online/ocm-sdk-go/testing" // nolint 27 | ) 28 | 29 | var _ = Describe("Token", func() { 30 | var ctx context.Context 31 | var cmd *CommandRunner 32 | 33 | BeforeEach(func() { 34 | ctx = context.Background() 35 | }) 36 | 37 | When("Logged in", func() { 38 | var accessToken string 39 | var refreshToken string 40 | 41 | BeforeEach(func() { 42 | // Create the tokens: 43 | accessToken = MakeTokenString("Bearer", 10*time.Minute) 44 | refreshToken = MakeTokenString("Refresh", 10*time.Hour) 45 | 46 | // Create the command: 47 | cmd = NewCommand(). 48 | ConfigString( 49 | `{ 50 | "refresh_token": "{{ .refreshToken }}", 51 | "access_token": "{{ .accessToken }}", 52 | "url": "http://my-server.example.com", 53 | "token_url": "http://my-sso.example.com" 54 | }`, 55 | "accessToken", accessToken, 56 | "refreshToken", refreshToken, 57 | ). 58 | Arg("token") 59 | }) 60 | 61 | It("Displays current access token", func() { 62 | result := cmd.Run(ctx) 63 | Expect(result.OutString()).To(Equal(accessToken + "\n")) 64 | Expect(result.ErrString()).To(BeEmpty()) 65 | Expect(result.ExitCode()).To(BeZero()) 66 | }) 67 | 68 | It("Displays current refresh token", func() { 69 | result := cmd.Arg("--refresh").Run(ctx) 70 | Expect(result.OutString()).To(Equal(refreshToken + "\n")) 71 | Expect(result.ErrString()).To(BeEmpty()) 72 | Expect(result.ExitCode()).To(BeZero()) 73 | }) 74 | }) 75 | 76 | When("Not logged in", func() { 77 | BeforeEach(func() { 78 | cmd = NewCommand().Arg("token") 79 | }) 80 | 81 | It("Fails", func() { 82 | result := cmd.Run(ctx) 83 | Expect(result.OutString()).To(BeEmpty()) 84 | Expect(result.ErrString()).To(ContainSubstring("Not logged in")) 85 | Expect(result.ExitCode()).ToNot(BeZero()) 86 | }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /cmd/ocm/config/get/get.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package get 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/config" 26 | ) 27 | 28 | var args struct { 29 | debug bool 30 | } 31 | 32 | var Cmd = &cobra.Command{ 33 | Use: "get [flags] VARIABLE", 34 | Short: "Prints the value of a config variable", 35 | Long: "Prints the value of a config variable. See 'ocm config --help' for supported config variables.", 36 | Args: cobra.ExactArgs(1), 37 | RunE: run, 38 | } 39 | 40 | func init() { 41 | flags := Cmd.Flags() 42 | flags.BoolVar( 43 | &args.debug, 44 | "debug", 45 | false, 46 | "Enable debug mode.", 47 | ) 48 | } 49 | 50 | func run(cmd *cobra.Command, argv []string) error { 51 | // Load the configuration file: 52 | cfg, err := config.Load() 53 | if err != nil { 54 | return fmt.Errorf("Can't load config file: %v", err) 55 | } 56 | 57 | // If the configuration file doesn't exist yet assume that all the configuration settings 58 | // are empty: 59 | if cfg == nil { 60 | fmt.Printf("\n") 61 | return nil 62 | } 63 | 64 | // Print the value of the requested configuration setting: 65 | switch argv[0] { 66 | case "access_token": 67 | fmt.Fprintf(os.Stdout, "%s\n", cfg.AccessToken) 68 | case "client_id": 69 | fmt.Fprintf(os.Stdout, "%s\n", cfg.ClientID) 70 | case "client_secret": 71 | fmt.Fprintf(os.Stdout, "%s\n", cfg.ClientSecret) 72 | case "insecure": 73 | fmt.Fprintf(os.Stdout, "%v\n", cfg.Insecure) 74 | case "password": 75 | fmt.Fprintf(os.Stdout, "%s\n", cfg.Password) 76 | case "refresh_token": 77 | fmt.Fprintf(os.Stdout, "%s\n", cfg.RefreshToken) 78 | case "scopes": 79 | fmt.Fprintf(os.Stdout, "%s\n", cfg.Scopes) 80 | case "token_url": 81 | fmt.Fprintf(os.Stdout, "%s\n", cfg.TokenURL) 82 | case "url": 83 | fmt.Fprintf(os.Stdout, "%s\n", cfg.URL) 84 | case "pager": 85 | fmt.Fprintf(os.Stdout, "%s\n", cfg.Pager) 86 | case "user": 87 | fmt.Fprintf(os.Stdout, "%s\n", cfg.User) 88 | default: 89 | return fmt.Errorf("Unknown setting") 90 | } 91 | 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /cmd/ocm/completion/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package completion 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var Cmd = &cobra.Command{ 27 | Use: "completion [bash|zsh|fish|powershell]", 28 | Short: "Generate completion scripts for various shells", 29 | Long: `To load completions: 30 | 31 | Bash: 32 | 33 | $ source <(ocm completion bash) 34 | 35 | # To load completions for each session, execute once: 36 | Linux: 37 | $ ocm completion bash > /etc/bash_completion.d/ocm 38 | MacOS: 39 | $ ocm completion bash > /usr/local/etc/bash_completion.d/ocm 40 | 41 | Zsh: 42 | 43 | # If shell completion is not already enabled in your environment you will need 44 | # to enable it. You can execute the following once: 45 | 46 | $ echo "autoload -U compinit; compinit" >> ~/.zshrc 47 | 48 | # To load completions for each session, execute once: 49 | $ ocm completion zsh > "${fpath[1]}/_ocm" 50 | 51 | # You will need to start a new shell for this setup to take effect. 52 | 53 | Fish: 54 | 55 | $ ocm completion fish | source 56 | 57 | # To load completions for each session, execute once: 58 | $ ocm completion fish > ~/.config/fish/completions/ocm.fish 59 | 60 | P.S. Debugging completion logic: 61 | - Set BASH_COMP_DEBUG_FILE env var to enable logging to that file. 62 | - See https://github.com/spf13/cobra/blob/master/shell_completions.md. 63 | `, 64 | DisableFlagsInUseLine: true, 65 | ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, 66 | Args: cobra.RangeArgs(0, 1), 67 | RunE: func(cmd *cobra.Command, args []string) error { 68 | // backward compatibility (previously only supported bash, took no args) 69 | if len(args) == 0 { 70 | args = []string{"bash"} 71 | } 72 | 73 | switch args[0] { 74 | case "bash": 75 | cmd.Root().GenBashCompletion(os.Stdout) 76 | case "zsh": 77 | cmd.Root().GenZshCompletion(os.Stdout) 78 | case "fish": 79 | cmd.Root().GenFishCompletion(os.Stdout, true) 80 | case "powershell": 81 | cmd.Root().GenPowerShellCompletion(os.Stdout) 82 | default: 83 | return fmt.Errorf("invalid shell %q", args[0]) 84 | } 85 | return nil 86 | }, 87 | } 88 | -------------------------------------------------------------------------------- /cmd/ocm/create/idp/htpasswd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package idp 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | 20 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 21 | 22 | "github.com/AlecAivazis/survey/v2" 23 | pwdgen "github.com/m1/go-generate-password/generator" 24 | ) 25 | 26 | func buildHtpasswdIdp(cluster *cmv1.Cluster, idpName string) (cmv1.IdentityProviderBuilder, string, error) { 27 | idpBuilder := cmv1.IdentityProviderBuilder{} 28 | message := fmt.Sprintf("Securely store your username and password.\n" + 29 | "If you lose these credentials, you will have to delete and recreate the IDP.\n") 30 | 31 | username := args.htpasswdUsername 32 | password := args.htpasswdPassword 33 | 34 | if username == "" { 35 | prompt := &survey.Input{ 36 | Message: "Enter username:", 37 | } 38 | err := survey.AskOne(prompt, &username) 39 | if err != nil { 40 | return idpBuilder, "", errors.New("Expected a username") 41 | } 42 | } 43 | 44 | if password == "" { 45 | prompt := &survey.Password{ 46 | Message: "Enter password or leave empty to generate:", 47 | } 48 | err := survey.AskOne(prompt, &password) 49 | if err != nil { 50 | return idpBuilder, "", errors.New("Expected a password") 51 | } 52 | } 53 | if password == "" { 54 | generator, err := pwdgen.NewWithDefault() 55 | if err != nil { 56 | return idpBuilder, "", errors.New("Failed to initialize password generator") 57 | } 58 | generatedPwd, err := generator.Generate() 59 | if err != nil { 60 | return idpBuilder, "", errors.New("Failed to generate a password") 61 | } 62 | password = *generatedPwd 63 | message += "You can now log in with the provided username and the password '" + password + "'.\n" 64 | } 65 | 66 | // Create HTPasswd IDP 67 | htpasswdIDP := cmv1.NewHTPasswdIdentityProvider(). 68 | Username(username). 69 | Password(password) 70 | 71 | // Create new IDP with HTPasswd provider 72 | idpBuilder. 73 | Type("HTPasswdIdentityProvider"). // FIXME: ocm-api-model has the wrong enum values 74 | Name(idpName). 75 | MappingMethod(cmv1.IdentityProviderMappingMethod(args.mappingMethod)). 76 | Htpasswd(htpasswdIDP) 77 | 78 | return idpBuilder, message, nil 79 | } 80 | -------------------------------------------------------------------------------- /cmd/ocm/config/set/set.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package set 18 | 19 | import ( 20 | "fmt" 21 | "strconv" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/config" 26 | ) 27 | 28 | var args struct { 29 | debug bool 30 | } 31 | 32 | var Cmd = &cobra.Command{ 33 | Use: "set [flags] VARIABLE VALUE", 34 | Short: "Sets the variable's value", 35 | Long: "Sets the value of a config variable. See 'ocm config --help' for supported config variables.", 36 | Args: cobra.ExactArgs(2), 37 | RunE: run, 38 | } 39 | 40 | func init() { 41 | flags := Cmd.Flags() 42 | flags.BoolVar( 43 | &args.debug, 44 | "debug", 45 | false, 46 | "Enable debug mode.", 47 | ) 48 | } 49 | 50 | func run(cmd *cobra.Command, argv []string) error { 51 | // Load the configuration: 52 | cfg, err := config.Load() 53 | if err != nil { 54 | return fmt.Errorf("Can't load config file: %v", err) 55 | } 56 | 57 | // Create an empty configuration if the configuration file doesn't exist: 58 | if cfg == nil { 59 | cfg = &config.Config{} 60 | } 61 | 62 | // Copy the value given in the command line to the configuration: 63 | name := argv[0] 64 | value := argv[1] 65 | switch name { 66 | case "access_token": 67 | cfg.AccessToken = value 68 | case "client_id": 69 | cfg.ClientID = value 70 | case "client_secret": 71 | cfg.ClientSecret = value 72 | case "insecure": 73 | cfg.Insecure, err = strconv.ParseBool(value) 74 | if err != nil { 75 | return fmt.Errorf("Failed to set insecure: %v", value) 76 | } 77 | case "password": 78 | cfg.Password = value 79 | case "refresh_token": 80 | cfg.RefreshToken = value 81 | case "scopes": 82 | return fmt.Errorf("Setting scopes is unsupported") 83 | case "token_url": 84 | cfg.TokenURL = value 85 | case "url": 86 | cfg.URL = value 87 | case "user": 88 | cfg.User = value 89 | case "pager": 90 | cfg.Pager = value 91 | default: 92 | return fmt.Errorf("Unknown setting") 93 | } 94 | 95 | // Save the configuration: 96 | err = config.Save(cfg) 97 | if err != nil { 98 | return fmt.Errorf("Can't save config file: %v", err) 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /hack/commit-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2022 Red Hat, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This generates the commit necessary for release of the next z-stream tag. 19 | 20 | git fetch --tags 21 | tagcommit=$(git rev-list --tags --max-count=1) 22 | current=$(git describe --tags $tagcommit | sed 's/v//') 23 | echo "Current version is $current" 24 | 25 | base=$(echo $current | grep -o ".*\.") 26 | next_z=$(echo $current | sed -E "s/.*\.([0-9]*)/\1+1/" | bc) 27 | base_y=$(echo $current | grep -o "^[0-9]*\.[0-9]*") 28 | next_y=$(echo $base_y | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') 29 | 30 | # Update version 31 | read -r -p "Bump minor version for '$current' ocm cli? [y/N] " response 32 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]] 33 | then 34 | next=$next_y.0 35 | else 36 | next=$base$next_z 37 | fi 38 | 39 | echo "Next version will be $next" 40 | 41 | # Update version 42 | read -r -p "Update version to '$next'? [y/N] " response 43 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]] 44 | then 45 | sed -i "s/$current/$next/" pkg/info/info.go 46 | fi 47 | 48 | # Update changelog 49 | read -r -p "Update changelog? [y/N] " response 50 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]] 51 | then 52 | log=$(git log v$current..HEAD --oneline --no-merges --no-decorate --reverse | sed "s/^\w*/-/") 53 | echo "$log" 54 | 55 | rest=$(awk "/ $current /{found=1} found" CHANGES.md) 56 | header=$(cat << EOM 57 | # Changes 58 | 59 | This document describes the relevant changes between releases of the\n\`ocm\` command line tool. 60 | EOM 61 | ) 62 | echo -e "$header\n\n## $next $(date "+%b %-d %Y")\n\n$log\n\n$rest" > CHANGES.md 63 | fi 64 | 65 | # Commit changes 66 | branch="release_$next" 67 | read -r -p "Commit changes to branch '$branch'? [y/N] " response 68 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]] 69 | then 70 | git checkout -b $branch 71 | git commit --all --message "Release v$next" --message "$log" 72 | else 73 | echo -e "\tgit checkout -b $branch" 74 | echo -e "\tgit commit --all" 75 | fi 76 | 77 | read -r -p "Push branch '$branch' to GitHub? [y/N] " response 78 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]] 79 | then 80 | git push --set-upstream origin $branch 81 | else 82 | echo -e "\tgit push --set-upstream origin $branch" 83 | fi 84 | 85 | -------------------------------------------------------------------------------- /.github/workflows/check-pull-request.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | name: Check pull request 18 | 19 | on: 20 | pull_request: 21 | branches: 22 | - main 23 | 24 | permissions: 25 | contents: read 26 | pull-requests: read 27 | 28 | jobs: 29 | 30 | test: 31 | name: Test 32 | strategy: 33 | matrix: 34 | platform: 35 | - ubuntu-latest 36 | - macos-latest 37 | - windows-latest 38 | runs-on: ${{ matrix.platform }} 39 | steps: 40 | - name: Checkout the source 41 | uses: actions/checkout@v3 42 | - name: Install Keyrings (macOS-only) 43 | if: ${{ contains(fromJSON('["macos-latest"]'), matrix.platform) }} 44 | run: brew install pass gnupg 45 | - name: Install Keyrings (linux) 46 | if: ${{ contains(fromJSON('["ubuntu-latest"]'), matrix.platform) }} 47 | run: sudo apt-get install pass 48 | - name: Setup Go 49 | uses: actions/setup-go@v5 50 | with: 51 | go-version: 1.22 52 | - name: Setup dependency cache 53 | uses: actions/cache@v3 54 | with: 55 | path: | 56 | ~/.cache/go-build 57 | ~/go/pkg/mod 58 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 59 | restore-keys: | 60 | ${{ runner.os }}-go- 61 | - name: Ensure dependencies are downloaded 62 | run: go mod download 63 | - name: Setup Ginkgo 64 | run: go install github.com/onsi/ginkgo/v2/ginkgo@v2.19 65 | - name: Run the tests 66 | run: make tests 67 | 68 | golangci: 69 | name: Lint 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Checkout the source 73 | uses: actions/checkout@v4 74 | - name: Setup Go 75 | uses: actions/setup-go@v5 76 | with: 77 | go-version: 1.23 78 | - name: Setup dependency cache 79 | uses: actions/cache@v3 80 | with: 81 | path: | 82 | ~/.cache/go-build 83 | ~/go/pkg/mod 84 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 85 | restore-keys: | 86 | ${{ runner.os }}-go- 87 | - name: Ensure dependencies are downloaded 88 | run: go mod download 89 | - name: golangci-lint 90 | uses: golangci/golangci-lint-action@v6 91 | with: 92 | version: v1.64.8 -------------------------------------------------------------------------------- /cmd/ocm/config/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | "strings" 23 | 24 | "github.com/spf13/cobra" 25 | 26 | "github.com/openshift-online/ocm-cli/cmd/ocm/config/get" 27 | "github.com/openshift-online/ocm-cli/cmd/ocm/config/set" 28 | "github.com/openshift-online/ocm-cli/pkg/config" 29 | "github.com/openshift-online/ocm-cli/pkg/properties" 30 | ) 31 | 32 | func configVarDocs() (ret string) { 33 | // TODO(efried): Figure out how to get the Type without instantiating. 34 | configType := reflect.ValueOf(config.Config{}).Type() 35 | fieldHelps := make([]string, configType.NumField()) 36 | for i := 0; i < len(fieldHelps); i++ { 37 | tag := configType.Field(i).Tag 38 | // TODO(efried): Use JSON parser instead 39 | name := strings.Split(tag.Get("json"), ",")[0] 40 | doc := tag.Get("doc") 41 | fieldHelps[i] = fmt.Sprintf("\t%-15s%s", name, doc) 42 | } 43 | ret = strings.Join(fieldHelps, "\n") 44 | return 45 | } 46 | 47 | func longHelp() (ret string) { 48 | loc, err := config.Location() 49 | if err != nil { 50 | // I think this only happens if homedir.Dir() fails, which is unlikely. 51 | loc = fmt.Sprintf("UNKNOWN (%s)", err) 52 | } 53 | ret = fmt.Sprintf(`Get or set variables from a configuration file. 54 | 55 | The location of the configuration file is gleaned from the 'OCM_CONFIG' environment variable, 56 | or ~/.ocm.json if that variable is unset. Currently using: %s 57 | 58 | The following variables are supported: 59 | 60 | %s 61 | 62 | Note that "ocm config get access_token" gives whatever the file contains - may be missing or expired; 63 | you probably want "ocm token" command instead which will obtain a fresh token if needed. 64 | 65 | If '%s' is set, the configuration file is ignored and the keyring is used instead. The 66 | following backends are supported for the keyring: 67 | 68 | - macOS: keychain, pass 69 | - Linux: secret-service, pass 70 | - Windows: wincred 71 | 72 | Available Keyrings on your OS: %s 73 | `, loc, configVarDocs(), properties.KeyringEnvKey, strings.Join(config.GetKeyrings(), ", ")) 74 | return 75 | } 76 | 77 | var Cmd = &cobra.Command{ 78 | Use: "config COMMAND VARIABLE", 79 | Short: "get or set configuration variables", 80 | Long: longHelp(), 81 | } 82 | 83 | func init() { 84 | Cmd.AddCommand(get.Cmd) 85 | Cmd.AddCommand(set.Cmd) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/utils/jwks_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/MicahParks/jwkset" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | 11 | "github.com/openshift-online/ocm-cli/pkg/utils" 12 | ) 13 | 14 | var _ = Describe("Jwks helpers", func() { 15 | type equalityTestSpec struct { 16 | JwksStrA string 17 | JwksStrB string 18 | Equal bool 19 | ValidA bool 20 | ValidB bool 21 | } 22 | DescribeTable("checking equality", func(spec equalityTestSpec) { 23 | var jwksA, jwksB jwkset.JWKSMarshal 24 | if spec.ValidA { 25 | Expect(json.Unmarshal(json.RawMessage(spec.JwksStrA), &jwksA)).ToNot(HaveOccurred(), 26 | "failed to unmarshall the first jwks parameter") 27 | } 28 | if spec.ValidB { 29 | Expect(json.Unmarshal(json.RawMessage(spec.JwksStrB), &jwksB)).ToNot(HaveOccurred(), 30 | "failed to unmarshall the first jwks parameter") 31 | } 32 | Expect(utils.JwksEqual(spec.JwksStrA, spec.JwksStrB)).To(Equal(spec.Equal)) 33 | }, 34 | Entry("returns true for two equal jwks", equalityTestSpec{ 35 | JwksStrA: generateJwksStr("test1"), 36 | JwksStrB: generateJwksStr("test1"), 37 | Equal: true, 38 | ValidA: true, 39 | ValidB: true, 40 | }), 41 | Entry("returns false for two unqeual jwks", equalityTestSpec{ 42 | JwksStrA: generateJwksStr("test2"), 43 | JwksStrB: generateJwksStr("foobar"), 44 | Equal: false, 45 | ValidA: true, 46 | ValidB: true, 47 | }), 48 | Entry("returns false if the first parameter is invalid", equalityTestSpec{ 49 | JwksStrA: "foobar", 50 | JwksStrB: generateJwksStr("test3"), 51 | Equal: false, 52 | ValidA: false, 53 | ValidB: true, 54 | }), 55 | Entry("returns false if the second parameter is invalid", equalityTestSpec{ 56 | JwksStrA: generateJwksStr("test4"), 57 | JwksStrB: "foobar", 58 | Equal: false, 59 | ValidA: true, 60 | ValidB: false, 61 | }), 62 | ) 63 | }) 64 | 65 | func generateJwksStr(kid string) string { 66 | //nolint:lll 67 | return fmt.Sprintf(`{ 68 | "keys": [ 69 | { 70 | "e": "AQAB", 71 | "n": "ubDivpwTQ84zcmhCC7Dlun34pv8N-kd44Kx1ohYa3HqAGFrYGVvxAc4bRgfFD1_Rt03uNGFy0lBkZ_Jv5mjGJ97eBXACuU1wiIX_C6gT7gwH9WlmbUnNndWz5CN7mvtspcHWv0E_uM08LJCNkThFe7dQESbWkyS8RO-dfJBUOwZH0N38AUnXOkNKfvFTMQr_-I9YHgaWr7rZxhoPV5viE1aM_H-kaLsgqFbD2VSGC48qpeO4FktnRrvM92mtK8RyqM0w4BnbNAlIk22SIWK0H_2nusdtYHnkPTY1nBk4f-TvcHLA1hEbGKK3eM9IQwWZFtmSlxwPQAD7l_gNREPPtDSuy2q5qHH6Ew3rKFxE2PTF0UNH1oHsgCf6DpRIcrqQ7rSuUAghmFgkqXBneiI-lCFqSHeFYUr2LpyF4LJ5GyWaEIyG54Rv9vpfpJYd6RmTfEferzwgCnm2fmClZQVa_fQMxluzw6UldrDzUF-rOko4klZNeSZita-5IZ3C9zU4ZVWEr4RR4_F3gnYLXYwz7asIFckIpXoPTggB29OpoWJPoYulMMedhZ3A1IjCCx7_Nxgj6qqwrJuyubkURpBsnhxVIsfumn5eNaDy2D8N2cpRxZmnCIJZ2ffEaLj0UNp4M3MAQONRKCaPNx-GbRxit3PvNDCr_LQ9C5fI-zUeUb8", 72 | "alg": "RS256", 73 | "kid": "%s", 74 | "kty": "RSA", 75 | "use": "sig" 76 | } 77 | ]}`, kid) 78 | } 79 | -------------------------------------------------------------------------------- /cmd/ocm/cluster/status/status.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package status 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/openshift-online/ocm-cli/pkg/ocm" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var Cmd = &cobra.Command{ 27 | Use: "status [flags] CLUSTER_ID", 28 | Short: "Status of a cluster", 29 | Long: "Get the status of a cluster identified by its cluster ID", 30 | RunE: run, 31 | } 32 | 33 | func run(cmd *cobra.Command, argv []string) error { 34 | 35 | if len(argv) != 1 { 36 | return fmt.Errorf("Expected exactly one cluster id") 37 | } 38 | 39 | // Create the client for the OCM API: 40 | connection, err := ocm.NewConnection().Build() 41 | if err != nil { 42 | return fmt.Errorf("Failed to create OCM connection: %v", err) 43 | } 44 | defer connection.Close() 45 | 46 | // Get the client for the resource that manages the collection of clusters: 47 | resource := connection.ClustersMgmt().V1().Clusters() 48 | 49 | // Get the resource that manages the cluster that we want to display: 50 | clusterID := argv[0] 51 | clusterResource := resource.Cluster(clusterID) 52 | 53 | // Retrieve the collection of clusters: 54 | response, err := clusterResource.Get().Send() 55 | if err != nil { 56 | return fmt.Errorf("Can't retrieve clusters: %s", err) 57 | } 58 | 59 | cluster := response.Body() 60 | 61 | // Get data out of the response 62 | state := cluster.State() 63 | 64 | // Fetch metrics from AMS 65 | search := fmt.Sprintf("cluster_id = '%s'", clusterID) 66 | subsList, err := connection.AccountsMgmt().V1().Subscriptions().List().Search(search).Send() 67 | if err != nil { 68 | return fmt.Errorf("Can't retrieve subscriptions: %s", err) 69 | } 70 | size, ok := subsList.GetSize() 71 | if !ok || size == 0 { 72 | fmt.Printf("State: %s\n", state) 73 | return nil 74 | } 75 | 76 | sub := subsList.Items().Get(0) 77 | metrics, ok := sub.GetMetrics() 78 | if !ok { 79 | // No metrics 80 | fmt.Printf("State: %s\n", state) 81 | return nil 82 | } 83 | 84 | clusterMemory := metrics[0].Memory() 85 | clusterCPU := metrics[0].Cpu() 86 | memUsed := clusterMemory.Used().Value() / 1000000000 87 | memTotal := clusterMemory.Total().Value() / 1000000000 88 | 89 | fmt.Printf("State: %s\n"+ 90 | "Memory: %.2f/%.2f used\n"+ 91 | "CPU: %.2f/%.2f used\n", 92 | state, 93 | memUsed, memTotal, 94 | clusterCPU.Used().Value(), clusterCPU.Total().Value(), 95 | ) 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /pkg/account/account.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package account 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | 23 | "github.com/openshift-online/ocm-sdk-go" 24 | amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" 25 | ) 26 | 27 | // GetRolesFromUsers gets all roles a specific user possesses. 28 | func GetRolesFromUsers(accounts []*amv1.Account, 29 | conn *sdk.Connection) (results map[*amv1.Account][]string, error error) { 30 | // Prepare the results: 31 | results = map[*amv1.Account][]string{} 32 | 33 | // Prepare a map of accounts indexed by identifier: 34 | accountsMap := map[string]*amv1.Account{} 35 | for _, account := range accounts { 36 | accountsMap[account.ID()] = account 37 | } 38 | 39 | // Prepare a query to retrieve all the role bindings that correspond to any of the 40 | // accounts: 41 | ids := &bytes.Buffer{} 42 | for i, account := range accounts { 43 | if i > 0 { 44 | fmt.Fprintf(ids, ", ") 45 | } 46 | fmt.Fprintf(ids, "'%s'", account.ID()) 47 | } 48 | query := fmt.Sprintf("account_id in (%s)", ids) 49 | 50 | index := 1 51 | size := 100 52 | 53 | for { 54 | // Prepare the request: 55 | response, err := conn.AccountsMgmt().V1().RoleBindings().List(). 56 | Size(size). 57 | Page(index). 58 | Parameter("search", query). 59 | Send() 60 | 61 | if err != nil { 62 | return nil, fmt.Errorf("Can't retrieve roles: %v", err) 63 | } 64 | // Loop through the results and save them: 65 | response.Items().Each(func(item *amv1.RoleBinding) bool { 66 | account := accountsMap[item.Account().ID()] 67 | 68 | itemID := item.Role().ID() 69 | 70 | if _, ok := results[account]; ok { 71 | if !stringInList(results[account], itemID) { 72 | results[account] = append(results[account], itemID) 73 | } 74 | } else { 75 | results[account] = append(results[account], itemID) 76 | } 77 | 78 | return true 79 | }) 80 | 81 | // Break the loop if the page size is smaller than requested, as that indicates 82 | // that this is the last page: 83 | if response.Size() < size { 84 | break 85 | } 86 | index++ 87 | } 88 | 89 | return 90 | } 91 | 92 | // stringInList returns a bool signifying whether 93 | // a string is in a string array. 94 | func stringInList(strArr []string, key string) bool { 95 | for _, str := range strArr { 96 | if str == key { 97 | return true 98 | } 99 | } 100 | return false 101 | } 102 | -------------------------------------------------------------------------------- /pkg/ocm/mock_cluster.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: cluster.go 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -source=cluster.go -package=ocm -destination=mock_cluster.go 7 | // 8 | 9 | // Package ocm is a generated GoMock package. 10 | package ocm 11 | 12 | import ( 13 | reflect "reflect" 14 | 15 | v1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 16 | gomock "go.uber.org/mock/gomock" 17 | ) 18 | 19 | // MockCluster is a mock of Cluster interface. 20 | type MockCluster struct { 21 | ctrl *gomock.Controller 22 | recorder *MockClusterMockRecorder 23 | } 24 | 25 | // MockClusterMockRecorder is the mock recorder for MockCluster. 26 | type MockClusterMockRecorder struct { 27 | mock *MockCluster 28 | } 29 | 30 | // NewMockCluster creates a new mock instance. 31 | func NewMockCluster(ctrl *gomock.Controller) *MockCluster { 32 | mock := &MockCluster{ctrl: ctrl} 33 | mock.recorder = &MockClusterMockRecorder{mock} 34 | return mock 35 | } 36 | 37 | // EXPECT returns an object that allows the caller to indicate expected use. 38 | func (m *MockCluster) EXPECT() *MockClusterMockRecorder { 39 | return m.recorder 40 | } 41 | 42 | // CcsEnabled mocks base method. 43 | func (m *MockCluster) CcsEnabled() bool { 44 | m.ctrl.T.Helper() 45 | ret := m.ctrl.Call(m, "CcsEnabled") 46 | ret0, _ := ret[0].(bool) 47 | return ret0 48 | } 49 | 50 | // CcsEnabled indicates an expected call of CcsEnabled. 51 | func (mr *MockClusterMockRecorder) CcsEnabled() *gomock.Call { 52 | mr.mock.ctrl.T.Helper() 53 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CcsEnabled", reflect.TypeOf((*MockCluster)(nil).CcsEnabled)) 54 | } 55 | 56 | // CloudProviderId mocks base method. 57 | func (m *MockCluster) CloudProviderId() string { 58 | m.ctrl.T.Helper() 59 | ret := m.ctrl.Call(m, "CloudProviderId") 60 | ret0, _ := ret[0].(string) 61 | return ret0 62 | } 63 | 64 | // CloudProviderId indicates an expected call of CloudProviderId. 65 | func (mr *MockClusterMockRecorder) CloudProviderId() *gomock.Call { 66 | mr.mock.ctrl.T.Helper() 67 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloudProviderId", reflect.TypeOf((*MockCluster)(nil).CloudProviderId)) 68 | } 69 | 70 | // Id mocks base method. 71 | func (m *MockCluster) Id() string { 72 | m.ctrl.T.Helper() 73 | ret := m.ctrl.Call(m, "Id") 74 | ret0, _ := ret[0].(string) 75 | return ret0 76 | } 77 | 78 | // Id indicates an expected call of Id. 79 | func (mr *MockClusterMockRecorder) Id() *gomock.Call { 80 | mr.mock.ctrl.T.Helper() 81 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockCluster)(nil).Id)) 82 | } 83 | 84 | // State mocks base method. 85 | func (m *MockCluster) State() v1.ClusterState { 86 | m.ctrl.T.Helper() 87 | ret := m.ctrl.Call(m, "State") 88 | ret0, _ := ret[0].(v1.ClusterState) 89 | return ret0 90 | } 91 | 92 | // State indicates an expected call of State. 93 | func (mr *MockClusterMockRecorder) State() *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockCluster)(nil).State)) 96 | } 97 | -------------------------------------------------------------------------------- /pkg/cluster/versions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cluster 18 | 19 | import ( 20 | "fmt" 21 | "sort" 22 | "strings" 23 | 24 | goVersion "github.com/hashicorp/go-version" 25 | 26 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 27 | ) 28 | 29 | const prefix = "openshift-v" 30 | 31 | func DropOpenshiftVPrefix(v string) string { 32 | return strings.TrimPrefix(v, prefix) 33 | } 34 | 35 | func EnsureOpenshiftVPrefix(v string) string { 36 | if !strings.HasPrefix(v, prefix) { 37 | return prefix + v 38 | } 39 | return v 40 | } 41 | 42 | // GetEnabledVersions returns the versions with enabled=true, and the one that has default=true. 43 | // The returned strings are the IDs without "openshift-v" prefix (e.g. "4.6.0-rc.4-candidate") 44 | // sorted in approximate SemVer order (handling of text parts is somewhat arbitrary). 45 | func GetEnabledVersions(client *cmv1.Client, 46 | channelGroup string, 47 | gcpMarketplaceEnabled string, 48 | additionalFilters string, 49 | ) ( 50 | versions []string, defaultVersion string, err error) { 51 | collection := client.Versions() 52 | page := 1 53 | size := 100 54 | filter := "enabled = 'true'" 55 | if gcpMarketplaceEnabled != "" { 56 | filter = fmt.Sprintf("%s AND gcp_marketplace_enabled = '%s'", filter, gcpMarketplaceEnabled) 57 | } 58 | if channelGroup != "" { 59 | filter = fmt.Sprintf("%s AND channel_group = '%s'", filter, channelGroup) 60 | } 61 | if additionalFilters != "" { 62 | filter = fmt.Sprintf("%s %s", filter, additionalFilters) 63 | } 64 | for { 65 | response, err := collection.List(). 66 | Search(filter). 67 | Page(page). 68 | Size(size). 69 | Send() 70 | if err != nil { 71 | return nil, "", err 72 | } 73 | 74 | for _, version := range response.Items().Slice() { 75 | short := DropOpenshiftVPrefix(version.ID()) 76 | if version.Enabled() { 77 | versions = append(versions, short) 78 | } 79 | if version.Default() { 80 | defaultVersion = short 81 | } 82 | } 83 | 84 | if response.Size() < size { 85 | break 86 | } 87 | page++ 88 | } 89 | 90 | sort.Slice(versions, func(i, j int) (less bool) { 91 | s1, s2 := versions[i], versions[j] 92 | v1, err1 := goVersion.NewVersion(s1) 93 | v2, err2 := goVersion.NewVersion(s2) 94 | if err1 != nil || err2 != nil { 95 | // Fall back to lexicographic comparison. 96 | return s1 < s2 97 | } 98 | return v1.LessThan(v2) 99 | }) 100 | return versions, defaultVersion, nil 101 | } 102 | -------------------------------------------------------------------------------- /pkg/urls/well_known.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This file contains constants for well known URLs. 18 | 19 | package urls 20 | 21 | import ( 22 | "fmt" 23 | "net/url" 24 | "strings" 25 | 26 | "github.com/openshift-online/ocm-cli/pkg/config" 27 | sdk "github.com/openshift-online/ocm-sdk-go" 28 | ) 29 | 30 | // OfflineTokenPage is the URL of the page used to generate offline access tokens. 31 | const OfflineTokenPage = "https://console.redhat.com/openshift/token" // #nosec G101 32 | 33 | const ( 34 | OCMProductionURL = "https://api.openshift.com" 35 | OCMStagingURL = "https://api.stage.openshift.com" 36 | OCMIntegrationURL = "https://api.integration.openshift.com" 37 | ) 38 | 39 | var OCMURLAliases = map[string]string{ 40 | "production": OCMProductionURL, 41 | "prod": OCMProductionURL, 42 | "prd": OCMProductionURL, 43 | "staging": OCMStagingURL, 44 | "stage": OCMStagingURL, 45 | "stg": OCMStagingURL, 46 | "integration": OCMIntegrationURL, 47 | "int": OCMIntegrationURL, 48 | } 49 | 50 | func ValidOCMUrlAliases() []string { 51 | keys := make([]string, 0, len(OCMURLAliases)) 52 | for k := range OCMURLAliases { 53 | keys = append(keys, k) 54 | } 55 | return keys 56 | } 57 | 58 | // URL Precedent (from highest priority to lowest priority): 59 | // 1. runtime `--url` cli arg (key found in `urlAliases`) 60 | // 2. runtime `--url` cli arg (non-empty string) 61 | // 3. config file `URL` value (non-empty string) 62 | // 4. sdk.DefaultURL 63 | // 64 | // Finally, it will try to url.ParseRequestURI the resolved URL to make sure it's a valid URL. 65 | func ResolveGatewayURL(optionalParsedCliFlagValue string, optionalParsedConfig *config.Config) (string, error) { 66 | gatewayURL := sdk.DefaultURL 67 | source := "default" 68 | if optionalParsedCliFlagValue != "" { 69 | gatewayURL = optionalParsedCliFlagValue 70 | source = "flag" 71 | if _, ok := OCMURLAliases[optionalParsedCliFlagValue]; ok { 72 | gatewayURL = OCMURLAliases[optionalParsedCliFlagValue] 73 | } 74 | } else if optionalParsedConfig != nil && optionalParsedConfig.URL != "" { 75 | // re-use the URL from the config file 76 | gatewayURL = optionalParsedConfig.URL 77 | source = "config" 78 | } 79 | 80 | url, err := url.ParseRequestURI(gatewayURL) 81 | if err != nil { 82 | return "", fmt.Errorf( 83 | "%w\n\nURL Source: %s\nExpected an absolute URI/path (e.g. %s) or a case-sensitive alias, one of: [%s]", 84 | err, source, sdk.DefaultURL, strings.Join(ValidOCMUrlAliases(), ", ")) 85 | } 86 | 87 | return url.String(), nil 88 | } 89 | -------------------------------------------------------------------------------- /cmd/ocm/account/status/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package status 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | acc_util "github.com/openshift-online/ocm-cli/pkg/account" 25 | "github.com/openshift-online/ocm-cli/pkg/config" 26 | "github.com/openshift-online/ocm-cli/pkg/ocm" 27 | amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" 28 | ) 29 | 30 | var args struct { 31 | debug bool 32 | } 33 | 34 | // Cmd is a new Cobra Command 35 | var Cmd = &cobra.Command{ 36 | Use: "status", 37 | Short: "Status of current user.", 38 | Long: "Display status of current user.", 39 | Args: cobra.NoArgs, 40 | RunE: run, 41 | } 42 | 43 | func init() { 44 | // Add flags to rootCmd: 45 | flags := Cmd.Flags() 46 | flags.BoolVar( 47 | &args.debug, 48 | "debug", 49 | false, 50 | "Enable debug mode.", 51 | ) 52 | } 53 | 54 | func run(cmd *cobra.Command, argv []string) error { 55 | 56 | // Load the configuration file: 57 | cfg, err := config.Load() 58 | if err != nil { 59 | return fmt.Errorf("Can't load config file: %v", err) 60 | } 61 | if cfg == nil { 62 | return fmt.Errorf("Not logged in, run the 'login' command") 63 | } 64 | 65 | // Create the client for the OCM API: 66 | connection, err := ocm.NewConnection().Build() 67 | if err != nil { 68 | return err 69 | } 70 | defer connection.Close() 71 | 72 | // Send the request: 73 | response, err := connection.AccountsMgmt().V1().CurrentAccount().Get(). 74 | Send() 75 | if err != nil { 76 | return fmt.Errorf("Can't get current account: %v", err) 77 | } 78 | 79 | // Display user and which server they are logged into 80 | currAccount := response.Body() 81 | currOrg := currAccount.Organization() 82 | fmt.Printf("User %s on %s in org '%s' %s (external_id: %s) ", 83 | currAccount.Username(), cfg.URL, currOrg.Name(), currOrg.ID(), currOrg.ExternalID()) 84 | 85 | // Display roles currently assigned to the user 86 | roleSlice, err := acc_util.GetRolesFromUsers([]*amv1.Account{currAccount}, connection) 87 | if err != nil { 88 | return err 89 | } 90 | fmt.Printf("Roles: %v\n", nicePrint(roleSlice[currAccount])) 91 | 92 | return nil 93 | } 94 | 95 | // prints array as string without brackets 96 | func nicePrint(stringArr []string) string { 97 | var finalString string 98 | for i, element := range stringArr { 99 | if i > 0 { 100 | finalString = fmt.Sprintf(`%s, %s`, finalString, element) 101 | } else { 102 | finalString = element 103 | } 104 | } 105 | return finalString 106 | } 107 | -------------------------------------------------------------------------------- /pkg/plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // Handler is capable of parsing command line arguments 11 | // and performing executable filename lookups to search 12 | // for valid plugin files, and execute found plugins. 13 | type Handler interface { 14 | // exists at the given filename, or a boolean false. 15 | // Lookup will iterate over a list of given prefixes 16 | // in order to recognize valid plugin filenames. 17 | // The first filepath to match a prefix is returned. 18 | Lookup(filename string) (string, bool) 19 | // Execute receives an executable's filepath, a slice 20 | // of arguments, and a slice of environment variables 21 | // to relay to the executable. 22 | Execute(executablePath string, cmdArgs, environment []string) error 23 | } 24 | 25 | // DefaultHandler implements Handler 26 | type DefaultHandler struct { 27 | ValidPrefixes []string 28 | } 29 | 30 | // NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of 31 | // given filename prefixes used to identify valid plugin filenames. 32 | func NewDefaultPluginHandler(validPrefixes []string) Handler { 33 | return &DefaultHandler{ 34 | ValidPrefixes: validPrefixes, 35 | } 36 | } 37 | 38 | // Lookup implements Handler 39 | func (h *DefaultHandler) Lookup(filename string) (string, bool) { 40 | for _, prefix := range h.ValidPrefixes { 41 | path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename)) 42 | if err != nil || len(path) == 0 { 43 | continue 44 | } 45 | return path, true 46 | } 47 | 48 | return "", false 49 | } 50 | 51 | // Execute implements Handler 52 | func (h *DefaultHandler) Execute(executablePath string, cmdArgs, environment []string) error { 53 | // #nosec G204 54 | cmd := exec.Command(executablePath, cmdArgs...) 55 | cmd.Stdout = os.Stdout 56 | cmd.Stderr = os.Stderr 57 | cmd.Stdin = os.Stdin 58 | cmd.Env = environment 59 | return cmd.Run() 60 | } 61 | 62 | // HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find 63 | // a plugin executable on the PATH that satisfies the given arguments. 64 | func HandlePluginCommand(pluginHandler Handler, cmdArgs []string) (found bool, err error) { 65 | remainingArgs := []string{} // all "non-flag" arguments 66 | 67 | for idx := range cmdArgs { 68 | if strings.HasPrefix(cmdArgs[idx], "-") { 69 | break 70 | } 71 | remainingArgs = append(remainingArgs, strings.Replace(cmdArgs[idx], "-", "_", -1)) 72 | } 73 | 74 | foundBinaryPath := "" 75 | 76 | // attempt to find binary, starting at longest possible name with given cmdArgs 77 | for len(remainingArgs) > 0 { 78 | path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-")) 79 | if !found { 80 | remainingArgs = remainingArgs[:len(remainingArgs)-1] 81 | continue 82 | } 83 | 84 | foundBinaryPath = path 85 | break 86 | } 87 | 88 | if len(foundBinaryPath) == 0 { 89 | return false, nil 90 | } 91 | 92 | // invoke cmd binary relaying the current environment and args given 93 | if err := pluginHandler.Execute(foundBinaryPath, cmdArgs[len(remainingArgs):], os.Environ()); err != nil { 94 | return true, err 95 | } 96 | 97 | return true, nil 98 | } 99 | -------------------------------------------------------------------------------- /cmd/ocm/gcp/get-wif-config.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/openshift-online/ocm-cli/pkg/arguments" 8 | "github.com/openshift-online/ocm-cli/pkg/dump" 9 | "github.com/openshift-online/ocm-cli/pkg/ocm" 10 | "github.com/pkg/errors" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var GetWorkloadIdentityConfigurationOpts struct { 15 | single bool 16 | parameter []string 17 | } 18 | 19 | func NewGetWorkloadIdentityConfiguration() *cobra.Command { 20 | getWorkloadIdentityPoolCmd := &cobra.Command{ 21 | Use: "wif-config [ID]", 22 | Short: "Retrieve workload identity federation configuration (wif-config) resource data.", 23 | Long: `Retrieve workload identity federation configuration (wif-config) resource data. 24 | 25 | The wif-config object returned by this command is in the json format returned 26 | by the OCM backend. It displays all of the data that is associated with the 27 | specified wif-config object. 28 | 29 | Calling this command without an ID specified results in a dump of all 30 | wif-config objects that belongs to the user's organization.`, 31 | RunE: getWorkloadIdentityConfigurationCmd, 32 | PreRunE: validationForGetWorkloadIdentityConfigurationCmd, 33 | Aliases: []string{"wif-configs"}, 34 | } 35 | 36 | fs := getWorkloadIdentityPoolCmd.Flags() 37 | arguments.AddParameterFlag(fs, &GetWorkloadIdentityConfigurationOpts.parameter) 38 | fs.BoolVar( 39 | &GetWorkloadIdentityConfigurationOpts.single, 40 | "single", 41 | false, 42 | "Return the output as a single line.", 43 | ) 44 | 45 | return getWorkloadIdentityPoolCmd 46 | } 47 | 48 | func getWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { 49 | var path string 50 | if len(argv) == 0 { 51 | path = "/api/clusters_mgmt/v1/gcp/wif_configs" 52 | } else if len(argv) == 1 { 53 | id := argv[0] 54 | path = fmt.Sprintf("/api/clusters_mgmt/v1/gcp/wif_configs/%s", id) 55 | } else { 56 | return fmt.Errorf("unexpected number of arguments") 57 | } 58 | 59 | connection, err := ocm.NewConnection().Build() 60 | if err != nil { 61 | return errors.Wrapf(err, "Failed to create OCM connection") 62 | } 63 | defer connection.Close() 64 | 65 | request := connection.Get().Path(path) 66 | arguments.ApplyParameterFlag(request, GetWorkloadIdentityConfigurationOpts.parameter) 67 | 68 | resp, err := request.Send() 69 | if err != nil { 70 | return errors.Wrapf(err, "can't send request") 71 | } 72 | status := resp.Status() 73 | body := resp.Bytes() 74 | if status < 400 { 75 | if GetWorkloadIdentityConfigurationOpts.single { 76 | err = dump.Single(os.Stdout, body) 77 | } else { 78 | err = dump.Pretty(os.Stdout, body) 79 | } 80 | } else { 81 | if GetWorkloadIdentityConfigurationOpts.single { 82 | err = dump.Single(os.Stderr, body) 83 | } else { 84 | err = dump.Pretty(os.Stderr, body) 85 | } 86 | } 87 | if err != nil { 88 | return errors.Wrapf(err, "can't print body") 89 | } 90 | return nil 91 | } 92 | 93 | func validationForGetWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { 94 | if len(argv) > 1 { 95 | return fmt.Errorf("expected at most one command line parameter containing the id of the WIF config") 96 | } 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /cmd/ocm/account/roles/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package roles 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/dump" 26 | "github.com/openshift-online/ocm-cli/pkg/ocm" 27 | amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" 28 | ) 29 | 30 | var args struct { 31 | debug bool 32 | } 33 | 34 | var Cmd = &cobra.Command{ 35 | Use: "roles [flags] [ROLE_NAME]", 36 | Short: "Retrieve information of the different roles", 37 | Long: "Get description of a role or list of all roles", 38 | Args: func(cmd *cobra.Command, args []string) error { 39 | if len(args) > 1 { 40 | return fmt.Errorf("Accepts at most 1 role name") 41 | } 42 | return nil 43 | }, 44 | RunE: run, 45 | } 46 | 47 | func init() { 48 | // Add flags to rootCmd: 49 | flags := Cmd.Flags() 50 | flags.BoolVar( 51 | &args.debug, 52 | "debug", 53 | false, 54 | "Enable debug mode.", 55 | ) 56 | } 57 | 58 | func run(cmd *cobra.Command, argv []string) error { 59 | 60 | // Create the client for the OCM API: 61 | connection, err := ocm.NewConnection().Build() 62 | if err != nil { 63 | return err 64 | } 65 | defer connection.Close() 66 | 67 | // No role name was provided; Print all roles. 68 | var rolesList []string 69 | if len(argv) < 1 { 70 | pageIndex := 1 71 | for { 72 | rolesListRequest := connection.AccountsMgmt().V1().Roles().List().Page(pageIndex) 73 | response, err := rolesListRequest.Send() 74 | if err != nil { 75 | return fmt.Errorf("Can't send request: %v", err) 76 | } 77 | response.Items().Each(func(item *amv1.Role) bool { 78 | rolesList = append(rolesList, item.ID()) 79 | return true 80 | }) 81 | pageIndex++ 82 | 83 | // Break on last page 84 | if response.Size() < 100 { 85 | break 86 | } 87 | 88 | } 89 | 90 | // Print each role: 91 | for _, element := range rolesList { 92 | fmt.Println(element) 93 | } 94 | 95 | } else { 96 | 97 | // Get role with provided id response: 98 | roleResponse, err := connection.AccountsMgmt().V1().Roles().Role(argv[0]).Get(). 99 | Send() 100 | if err != nil { 101 | return fmt.Errorf("Can't send request: %v", err) 102 | } 103 | role := roleResponse.Body() 104 | 105 | // Use role in new get request 106 | byteRole, err := connection.Get().Path(role.HREF()). 107 | Send() 108 | if err != nil { 109 | return fmt.Errorf("Can't send request: %v", err) 110 | } 111 | 112 | // Dump pretty: 113 | err = dump.Pretty(os.Stdout, byteRole.Bytes()) 114 | if err != nil { 115 | return fmt.Errorf("Failed to display role JSON: %v", err) 116 | } 117 | 118 | } 119 | 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /cmd/ocm/edit/ingress/cmd_test.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "fmt" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Parse component routes", func() { 11 | It("Parses input string for component routes", func() { 12 | componentRouteBuilder, err := parseComponentRoutes( 13 | //nolint:lll 14 | "oauth: hostname=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret", 15 | ) 16 | Expect(err).To(BeNil()) 17 | for key, builder := range componentRouteBuilder { 18 | expectedHostname := fmt.Sprintf("%s-host", key) 19 | expectedTlsRef := fmt.Sprintf("%s-secret", key) 20 | componentRoute, err := builder.Build() 21 | Expect(err).To(BeNil()) 22 | Expect(componentRoute.Hostname()).To(Equal(expectedHostname)) 23 | Expect(componentRoute.TlsSecretRef()).To(Equal(expectedTlsRef)) 24 | } 25 | }) 26 | Context("Fails to parse input string for component routes", func() { 27 | It("fails due to invalid component route", func() { 28 | _, err := parseComponentRoutes( 29 | //nolint:lll 30 | "unknown: hostname=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret", 31 | ) 32 | Expect(err).ToNot(BeNil()) 33 | Expect( 34 | err.Error(), 35 | ).To(Equal("'unknown' is not a valid component name. Expected include [oauth, console, downloads]")) 36 | }) 37 | It("fails due to wrong amount of component routes", func() { 38 | _, err := parseComponentRoutes( 39 | //nolint:lll 40 | "oauth: hostname=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret", 41 | ) 42 | Expect(err).ToNot(BeNil()) 43 | Expect( 44 | err.Error(), 45 | ).To(Equal("the expected amount of component routes is 3, but 2 have been supplied")) 46 | }) 47 | It("fails if it can split ':' in more than they key separation", func() { 48 | _, err := parseComponentRoutes( 49 | //nolint:lll 50 | "oauth: hostname=oauth:-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,", 51 | ) 52 | Expect(err).ToNot(BeNil()) 53 | Expect( 54 | err.Error(), 55 | ).To(Equal("only the name of the component should be followed by ':'")) 56 | }) 57 | It("fails due to invalid parameter", func() { 58 | _, err := parseComponentRoutes( 59 | //nolint:lll 60 | "oauth: unknown=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret", 61 | ) 62 | Expect(err).ToNot(BeNil()) 63 | Expect( 64 | err.Error(), 65 | ).To(Equal("'unknown' is not a valid parameter for a component route. Expected include [hostname, tlsSecretRef]")) 66 | }) 67 | It("fails due to wrong amount of parameters", func() { 68 | _, err := parseComponentRoutes( 69 | //nolint:lll 70 | "oauth: hostname=oauth-host,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret", 71 | ) 72 | Expect(err).ToNot(BeNil()) 73 | Expect( 74 | err.Error(), 75 | ).To(Equal("only 2 parameters are expected for each component")) 76 | }) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /pkg/provider/subnetworks.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/openshift-online/ocm-cli/pkg/cluster" 7 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 8 | ) 9 | 10 | func getAWSVPCs(client *cmv1.Client, ccs cluster.CCS, 11 | region string) (cloudVPCList []*cmv1.CloudVPC, err error) { 12 | 13 | cloudProviderData, err := cmv1.NewCloudProviderData(). 14 | AWS(cmv1.NewAWS().AccessKeyID(ccs.AWS.AccessKeyID).SecretAccessKey(ccs.AWS.SecretAccessKey)). 15 | Region(cmv1.NewCloudRegion().ID(region)). 16 | Build() 17 | if err != nil { 18 | return nil, fmt.Errorf("Failed to build AWS cloud provider data: %v", err) 19 | } 20 | 21 | response, err := client.AWSInquiries().Vpcs().Search(). 22 | Page(1). 23 | Size(-1). 24 | Body(cloudProviderData). 25 | Send() 26 | if err != nil { 27 | return nil, err 28 | } 29 | return response.Items().Slice(), err 30 | } 31 | 32 | func GetGCPVPCs(client *cmv1.Client, ccs cluster.CCS, 33 | gcpAuth cluster.GcpAuthentication, region string) (cloudVPCList []*cmv1.CloudVPC, err error) { 34 | 35 | gcpBuilder := cmv1.NewGCP() 36 | 37 | switch gcpAuth.Type { 38 | case cluster.AuthenticationWif: 39 | gcpAuth := cmv1.NewGcpAuthentication(). 40 | Kind(cmv1.WifConfigKind). 41 | Id(gcpAuth.Id) 42 | gcpBuilder.Authentication(gcpAuth) 43 | case cluster.AuthenticationKey: 44 | gcpBuilder.ProjectID(ccs.GCP.ProjectID). 45 | ClientEmail(ccs.GCP.ClientEmail). 46 | Type(ccs.GCP.Type). 47 | PrivateKey(ccs.GCP.PrivateKey). 48 | PrivateKeyID(ccs.GCP.PrivateKeyID). 49 | AuthProviderX509CertURL(ccs.GCP.AuthProviderX509CertURL). 50 | AuthURI(ccs.GCP.AuthURI).TokenURI(ccs.GCP.TokenURI). 51 | ClientX509CertURL(ccs.GCP.ClientX509CertURL). 52 | ClientID(ccs.GCP.ClientID).TokenURI(ccs.GCP.TokenURI) 53 | default: 54 | return nil, fmt.Errorf("Failed to build GCP provider data, unexpected GCP authentication method %q", gcpAuth.Type) 55 | } 56 | 57 | cloudProviderData, err := cmv1.NewCloudProviderData(). 58 | GCP(gcpBuilder). 59 | Region(cmv1.NewCloudRegion().ID(region)). 60 | Build() 61 | if err != nil { 62 | return nil, fmt.Errorf("Failed to build GCP provider data: %v", err) 63 | } 64 | 65 | response, err := client.GCPInquiries().Vpcs().Search(). 66 | Page(1). 67 | Size(-1). 68 | Body(cloudProviderData). 69 | Send() 70 | if err != nil { 71 | return nil, err 72 | } 73 | return response.Items().Slice(), err 74 | } 75 | 76 | func GetAWSSubnetworks(client *cmv1.Client, ccs cluster.CCS, 77 | region string) (subnetworkList []*cmv1.Subnetwork, err error) { 78 | cloudVPCs, err := getAWSVPCs(client, ccs, region) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | for _, vpc := range cloudVPCs { 84 | subnetworkList = append(subnetworkList, vpc.AWSSubnets()...) 85 | } 86 | return subnetworkList, nil 87 | } 88 | 89 | func GetGCPSubnetList(client *cmv1.Client, provider string, ccs cluster.CCS, 90 | gcpAuth cluster.GcpAuthentication, region string) (subnetList []string, err error) { 91 | if ccs.Enabled && provider == "gcp" { 92 | 93 | cloudVPCs, err := GetGCPVPCs(client, ccs, gcpAuth, region) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | for _, vpc := range cloudVPCs { 99 | subnetList = append(subnetList, vpc.Subnets()...) 100 | } 101 | } 102 | return 103 | } 104 | -------------------------------------------------------------------------------- /pkg/dump/dump.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package dump contains functions used to dump JSON documents to the output of the tool. 18 | package dump 19 | 20 | import ( 21 | "encoding/json" 22 | "io" 23 | "runtime" 24 | 25 | "github.com/nwidger/jsoncolor" 26 | "github.com/openshift-online/ocm-cli/pkg/output" 27 | "gitlab.com/c0b/go-ordered-json" 28 | ) 29 | 30 | // Pretty dumps the given data to the given stream so that it looks pretty. If the data is a valid 31 | // JSON document then it will be indented before printing it. If the stream is a terminal then the 32 | // output will also use colors. 33 | func Pretty(stream io.Writer, body []byte) error { 34 | if len(body) == 0 { 35 | return nil 36 | } 37 | data := ordered.NewOrderedMap() 38 | err := json.Unmarshal(body, data) 39 | if err != nil { 40 | return dumpBytes(stream, body) 41 | } 42 | if output.IsTerminal(stream) && !isWindows() { 43 | return dumpColor(stream, data) 44 | } 45 | return dumpMonochrome(stream, data) 46 | } 47 | 48 | func dumpColor(stream io.Writer, data interface{}) error { 49 | encoder := jsoncolor.NewEncoder(stream) 50 | encoder.SetEscapeHTML(false) 51 | encoder.SetIndent("", " ") 52 | return encoder.Encode(data) 53 | } 54 | 55 | func dumpMonochrome(stream io.Writer, data interface{}) error { 56 | encoder := json.NewEncoder(stream) 57 | encoder.SetIndent("", " ") 58 | return encoder.Encode(data) 59 | } 60 | 61 | // Single functions exactly the same as Pretty except it generates a single line without indentation 62 | // or any other white space. 63 | func Single(stream io.Writer, body []byte) error { 64 | if len(body) == 0 { 65 | return nil 66 | } 67 | data := ordered.NewOrderedMap() 68 | err := json.Unmarshal(body, data) 69 | if err != nil { 70 | return dumpBytes(stream, body) 71 | } 72 | if output.IsTerminal(stream) && !isWindows() { 73 | return dumpColorSingleLine(stream, data) 74 | } 75 | return dumpMonochromeSingleLine(stream, data) 76 | } 77 | 78 | func dumpColorSingleLine(stream io.Writer, data interface{}) error { 79 | encoder := jsoncolor.NewEncoder(stream) 80 | err := encoder.Encode(data) 81 | if err != nil { 82 | return err 83 | } 84 | _, err = stream.Write([]byte("\n")) 85 | return err 86 | } 87 | 88 | func dumpMonochromeSingleLine(stream io.Writer, data interface{}) error { 89 | encoder := json.NewEncoder(stream) 90 | return encoder.Encode(data) 91 | } 92 | 93 | func dumpBytes(stream io.Writer, data []byte) error { 94 | _, err := stream.Write(data) 95 | if err != nil { 96 | return err 97 | } 98 | _, err = stream.Write([]byte("\n")) 99 | return err 100 | } 101 | 102 | // isWindows checks if the operating system is Windows. 103 | func isWindows() bool { 104 | return runtime.GOOS == "windows" 105 | } 106 | -------------------------------------------------------------------------------- /cmd/ocm/delete/machinepool/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package machinepool 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/spf13/cobra" 20 | 21 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 22 | "github.com/openshift-online/ocm-cli/pkg/ocm" 23 | ) 24 | 25 | var args struct { 26 | clusterKey string 27 | } 28 | 29 | var Cmd = &cobra.Command{ 30 | Use: "machinepool --cluster={NAME|ID|EXTERNAL_ID} [flags] MACHINE_POOL_ID", 31 | Aliases: []string{"machine-pool", "machinepools", "machine-pools"}, 32 | Short: "Delete cluster machine pool", 33 | Long: "Delete the additional machine pool of a cluster.", 34 | Example: ` # Delete machine pool with ID mp-1 from a cluster named 'mycluster' 35 | ocm delete machinepool --cluster=mycluster mp-1`, 36 | RunE: run, 37 | } 38 | 39 | func init() { 40 | flags := Cmd.Flags() 41 | 42 | flags.StringVarP( 43 | &args.clusterKey, 44 | "cluster", 45 | "c", 46 | "", 47 | "Name or ID or external_id of the cluster to delete the machine pool from (required).", 48 | ) 49 | //nolint:gosec 50 | Cmd.MarkFlagRequired("cluster") 51 | } 52 | 53 | func run(cmd *cobra.Command, argv []string) error { 54 | 55 | // Check command line arguments: 56 | if len(argv) != 1 { 57 | return fmt.Errorf( 58 | "Expected exactly one command line parameters containing the ID " + 59 | "of the machine pool.", 60 | ) 61 | } 62 | 63 | machinePoolID := argv[0] 64 | 65 | // Check that the cluster key (name, identifier or external identifier) given by the user 66 | // is reasonably safe so that there is no risk of SQL injection: 67 | clusterKey := args.clusterKey 68 | if !c.IsValidClusterKey(clusterKey) { 69 | return fmt.Errorf( 70 | "Cluster name, identifier or external identifier '%s' isn't valid: it "+ 71 | "must contain only letters, digits, dashes and underscores", 72 | clusterKey, 73 | ) 74 | } 75 | // Create the client for the OCM API: 76 | connection, err := ocm.NewConnection().Build() 77 | if err != nil { 78 | return fmt.Errorf("Failed to create OCM connection: %v", err) 79 | } 80 | defer connection.Close() 81 | 82 | // Get the client for the cluster management api 83 | clusterCollection := connection.ClustersMgmt().V1().Clusters() 84 | 85 | cluster, err := c.GetCluster(connection, clusterKey) 86 | if err != nil { 87 | return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err) 88 | } 89 | 90 | _, err = clusterCollection. 91 | Cluster(cluster.ID()). 92 | MachinePools(). 93 | MachinePool(machinePoolID). 94 | Delete(). 95 | Send() 96 | if err != nil { 97 | return fmt.Errorf("Failed to delete machine pool '%s' on cluster '%s'", machinePoolID, clusterKey) 98 | } 99 | 100 | fmt.Printf("Deleted machine pool '%s' on cluster '%s'\n", machinePoolID, clusterKey) 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /cmd/ocm/delete/upgradepolicy/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package upgradepolicy 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/spf13/cobra" 20 | 21 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 22 | "github.com/openshift-online/ocm-cli/pkg/ocm" 23 | ) 24 | 25 | var args struct { 26 | clusterKey string 27 | } 28 | 29 | var Cmd = &cobra.Command{ 30 | Use: "upgradepolicy --cluster={NAME|ID|EXTERNAL_ID} [flags] UPGRADE_POLICY_ID", 31 | Aliases: []string{"upgrade-policy", "upgradepolicies", "upgrade-policies"}, 32 | Short: "Delete cluster upgrade policy", 33 | Long: "Delete the upgrade policy of a cluster.", 34 | Example: ` # Delete upgrade policy from a cluster named 'mycluster' 35 | ocm delete upgradepolicy --cluster=mycluster `, 36 | RunE: run, 37 | } 38 | 39 | func init() { 40 | flags := Cmd.Flags() 41 | 42 | flags.StringVarP( 43 | &args.clusterKey, 44 | "cluster", 45 | "c", 46 | "", 47 | "Name or ID or external_id of the cluster to delete the upgrade policy from (required).", 48 | ) 49 | //nolint:gosec 50 | Cmd.MarkFlagRequired("cluster") 51 | } 52 | 53 | func run(cmd *cobra.Command, argv []string) error { 54 | 55 | // Check command line arguments: 56 | if len(argv) != 1 { 57 | return fmt.Errorf( 58 | "Expected exactly one command line parameters containing the ID " + 59 | "of the upgrade policy.", 60 | ) 61 | } 62 | 63 | upgradePolicyID := argv[0] 64 | 65 | // Check that the cluster key (name, identifier or external identifier) given by the user 66 | // is reasonably safe so that there is no risk of SQL injection: 67 | clusterKey := args.clusterKey 68 | if !c.IsValidClusterKey(clusterKey) { 69 | return fmt.Errorf( 70 | "Cluster name, identifier or external identifier '%s' isn't valid: it "+ 71 | "must contain only letters, digits, dashes and underscores", 72 | clusterKey, 73 | ) 74 | } 75 | // Create the client for the OCM API: 76 | connection, err := ocm.NewConnection().Build() 77 | if err != nil { 78 | return fmt.Errorf("Failed to create OCM connection: %v", err) 79 | } 80 | defer connection.Close() 81 | 82 | // Get the client for the cluster management api 83 | clusterCollection := connection.ClustersMgmt().V1().Clusters() 84 | 85 | cluster, err := c.GetCluster(connection, clusterKey) 86 | if err != nil { 87 | return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err) 88 | } 89 | 90 | _, err = clusterCollection. 91 | Cluster(cluster.ID()). 92 | UpgradePolicies(). 93 | UpgradePolicy(upgradePolicyID). 94 | Delete(). 95 | Send() 96 | if err != nil { 97 | return fmt.Errorf("Failed to delete upgrade policy '%s' on cluster '%s'", upgradePolicyID, clusterKey) 98 | } 99 | 100 | fmt.Printf("Deleted upgrade policy '%s' on cluster '%s'\n", upgradePolicyID, clusterKey) 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /cmd/ocm/patch/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package patch 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/arguments" 26 | "github.com/openshift-online/ocm-cli/pkg/config" 27 | "github.com/openshift-online/ocm-cli/pkg/dump" 28 | "github.com/openshift-online/ocm-cli/pkg/ocm" 29 | "github.com/openshift-online/ocm-cli/pkg/urls" 30 | ) 31 | 32 | var args struct { 33 | parameter []string 34 | header []string 35 | body string 36 | } 37 | 38 | var Cmd = &cobra.Command{ 39 | Use: "patch PATH", 40 | Short: "Send a PATCH request", 41 | Long: "Send a PATCH request to the given path.", 42 | RunE: run, 43 | ValidArgs: urls.Resources(), 44 | } 45 | 46 | func init() { 47 | fs := Cmd.Flags() 48 | arguments.AddParameterFlag(fs, &args.parameter) 49 | arguments.AddHeaderFlag(fs, &args.header) 50 | arguments.AddBodyFlag(fs, &args.body) 51 | } 52 | 53 | func run(cmd *cobra.Command, argv []string) error { 54 | path, err := urls.Expand(argv) 55 | if err != nil { 56 | return fmt.Errorf("Could not create URI: %v", err) 57 | } 58 | 59 | // Create the client for the OCM API: 60 | connection, err := ocm.NewConnection().Build() 61 | if err != nil { 62 | return fmt.Errorf("Failed to create OCM connection: %v", err) 63 | } 64 | defer connection.Close() 65 | 66 | // Create and populate the request: 67 | request := connection.Patch() 68 | err = arguments.ApplyPathArg(request, path) 69 | if err != nil { 70 | fmt.Fprintf(os.Stderr, "Can't parse path '%s': %v\n", path, err) 71 | os.Exit(1) 72 | } 73 | arguments.ApplyParameterFlag(request, args.parameter) 74 | arguments.ApplyHeaderFlag(request, args.header) 75 | err = arguments.ApplyBodyFlag(request, args.body) 76 | if err != nil { 77 | return fmt.Errorf("Can't read body: %v", err) 78 | } 79 | 80 | // Send the request: 81 | response, err := request.Send() 82 | if err != nil { 83 | return fmt.Errorf("Can't send request: %v", err) 84 | } 85 | status := response.Status() 86 | body := response.Bytes() 87 | if status < 400 { 88 | err = dump.Pretty(os.Stdout, body) 89 | } else { 90 | err = dump.Pretty(os.Stderr, body) 91 | } 92 | if err != nil { 93 | return fmt.Errorf("Can't print body: %v", err) 94 | } 95 | // Load the configuration file: 96 | cfg, err := config.Load() 97 | if err != nil { 98 | return fmt.Errorf("Can't load config file: %v", err) 99 | } 100 | // Save the configuration: 101 | cfg.AccessToken, cfg.RefreshToken, err = connection.Tokens() 102 | if err != nil { 103 | return fmt.Errorf("Can't get tokens: %v", err) 104 | } 105 | err = config.Save(cfg) 106 | if err != nil { 107 | return fmt.Errorf("Can't save config file: %v", err) 108 | } 109 | 110 | // Bye: 111 | if status >= 400 { 112 | os.Exit(1) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /cmd/ocm/post/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package post 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/arguments" 26 | "github.com/openshift-online/ocm-cli/pkg/config" 27 | "github.com/openshift-online/ocm-cli/pkg/dump" 28 | "github.com/openshift-online/ocm-cli/pkg/ocm" 29 | "github.com/openshift-online/ocm-cli/pkg/urls" 30 | ) 31 | 32 | var args struct { 33 | parameter []string 34 | header []string 35 | body string 36 | } 37 | 38 | var Cmd = &cobra.Command{ 39 | Use: "post PATH", 40 | Short: "Send a POST request", 41 | Long: "Send a POST request to the given path.", 42 | RunE: run, 43 | ValidArgs: urls.Resources(), 44 | } 45 | 46 | func init() { 47 | fs := Cmd.Flags() 48 | arguments.AddParameterFlag(fs, &args.parameter) 49 | arguments.AddHeaderFlag(fs, &args.header) 50 | arguments.AddBodyFlag(fs, &args.body) 51 | } 52 | 53 | func run(cmd *cobra.Command, argv []string) error { 54 | path, err := urls.Expand(argv) 55 | if err != nil { 56 | return fmt.Errorf("Could not create URI: %v", err) 57 | } 58 | 59 | // Create the client for the OCM API: 60 | connection, err := ocm.NewConnection().Build() 61 | if err != nil { 62 | return fmt.Errorf("Failed to create OCM connection: %v", err) 63 | } 64 | defer connection.Close() 65 | 66 | // Create and populate the request: 67 | request := connection.Post() 68 | err = arguments.ApplyPathArg(request, path) 69 | if err != nil { 70 | fmt.Fprintf(os.Stderr, "Can't parse path '%s': %v\n", path, err) 71 | os.Exit(1) 72 | } 73 | arguments.ApplyParameterFlag(request, args.parameter) 74 | arguments.ApplyHeaderFlag(request, args.header) 75 | err = arguments.ApplyBodyFlag(request, args.body) 76 | if err != nil { 77 | return fmt.Errorf("Can't read body: %v", err) 78 | } 79 | 80 | // Send the request: 81 | response, err := request.Send() 82 | if err != nil { 83 | return fmt.Errorf("Can't send request: %v", err) 84 | } 85 | status := response.Status() 86 | body := response.Bytes() 87 | if status < 400 { 88 | err = dump.Pretty(os.Stdout, body) 89 | } else { 90 | err = dump.Pretty(os.Stderr, body) 91 | } 92 | if err != nil { 93 | return fmt.Errorf("Can't print body: %v", err) 94 | } 95 | 96 | // Load the configuration file: 97 | cfg, err := config.Load() 98 | if err != nil { 99 | return fmt.Errorf("Can't load config file: %v", err) 100 | } 101 | // Save the configuration: 102 | cfg.AccessToken, cfg.RefreshToken, err = connection.Tokens() 103 | if err != nil { 104 | return fmt.Errorf("Can't get tokens: %v", err) 105 | } 106 | err = config.Save(cfg) 107 | if err != nil { 108 | return fmt.Errorf("Can't save config file: %v", err) 109 | } 110 | 111 | // Bye: 112 | if status >= 400 { 113 | os.Exit(1) 114 | } 115 | 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /cmd/ocm/list/user/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package user 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "text/tabwriter" 23 | 24 | "github.com/spf13/cobra" 25 | 26 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 27 | "github.com/openshift-online/ocm-cli/pkg/ocm" 28 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 29 | ) 30 | 31 | var args struct { 32 | clusterKey string 33 | } 34 | 35 | // Cmd Constant: 36 | var Cmd = &cobra.Command{ 37 | Use: "users --cluster={NAME|ID|EXTERNAL_ID}", 38 | Aliases: []string{"user"}, 39 | Short: "List cluster users", 40 | Long: "List administrative cluster users", 41 | Args: cobra.NoArgs, 42 | RunE: run, 43 | } 44 | 45 | func init() { 46 | fs := Cmd.Flags() 47 | fs.StringVarP( 48 | &args.clusterKey, 49 | "cluster", 50 | "c", 51 | "", 52 | "Name or ID or external_id of the cluster to add the IdP to (required).", 53 | ) 54 | //nolint:gosec 55 | Cmd.MarkFlagRequired("cluster") 56 | } 57 | 58 | func run(cmd *cobra.Command, argv []string) error { 59 | // Check that the cluster key (name, identifier or external identifier) given by the user 60 | // is reasonably safe so that there is no risk of SQL injection: 61 | clusterKey := args.clusterKey 62 | if !c.IsValidClusterKey(clusterKey) { 63 | return fmt.Errorf( 64 | "Cluster name, identifier or external identifier '%s' isn't valid: it "+ 65 | "must contain only letters, digits, dashes and underscores", 66 | clusterKey, 67 | ) 68 | } 69 | // Create the client for the OCM API: 70 | connection, err := ocm.NewConnection().Build() 71 | if err != nil { 72 | return fmt.Errorf("Failed to create OCM connection: %v", err) 73 | } 74 | defer connection.Close() 75 | 76 | clusterCollection := connection.ClustersMgmt().V1().Clusters() 77 | 78 | cluster, err := c.GetCluster(connection, clusterKey) 79 | if err != nil { 80 | return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err) 81 | } 82 | 83 | if cluster.State() != cmv1.ClusterStateReady { 84 | return fmt.Errorf("Cluster '%s' is not yet ready", clusterKey) 85 | } 86 | 87 | groups, err := c.GetGroups(clusterCollection, cluster.ID()) 88 | if err != nil { 89 | return fmt.Errorf("Failed to get users for cluster '%s': %v", clusterKey, err) 90 | } 91 | 92 | // Create the writer that will be used to print the tabulated results: 93 | writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 94 | fmt.Fprintf(writer, "GROUP\t\tUSER\n") 95 | 96 | for _, group := range groups { 97 | groupName := group.ID() 98 | for _, user := range group.Users().Slice() { 99 | fmt.Fprintf(writer, "%s\t\t%s\n", groupName, user.ID()) 100 | } 101 | } 102 | 103 | err = writer.Flush() 104 | if err != nil { 105 | return fmt.Errorf("Failed to flush user output for cluster '%s': %v", clusterKey, err) 106 | } 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /pkg/ingress/describe.go: -------------------------------------------------------------------------------- 1 | package ingress 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/openshift-online/ocm-cli/pkg/utils" 10 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 11 | ) 12 | 13 | func PrintIngressDescription(ingress *cmv1.Ingress, cluster *cmv1.Cluster) error { 14 | entries := generateEntriesOutput(cluster, ingress) 15 | ingressOutput := "" 16 | keys := utils.MapKeys(entries) 17 | sort.Strings(keys) 18 | minWidth := getMinWidth(keys) 19 | for _, key := range keys { 20 | ingressOutput += fmt.Sprintf("%s: %s\n", key, strings.Repeat(" ", minWidth-len(key))+entries[key]) 21 | } 22 | fmt.Print(ingressOutput) 23 | return nil 24 | } 25 | 26 | // Min width is defined as the length of the longest string 27 | func getMinWidth(keys []string) int { 28 | minWidth := 0 29 | for _, key := range keys { 30 | if len(key) > minWidth { 31 | minWidth = len(key) 32 | } 33 | } 34 | return minWidth 35 | } 36 | 37 | func generateEntriesOutput(cluster *cmv1.Cluster, ingress *cmv1.Ingress) map[string]string { 38 | private := false 39 | if ingress.Listening() == cmv1.ListeningMethodInternal { 40 | private = true 41 | } 42 | entries := map[string]string{ 43 | "ID": ingress.ID(), 44 | "Cluster ID": cluster.ID(), 45 | "Default": strconv.FormatBool(ingress.Default()), 46 | "Private": strconv.FormatBool(private), 47 | "LB-Type": string(ingress.LoadBalancerType()), 48 | } 49 | // These are only available for ingress v2 50 | wildcardPolicy := string(ingress.RouteWildcardPolicy()) 51 | if wildcardPolicy != "" { 52 | entries["Wildcard Policy"] = string(ingress.RouteWildcardPolicy()) 53 | } 54 | namespaceOwnershipPolicy := string(ingress.RouteNamespaceOwnershipPolicy()) 55 | if namespaceOwnershipPolicy != "" { 56 | entries["Namespace Ownership Policy"] = namespaceOwnershipPolicy 57 | } 58 | routeSelectors := "" 59 | if len(ingress.RouteSelectors()) > 0 { 60 | routeSelectors = fmt.Sprintf("%v", ingress.RouteSelectors()) 61 | } 62 | if routeSelectors != "" { 63 | entries["Route Selectors"] = routeSelectors 64 | } 65 | excludedNamespaces := utils.SliceToSortedString(ingress.ExcludedNamespaces()) 66 | if excludedNamespaces != "" { 67 | entries["Excluded Namespaces"] = excludedNamespaces 68 | } 69 | componentRoutes := "" 70 | componentKeys := utils.MapKeys(ingress.ComponentRoutes()) 71 | sort.Strings(componentKeys) 72 | for _, component := range componentKeys { 73 | value := ingress.ComponentRoutes()[component] 74 | keys := utils.MapKeys(entries) 75 | minWidth := getMinWidth(keys) 76 | depth := 4 77 | componentRouteEntries := map[string]string{ 78 | "Hostname": value.Hostname(), 79 | "TLS Secret Ref": value.TlsSecretRef(), 80 | } 81 | componentRoutes += fmt.Sprintf("%s: \n", strings.Repeat(" ", depth)+component) 82 | depth *= 2 83 | paramKeys := utils.MapKeys(componentRouteEntries) 84 | sort.Strings(paramKeys) 85 | for _, param := range paramKeys { 86 | componentRoutes += fmt.Sprintf( 87 | "%s: %s\n", 88 | strings.Repeat(" ", depth)+param, 89 | strings.Repeat(" ", minWidth-len(param)-depth)+componentRouteEntries[param], 90 | ) 91 | } 92 | } 93 | if componentRoutes != "" { 94 | componentRoutes = fmt.Sprintf("\n%s", componentRoutes) 95 | //remove extra \n at the end 96 | componentRoutes = componentRoutes[:len(componentRoutes)-1] 97 | entries["Component Routes"] = componentRoutes 98 | } 99 | return entries 100 | } 101 | -------------------------------------------------------------------------------- /tests/whoami_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package tests 18 | 19 | import ( 20 | "context" 21 | "net/http" 22 | 23 | "github.com/golang-jwt/jwt/v4" 24 | 25 | . "github.com/onsi/ginkgo/v2" // nolint 26 | . "github.com/onsi/gomega" // nolint 27 | . "github.com/onsi/gomega/ghttp" // nolint 28 | . "github.com/openshift-online/ocm-sdk-go/testing" // nolint 29 | ) 30 | 31 | var _ = Describe("Whoami", func() { 32 | var ctx context.Context 33 | 34 | BeforeEach(func() { 35 | // Create a context: 36 | ctx = context.Background() 37 | }) 38 | 39 | When("Offline user session not found", func() { 40 | var ssoServer *Server 41 | var apiServer *Server 42 | 43 | BeforeEach(func() { 44 | // Create the servers: 45 | ssoServer = MakeTCPServer() 46 | apiServer = MakeTCPServer() 47 | 48 | // Prepare the server: 49 | ssoServer.AppendHandlers( 50 | RespondWithJSON( 51 | http.StatusBadRequest, 52 | `{ 53 | "error": "invalid_grant", 54 | "error_description": "Offline user session not found" 55 | }`, 56 | ), 57 | ) 58 | }) 59 | 60 | AfterEach(func() { 61 | // Close the servers: 62 | ssoServer.Close() 63 | apiServer.Close() 64 | }) 65 | 66 | It("Writes user friendly message", func() { 67 | // Create a valid offline token: 68 | tokenObject := MakeTokenObject( 69 | jwt.MapClaims{ 70 | "typ": "Offline", 71 | "exp": nil, 72 | }, 73 | ) 74 | tokenString := tokenObject.Raw 75 | 76 | // Prepare the SSO server so that it will respond saying that the offline 77 | // session doesn't exist. This is something that happens some times in 78 | // `sso.redhat.com` when the servers are restarted. 79 | ssoServer.AppendHandlers( 80 | RespondWithJSON( 81 | http.StatusBadRequest, `{ 82 | "error_code": "invalid_grant", 83 | "error_message": "Offline user session not found" 84 | }`, 85 | ), 86 | ) 87 | 88 | // Run the command: 89 | whoamiResult := NewCommand(). 90 | ConfigString( 91 | `{ 92 | "client_id": "cloud-services", 93 | "insecure": true, 94 | "refresh_token": "{{ .Token }}", 95 | "scopes": [ 96 | "openid" 97 | ], 98 | "token_url": "{{ .TokenURL }}", 99 | "url": "{{ .URL }}" 100 | }`, 101 | "Token", tokenString, 102 | "TokenURL", ssoServer.URL(), 103 | "URL", apiServer.URL(), 104 | ). 105 | Args("whoami"). 106 | Run(ctx) 107 | Expect(whoamiResult.ExitCode()).ToNot(BeZero()) 108 | Expect(whoamiResult.ErrString()).To(Equal( 109 | "Offline access token is no longer valid. Go to " + 110 | "https://console.redhat.com/openshift/token to get a new " + 111 | "one and then use the 'ocm login --token=...' command to " + 112 | "log in with that new token.\n", 113 | )) 114 | }) 115 | }) 116 | }) 117 | -------------------------------------------------------------------------------- /cmd/ocm/create/idp/google.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package idp 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "net/url" 20 | 21 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 22 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 23 | 24 | "github.com/AlecAivazis/survey/v2" 25 | ) 26 | 27 | func buildGoogleIdp(cluster *cmv1.Cluster, idpName string) (idpBuilder cmv1.IdentityProviderBuilder, err error) { 28 | clientID := args.clientID 29 | clientSecret := args.clientSecret 30 | hostedDomain := args.googleHostedDomain 31 | 32 | isInteractive := clientID == "" || 33 | clientSecret == "" || 34 | (args.mappingMethod != "lookup" && hostedDomain == "") 35 | 36 | if isInteractive { 37 | fmt.Println("To use Google as an identity provider, you must first register the application:") 38 | instructionsURL := "https://console.developers.google.com/projectcreate" 39 | fmt.Println("* Open the following URL:", instructionsURL) 40 | fmt.Println("* Follow the instructions to register your application") 41 | 42 | oauthURL := c.GetClusterOauthURL(cluster) 43 | 44 | fmt.Println("* When creating the OAuth client ID, use the following URL for the Authorized redirect URI: ", 45 | oauthURL+"/oauth2callback/"+idpName) 46 | 47 | if clientID == "" { 48 | prompt := &survey.Input{ 49 | Message: "Copy the Client ID provided by Google:", 50 | } 51 | err = survey.AskOne(prompt, &clientID) 52 | if err != nil { 53 | return idpBuilder, errors.New("Expected a Google application Client ID") 54 | } 55 | } 56 | 57 | if clientSecret == "" { 58 | prompt := &survey.Input{ 59 | Message: "Copy the Client Secret provided by Google:", 60 | } 61 | err = survey.AskOne(prompt, &clientSecret) 62 | if err != nil { 63 | return idpBuilder, errors.New("Expected a Google application Client Secret") 64 | } 65 | } 66 | 67 | if args.mappingMethod != "lookup" && hostedDomain == "" { 68 | prompt := &survey.Input{ 69 | Message: "Hosted Domain to restrict users:", 70 | } 71 | err = survey.AskOne(prompt, &hostedDomain) 72 | if err != nil { 73 | return idpBuilder, errors.New("Expected a valid Hosted Domain") 74 | } 75 | } 76 | } 77 | 78 | // Create Google IDP 79 | googleIDP := cmv1.NewGoogleIdentityProvider(). 80 | ClientID(clientID). 81 | ClientSecret(clientSecret) 82 | 83 | if hostedDomain != "" { 84 | hostedDomainParsed, err := url.ParseRequestURI(hostedDomain) 85 | if err != nil { 86 | return idpBuilder, fmt.Errorf("Expected a valid Hosted Domain: %v", err) 87 | } 88 | // Set the hosted domain, if any 89 | googleIDP = googleIDP.HostedDomain(hostedDomainParsed.Hostname()) 90 | } 91 | 92 | // Create new IDP with Google provider 93 | idpBuilder. 94 | Type("GoogleIdentityProvider"). // FIXME: ocm-api-model has the wrong enum values 95 | Name(idpName). 96 | MappingMethod(cmv1.IdentityProviderMappingMethod(args.mappingMethod)). 97 | Google(googleIDP) 98 | 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /cmd/ocm/gcp/helpers.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "regexp" 8 | 9 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | const ( 14 | ModeAuto = "auto" 15 | ModeManual = "manual" 16 | ) 17 | 18 | var Modes = []string{ModeAuto, ModeManual} 19 | 20 | // Checks for WIF config name or id in input 21 | func wifKeyArgCheck(args []string) error { 22 | if len(args) != 1 || args[0] == "" { 23 | return fmt.Errorf("expected exactly one command line parameters containing the name " + 24 | "or ID of the WIF config") 25 | } 26 | return nil 27 | } 28 | 29 | // Extracts WIF config name or id from input 30 | func wifKeyFromArgs(args []string) (string, error) { 31 | if err := wifKeyArgCheck(args); err != nil { 32 | return "", err 33 | } 34 | return args[0], nil 35 | } 36 | 37 | // findWifConfig finds the WIF configuration by ID or name 38 | func findWifConfig(client *cmv1.Client, key string) (*cmv1.WifConfig, error) { 39 | collection := client.GCP().WifConfigs() 40 | page := 1 41 | size := 1 42 | query := fmt.Sprintf( 43 | "id = '%s' or display_name = '%s'", 44 | key, key, 45 | ) 46 | 47 | response, err := collection.List().Search(query).Page(page).Size(size).Send() 48 | if err != nil { 49 | return nil, err 50 | } 51 | if response.Total() == 0 { 52 | return nil, fmt.Errorf("WIF configuration with identifier or name '%s' not found", key) 53 | } 54 | if response.Total() > 1 { 55 | return nil, fmt.Errorf("there are %d WIF configurations found with identifier or name '%s'", response.Total(), key) 56 | } 57 | return response.Items().Slice()[0], nil 58 | } 59 | 60 | // getPathFromFlag validates the filepath 61 | func getPathFromFlag(targetDir string) (string, error) { 62 | if targetDir == "" { 63 | pwd, err := os.Getwd() 64 | if err != nil { 65 | return "", errors.Wrapf(err, "failed to get current directory") 66 | } 67 | 68 | return pwd, nil 69 | } 70 | 71 | fPath, err := filepath.Abs(targetDir) 72 | if err != nil { 73 | return "", errors.Wrapf(err, "failed to resolve full path") 74 | } 75 | 76 | sResult, err := os.Stat(fPath) 77 | if os.IsNotExist(err) { 78 | return "", fmt.Errorf("directory %s does not exist", fPath) 79 | } 80 | if !sResult.IsDir() { 81 | return "", fmt.Errorf("file %s exists and is not a directory", fPath) 82 | } 83 | 84 | return targetDir, nil 85 | } 86 | 87 | // converts openshift version of form X.Y to template ID of form vX.Y 88 | func versionToTemplateID(version string) string { 89 | // Check if version is a semver in the form X.Y 90 | re := regexp.MustCompile(`^\d+\.\d+$`) 91 | if re.MatchString(version) { 92 | return "v" + version 93 | } 94 | 95 | // Otherwise, return the version as is 96 | return version 97 | } 98 | 99 | // getFederatedProjectNumber returns the federated project number if it exists, otherwise returns the project number 100 | func getFederatedProjectNumber(wifConfig *cmv1.WifConfig) string { 101 | if wifConfig.Gcp().FederatedProjectNumber() != "" && wifConfig.Gcp().FederatedProjectNumber() != "0" { 102 | return wifConfig.Gcp().FederatedProjectNumber() 103 | } 104 | return wifConfig.Gcp().ProjectNumber() 105 | } 106 | 107 | // getFederatedProjectId returns the federated project id if it exists, otherwise returns the project id 108 | func getFederatedProjectId(wifConfig *cmv1.WifConfig) string { 109 | if wifConfig.Gcp().FederatedProjectId() != "" { 110 | return wifConfig.Gcp().FederatedProjectId() 111 | } 112 | return wifConfig.Gcp().ProjectId() 113 | } 114 | -------------------------------------------------------------------------------- /pkg/config/token.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "encoding/base64" 21 | "encoding/json" 22 | "fmt" 23 | "strings" 24 | "time" 25 | 26 | "github.com/golang-jwt/jwt/v4" 27 | ) 28 | 29 | type JWETokenHeader struct { 30 | Algorithm string `json:"alg"` 31 | Encryption string `json:"enc"` 32 | ContentType string `json:"cty,omitempty"` 33 | } 34 | 35 | func IsEncryptedToken(textToken string) bool { 36 | parts := strings.Split(textToken, ".") 37 | if len(parts) != 5 { 38 | return false 39 | } 40 | encoded := fmt.Sprintf("%s==", parts[0]) 41 | decoded, err := base64.StdEncoding.DecodeString(encoded) 42 | if err != nil || len(decoded) == 0 { 43 | return false 44 | } 45 | header := new(JWETokenHeader) 46 | err = json.Unmarshal(decoded, header) 47 | if err != nil { 48 | return false 49 | } 50 | if header.Encryption != "" && header.ContentType == "JWT" { 51 | return true 52 | } 53 | return false 54 | } 55 | 56 | func ParseToken(textToken string) (token *jwt.Token, err error) { 57 | parser := new(jwt.Parser) 58 | token, _, err = parser.ParseUnverified(textToken, jwt.MapClaims{}) 59 | if err != nil { 60 | return 61 | } 62 | return token, nil 63 | } 64 | 65 | // tokenUsable checks if the given token is usable. 66 | func tokenUsable(token string, margin time.Duration) (usable bool, err error) { 67 | parsed, err := ParseToken(token) 68 | if err != nil { 69 | return 70 | } 71 | expires, left, err := tokenExpiration(parsed) 72 | if err != nil { 73 | return 74 | } 75 | if !expires { 76 | usable = true 77 | return 78 | } 79 | if left >= margin { 80 | usable = true 81 | return 82 | } 83 | return 84 | } 85 | 86 | // tokenExpiration determines if the given token expires, and the time that remains till it expires. 87 | func tokenExpiration(token *jwt.Token) (expires bool, left time.Duration, err error) { 88 | claims, ok := token.Claims.(jwt.MapClaims) 89 | if !ok { 90 | err = fmt.Errorf("expected map claims bug got %T", claims) 91 | return 92 | } 93 | claim, ok := claims["exp"] 94 | if !ok { 95 | return 96 | } 97 | exp, ok := claim.(float64) 98 | if !ok { 99 | err = fmt.Errorf("expected floating point 'exp' but got %T", claim) 100 | return 101 | } 102 | if exp == 0 { 103 | return 104 | } 105 | expires = true 106 | left = time.Until(time.Unix(int64(exp), 0)) 107 | return 108 | } 109 | 110 | // TokenType extracts the value of the `typ` claim. It returns the value as a string, or the empty 111 | // string if there is no such claim. 112 | func TokenType(token *jwt.Token) (typ string, err error) { 113 | claims, ok := token.Claims.(jwt.MapClaims) 114 | if !ok { 115 | err = fmt.Errorf("expected map claims but got %T", claims) 116 | return 117 | } 118 | claim, ok := claims["typ"] 119 | if !ok { 120 | return 121 | } 122 | value, ok := claim.(string) 123 | if !ok { 124 | err = fmt.Errorf("expected string 'typ' but got %T", claim) 125 | return 126 | } 127 | typ = value 128 | return 129 | } 130 | -------------------------------------------------------------------------------- /cmd/ocm/get/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package get 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/openshift-online/ocm-cli/pkg/arguments" 26 | "github.com/openshift-online/ocm-cli/pkg/config" 27 | "github.com/openshift-online/ocm-cli/pkg/dump" 28 | "github.com/openshift-online/ocm-cli/pkg/ocm" 29 | "github.com/openshift-online/ocm-cli/pkg/urls" 30 | ) 31 | 32 | var args struct { 33 | parameter []string 34 | header []string 35 | single bool 36 | } 37 | 38 | var Cmd = &cobra.Command{ 39 | Use: "get RESOURCE [ID]", 40 | Short: "Send a GET request", 41 | Long: "Send a GET request to the given path.", 42 | RunE: run, 43 | ValidArgs: urls.Resources(), 44 | } 45 | 46 | func init() { 47 | fs := Cmd.Flags() 48 | arguments.AddParameterFlag(fs, &args.parameter) 49 | arguments.AddHeaderFlag(fs, &args.header) 50 | fs.BoolVar( 51 | &args.single, 52 | "single", 53 | false, 54 | "Return the output as a single line.", 55 | ) 56 | } 57 | 58 | func run(cmd *cobra.Command, argv []string) error { 59 | path, err := urls.Expand(argv) 60 | if err != nil { 61 | return fmt.Errorf("Could not create URI: %v", err) 62 | } 63 | 64 | // Load the configuration file: 65 | cfg, err := config.Load() 66 | if err != nil { 67 | return fmt.Errorf("Can't load config file: %v", err) 68 | } 69 | if cfg == nil { 70 | return fmt.Errorf("Not logged in, run the 'login' command") 71 | } 72 | 73 | // Create the client for the OCM API: 74 | connection, err := ocm.NewConnection().Build() 75 | if err != nil { 76 | return err 77 | } 78 | defer connection.Close() 79 | 80 | // Create and populate the request: 81 | request := connection.Get() 82 | err = arguments.ApplyPathArg(request, path) 83 | if err != nil { 84 | fmt.Fprintf(os.Stderr, "Can't parse path '%s': %v\n", path, err) 85 | os.Exit(1) 86 | } 87 | arguments.ApplyParameterFlag(request, args.parameter) 88 | arguments.ApplyHeaderFlag(request, args.header) 89 | 90 | // Send the request: 91 | response, err := request.Send() 92 | if err != nil { 93 | return fmt.Errorf("Can't send request: %v", err) 94 | } 95 | status := response.Status() 96 | body := response.Bytes() 97 | if status < 400 { 98 | if args.single { 99 | err = dump.Single(os.Stdout, body) 100 | } else { 101 | err = dump.Pretty(os.Stdout, body) 102 | } 103 | } else { 104 | if args.single { 105 | err = dump.Single(os.Stderr, body) 106 | } else { 107 | err = dump.Pretty(os.Stderr, body) 108 | } 109 | } 110 | if err != nil { 111 | return fmt.Errorf("Can't print body: %v", err) 112 | } 113 | 114 | // Save the configuration: 115 | cfg.AccessToken, cfg.RefreshToken, err = connection.Tokens() 116 | if err != nil { 117 | return fmt.Errorf("Can't get tokens: %v", err) 118 | } 119 | err = config.Save(cfg) 120 | if err != nil { 121 | return fmt.Errorf("Can't save config file: %v", err) 122 | } 123 | 124 | // Bye: 125 | if status >= 400 { 126 | os.Exit(1) 127 | } 128 | 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /cmd/ocm/list/upgradepolicy/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package upgradepolicy 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "text/tabwriter" 23 | 24 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 25 | "github.com/openshift-online/ocm-cli/pkg/ocm" 26 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 27 | 28 | "github.com/spf13/cobra" 29 | ) 30 | 31 | var args struct { 32 | clusterKey string 33 | } 34 | 35 | var Cmd = &cobra.Command{ 36 | Use: "upgradepolicies --cluster={NAME|ID|EXTERNAL_ID}", 37 | Aliases: []string{"upgrade-policy", "upgrade-policies", "upgradepolicy"}, 38 | Short: "List cluster upgrade policies", 39 | Long: "List upgrade policies for a cluster.", 40 | Example: ` # List all upgrade policies on a cluster named "mycluster" 41 | ocm list upgradepolicies --cluster=mycluster`, 42 | Args: cobra.NoArgs, 43 | RunE: run, 44 | } 45 | 46 | func init() { 47 | flags := Cmd.Flags() 48 | 49 | flags.StringVarP( 50 | &args.clusterKey, 51 | "cluster", 52 | "c", 53 | "", 54 | "Name or ID or external_id of the cluster to list the upgrade policies of (required).", 55 | ) 56 | //nolint:gosec 57 | Cmd.MarkFlagRequired("cluster") 58 | } 59 | 60 | func run(cmd *cobra.Command, argv []string) error { 61 | 62 | // Check that the cluster key (name, identifier or external identifier) given by the user 63 | // is reasonably safe so that there is no risk of SQL injection: 64 | clusterKey := args.clusterKey 65 | if !c.IsValidClusterKey(clusterKey) { 66 | return fmt.Errorf( 67 | "Cluster name, identifier or external identifier '%s' isn't valid: it "+ 68 | "must contain only letters, digits, dashes and underscores", 69 | clusterKey, 70 | ) 71 | } 72 | 73 | // Create the client for the OCM API: 74 | connection, err := ocm.NewConnection().Build() 75 | if err != nil { 76 | return fmt.Errorf("Failed to create OCM connection: %v", err) 77 | } 78 | defer connection.Close() 79 | 80 | // Get the client for the cluster management api 81 | clusterCollection := connection.ClustersMgmt().V1().Clusters() 82 | 83 | cluster, err := c.GetCluster(connection, clusterKey) 84 | if err != nil { 85 | return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err) 86 | } 87 | 88 | if cluster.State() != cmv1.ClusterStateReady { 89 | return fmt.Errorf("Cluster '%s' is not yet ready", clusterKey) 90 | } 91 | 92 | upgradePolicies, err := c.GetUpgradePolicies(clusterCollection, cluster.ID()) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | // Create the writer that will be used to print the tabulated results: 98 | writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 99 | 100 | fmt.Fprintf(writer, "ID\tSCHEDULE TYPE\t\t\tUPGRADE VERSION\t\tNEXT RUN\n") 101 | for _, upgradePolicy := range upgradePolicies { 102 | fmt.Fprintf(writer, "%s\t%s\t\t\t%s\t\t%v\n", 103 | upgradePolicy.ID(), 104 | upgradePolicy.ScheduleType(), 105 | upgradePolicy.Version(), 106 | upgradePolicy.NextRun()) 107 | } 108 | 109 | //nolint:gosec 110 | writer.Flush() 111 | 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /cmd/ocm/create/idp/ldap.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package idp 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "net/url" 20 | "strings" 21 | 22 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 23 | 24 | "github.com/AlecAivazis/survey/v2" 25 | ) 26 | 27 | func buildLdapIdp(_ *cmv1.Cluster, idpName string) (idpBuilder cmv1.IdentityProviderBuilder, err error) { 28 | ldapURL := args.ldapURL 29 | ldapIDs := args.ldapIDs 30 | 31 | isInteractive := ldapURL == "" || ldapIDs == "" 32 | 33 | if isInteractive { 34 | fmt.Println("To use LDAP as an identity provider, you must first register the application:") 35 | instructionsURL := "https://docs.openshift.com/dedicated/osd_install_access_delete_cluster/" + 36 | "config-identity-providers.html#config-ldap-idp_config-identity-providers" 37 | fmt.Println("* Open the following URL:", instructionsURL) 38 | fmt.Println("* Follow the instructions to register your application") 39 | 40 | if ldapURL == "" { 41 | prompt := &survey.Input{ 42 | Message: "URL which specifies the LDAP search parameters to use:", 43 | } 44 | err = survey.AskOne(prompt, &ldapURL) 45 | if err != nil { 46 | return idpBuilder, errors.New("Expected a valid LDAP URL") 47 | } 48 | } 49 | 50 | if ldapIDs == "" { 51 | prompt := &survey.Input{ 52 | Message: "List of attributes whose values should be used as the user ID:", 53 | } 54 | err = survey.AskOne(prompt, &ldapIDs) 55 | if err != nil { 56 | return idpBuilder, errors.New("Expected a valid comma-separated list of attributes") 57 | } 58 | } 59 | } 60 | 61 | parsedLdapURL, err := url.ParseRequestURI(ldapURL) 62 | if err != nil { 63 | return idpBuilder, fmt.Errorf("Expected a valid LDAP URL: %v", err) 64 | } 65 | if parsedLdapURL.Scheme != "ldap" && parsedLdapURL.Scheme != "ldaps" { 66 | return idpBuilder, errors.New("Expected LDAP URL to have an ldap:// or ldaps:// scheme") 67 | } 68 | 69 | // Create LDAP attributes 70 | ldapAttributes := cmv1.NewLDAPAttributes(). 71 | ID(strings.Split(ldapIDs, ",")...) 72 | 73 | if args.ldapUsernames != "" { 74 | ldapAttributes = ldapAttributes.PreferredUsername(strings.Split(args.ldapUsernames, ",")...) 75 | } 76 | if args.ldapDisplayNames != "" { 77 | ldapAttributes = ldapAttributes.Name(strings.Split(args.ldapDisplayNames, ",")...) 78 | } 79 | if args.ldapEmails != "" { 80 | ldapAttributes = ldapAttributes.Email(strings.Split(args.ldapEmails, ",")...) 81 | } 82 | 83 | // Create LDAP IDP 84 | ldapIDP := cmv1.NewLDAPIdentityProvider(). 85 | URL(ldapURL). 86 | Attributes(ldapAttributes) 87 | 88 | if args.ldapBindDN != "" { 89 | ldapIDP = ldapIDP.BindDN(args.ldapBindDN) 90 | if args.ldapBindPassword != "" { 91 | ldapIDP = ldapIDP.BindPassword(args.ldapBindPassword) 92 | } 93 | } 94 | 95 | // Create new IDP with LDAP provider 96 | idpBuilder. 97 | Type("LDAPIdentityProvider"). // FIXME: ocm-api-model has the wrong enum values 98 | Name(idpName). 99 | MappingMethod(cmv1.IdentityProviderMappingMethod(args.mappingMethod)). 100 | LDAP(ldapIDP) 101 | 102 | return 103 | } 104 | -------------------------------------------------------------------------------- /tests/logout_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package tests 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | . "github.com/onsi/ginkgo/v2" // nolint 24 | . "github.com/onsi/gomega" // nolint 25 | 26 | . "github.com/openshift-online/ocm-sdk-go/testing" // nolint 27 | ) 28 | 29 | var _ = Describe("Logout", func() { 30 | var ctx context.Context 31 | 32 | BeforeEach(func() { 33 | ctx = context.Background() 34 | }) 35 | 36 | It("Doesn't remove configuration file", func() { 37 | result := NewCommand(). 38 | ConfigString(`{}`). 39 | Args("logout"). 40 | Run(ctx) 41 | Expect(result.ExitCode()).To(BeZero()) 42 | Expect(result.ConfigFile()).ToNot(BeEmpty()) 43 | }) 44 | 45 | It("Removes tokens from configuration file", func() { 46 | // Generate the tokens: 47 | accessToken := MakeTokenString("Bearer", 15*time.Minute) 48 | refreshToken := MakeTokenString("Refresh", 10*time.Hour) 49 | 50 | // Run the command: 51 | result := NewCommand(). 52 | ConfigString( 53 | `{ 54 | "access_token": "{{ .accessToken }}", 55 | "refresh_token": "{{ .refreshToken }}" 56 | }`, 57 | "accessToken", accessToken, 58 | "refreshToken", refreshToken, 59 | ). 60 | Args("logout"). 61 | Run(ctx) 62 | Expect(result.ExitCode()).To(BeZero()) 63 | Expect(result.ConfigString()).To(MatchJSON(`{}`)) 64 | }) 65 | 66 | It("Removes client credentials from configuration file", func() { 67 | result := NewCommand(). 68 | ConfigString(`{ 69 | "client_id": "my_client", 70 | "client_secret": "my_secret" 71 | }`). 72 | Args("logout"). 73 | Run(ctx) 74 | Expect(result.ExitCode()).To(BeZero()) 75 | Expect(result.ConfigString()).To(MatchJSON(`{}`)) 76 | }) 77 | 78 | It("Removes URLs from configuration file", func() { 79 | result := NewCommand(). 80 | ConfigString(`{ 81 | "token_url": "http://my-sso.example.com", 82 | "url": "http://my-api.example.com" 83 | }`). 84 | Args("logout"). 85 | Run(ctx) 86 | Expect(result.ExitCode()).To(BeZero()) 87 | Expect(result.ConfigString()).To(MatchJSON(`{}`)) 88 | }) 89 | 90 | It("Removes scopes from configuration file", func() { 91 | result := NewCommand(). 92 | ConfigString(`{ 93 | "scopes": [ 94 | "my_scope", 95 | "your_scope" 96 | ] 97 | }`). 98 | Args("logout"). 99 | Run(ctx) 100 | Expect(result.ExitCode()).To(BeZero()) 101 | Expect(result.ConfigString()).To(MatchJSON(`{}`)) 102 | }) 103 | 104 | It("Removes insecure flag from configuration file", func() { 105 | result := NewCommand(). 106 | ConfigString(`{ 107 | "insecure": true 108 | }`). 109 | Args("logout"). 110 | Run(ctx) 111 | Expect(result.ExitCode()).To(BeZero()) 112 | Expect(result.ConfigString()).To(MatchJSON(`{}`)) 113 | }) 114 | 115 | It("Doesn't remove settings not related to authentication", func() { 116 | result := NewCommand(). 117 | ConfigString(`{ 118 | "pager": "less" 119 | }`). 120 | Args("logout"). 121 | Run(ctx) 122 | Expect(result.ExitCode()).To(BeZero()) 123 | Expect(result.ConfigString()).To(MatchJSON(`{ 124 | "pager": "less" 125 | }`)) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /cmd/ocm/delete/user/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Red Hat, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package user 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/spf13/cobra" 20 | 21 | c "github.com/openshift-online/ocm-cli/pkg/cluster" 22 | "github.com/openshift-online/ocm-cli/pkg/ocm" 23 | ) 24 | 25 | var args struct { 26 | clusterKey string 27 | group string 28 | } 29 | 30 | var Cmd = &cobra.Command{ 31 | Use: "user --cluster={NAME|ID|EXTERNAL_ID} --group=GROUP_ID [flags] USER1", 32 | Aliases: []string{"users"}, 33 | Short: "Remove user access from cluster", 34 | Long: "Remove a user from a priviledged group on a cluster.", 35 | Example: `# Delete users from the dedicated-admins group 36 | ocm delete user user1 --cluster=mycluster --group=dedicated-admins`, 37 | RunE: run, 38 | } 39 | 40 | func init() { 41 | flags := Cmd.Flags() 42 | 43 | flags.StringVarP( 44 | &args.clusterKey, 45 | "cluster", 46 | "c", 47 | "", 48 | "Name or ID or external_id of the cluster to delete the user from (required).", 49 | ) 50 | //nolint:gosec 51 | Cmd.MarkFlagRequired("cluster") 52 | 53 | flags.StringVar( 54 | &args.group, 55 | "group", 56 | "", 57 | "Group name to delete the user from.", 58 | ) 59 | //nolint:gosec 60 | Cmd.MarkFlagRequired("group") 61 | } 62 | 63 | func run(cmd *cobra.Command, argv []string) error { 64 | 65 | // Check command line arguments: 66 | if len(argv) != 1 { 67 | return fmt.Errorf( 68 | "Expected exactly one command line parameters containing the user name") 69 | } 70 | username := argv[0] 71 | 72 | // Check that the cluster key (name, identifier or external identifier) given by the user 73 | // is reasonably safe so that there is no risk of SQL injection: 74 | clusterKey := args.clusterKey 75 | if !c.IsValidClusterKey(clusterKey) { 76 | return fmt.Errorf( 77 | "Cluster name, identifier or external identifier '%s' isn't valid: it "+ 78 | "must contain only letters, digits, dashes and underscores", 79 | clusterKey, 80 | ) 81 | } 82 | // Create the client for the OCM API: 83 | connection, err := ocm.NewConnection().Build() 84 | if err != nil { 85 | return fmt.Errorf("Failed to create OCM connection: %v", err) 86 | } 87 | defer connection.Close() 88 | 89 | // Get the client for the cluster management api 90 | clusterCollection := connection.ClustersMgmt().V1().Clusters() 91 | 92 | cluster, err := c.GetCluster(connection, clusterKey) 93 | if err != nil { 94 | return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err) 95 | } 96 | 97 | _, err = clusterCollection.Cluster(cluster.ID()). 98 | Groups(). 99 | Group(args.group). 100 | Get(). 101 | Send() 102 | if err != nil { 103 | return fmt.Errorf("Group '%s' in cluster '%s' doesn't exist", args.group, clusterKey) 104 | } 105 | 106 | _, err = clusterCollection. 107 | Cluster(cluster.ID()). 108 | Groups(). 109 | Group(args.group). 110 | Users(). 111 | User(username). 112 | Delete(). 113 | Send() 114 | if err != nil { 115 | return fmt.Errorf("Failed to delete '%s' user '%s' on cluster '%s'", args.group, username, clusterKey) 116 | } 117 | 118 | fmt.Printf("Deleted '%s' user '%s' on cluster '%s'\n", args.group, username, clusterKey) 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /cmd/ocm/list/quota/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package quota 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "text/tabwriter" 23 | 24 | "github.com/spf13/cobra" 25 | 26 | "github.com/openshift-online/ocm-cli/pkg/dump" 27 | "github.com/openshift-online/ocm-cli/pkg/ocm" 28 | amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" 29 | ) 30 | 31 | var args struct { 32 | json bool 33 | org string 34 | } 35 | 36 | var Cmd = &cobra.Command{ 37 | Use: "quota", 38 | Short: "Retrieve cluster quota information.", 39 | Long: "Retrieve cluster quota information of a specific organization.", 40 | Args: cobra.NoArgs, 41 | RunE: run, 42 | } 43 | 44 | func init() { 45 | // Add flags to rootCmd: 46 | flags := Cmd.Flags() 47 | flags.BoolVar( 48 | &args.json, 49 | "json", 50 | false, 51 | "Returns a list of resource quota objects in JSON.", 52 | ) 53 | flags.StringVar( 54 | &args.org, 55 | "org", 56 | "", 57 | "Specify which organization to query information from. Default to local users organization.", 58 | ) 59 | } 60 | 61 | func run(cmd *cobra.Command, argv []string) error { 62 | connection, err := ocm.NewConnection().Build() 63 | if err != nil { 64 | return fmt.Errorf("Failed to create OCM connection: %v", err) 65 | } 66 | defer connection.Close() 67 | orgID := args.org 68 | 69 | if args.org == "" { 70 | // Get organization of current user: 71 | userConn, err := connection.AccountsMgmt().V1().CurrentAccount().Get(). 72 | Send() 73 | if err != nil { 74 | return fmt.Errorf("Can't retrieve current user information: %v", err) 75 | } 76 | userOrg, _ := userConn.Body().GetOrganization() 77 | orgID = userOrg.ID() 78 | } 79 | 80 | orgCollection := connection.AccountsMgmt().V1().Organizations().Organization(orgID) 81 | if err != nil { 82 | return fmt.Errorf("Can't retrieve organization information: %v", err) 83 | } 84 | 85 | quotaClient := orgCollection.QuotaCost() 86 | 87 | if !args.json { 88 | quotasListResponse, err := quotaClient.List(). 89 | Parameter("fetchRelatedResources", true). 90 | Send() 91 | if err != nil { 92 | return fmt.Errorf("Failed to retrieve quota: %v", err) 93 | } 94 | writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 95 | fmt.Fprintf( 96 | writer, 97 | "CONSUMED\t\tALLOWED\t\tQUOTA ID\n") 98 | 99 | quotasListResponse.Items().Each(func(quota *amv1.QuotaCost) bool { 100 | fmt.Fprintf(writer, "%d\t\t%d\t\t%s\n", quota.Consumed(), quota.Allowed(), quota.QuotaID()) 101 | return true 102 | }) 103 | 104 | err = writer.Flush() 105 | if err != nil { 106 | return nil 107 | } 108 | 109 | return nil 110 | } 111 | 112 | // TODO: Do this without hard-code; could not find any marshall method 113 | jsonDisplay, err := connection.Get().Path( 114 | fmt.Sprintf("/api/accounts_mgmt/v1/organizations/%s/resource_quota", orgID)). 115 | Parameter("fetchRelatedResources", true). 116 | Send() 117 | if err != nil { 118 | return fmt.Errorf("Failed to get resource quota: %v", err) 119 | } 120 | jsonDisplay.Bytes() 121 | err = dump.Pretty(os.Stdout, jsonDisplay.Bytes()) 122 | if err != nil { 123 | return fmt.Errorf("Failed to display quota JSON: %v", err) 124 | } 125 | 126 | return nil 127 | } 128 | -------------------------------------------------------------------------------- /cmd/ocm/gcp/list-wif-config.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/openshift-online/ocm-cli/pkg/config" 9 | "github.com/openshift-online/ocm-cli/pkg/ocm" 10 | "github.com/openshift-online/ocm-cli/pkg/output" 11 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 12 | "github.com/pkg/errors" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | const ( 17 | versionColumn = "versions" 18 | ) 19 | 20 | var ListWorkloadIdentityConfigurationOpts struct { 21 | columns string 22 | noHeaders bool 23 | } 24 | 25 | // NewListWorkloadIdentityConfiguration provides the "gcp list wif-config" subcommand 26 | func NewListWorkloadIdentityConfiguration() *cobra.Command { 27 | listWorkloadIdentityPoolCmd := &cobra.Command{ 28 | Use: "wif-config", 29 | Aliases: []string{"wif-configs"}, 30 | Short: "List workload identity federation configurations (wif-configs)", 31 | Long: `List workload identity federation configurations (wif-configs). 32 | 33 | The caller of the command will only view data from wif-config objects that 34 | belong to the user's organization.`, 35 | RunE: listWorkloadIdentityConfigurationCmd, 36 | } 37 | 38 | fs := listWorkloadIdentityPoolCmd.Flags() 39 | fs.StringVar( 40 | &ListWorkloadIdentityConfigurationOpts.columns, 41 | "columns", 42 | "id, display_name, gcp.project_id, "+versionColumn, 43 | `Specify which columns to display separated by commas. 44 | The path is based on wif-config struct. 45 | `, 46 | ) 47 | fs.BoolVar( 48 | &ListWorkloadIdentityConfigurationOpts.noHeaders, 49 | "no-headers", 50 | false, 51 | "Don't print header row", 52 | ) 53 | 54 | return listWorkloadIdentityPoolCmd 55 | } 56 | 57 | func listWorkloadIdentityConfigurationCmd(cmd *cobra.Command, argv []string) error { 58 | // Create a context: 59 | ctx := context.Background() 60 | 61 | // Load the configuration: 62 | cfg, err := config.Load() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | // Create the client for the OCM API: 68 | connection, err := ocm.NewConnection().Build() 69 | if err != nil { 70 | return errors.Wrapf(err, "Failed to create OCM connection") 71 | } 72 | defer connection.Close() 73 | 74 | // Create the output printer: 75 | printer, err := output.NewPrinter(). 76 | Writer(os.Stdout). 77 | Pager(cfg.Pager). 78 | Build(ctx) 79 | if err != nil { 80 | return err 81 | } 82 | defer printer.Close() 83 | 84 | // Create the output table: 85 | table, err := printer.NewTable(). 86 | Name("wifconfigs"). 87 | Columns(ListWorkloadIdentityConfigurationOpts.columns). 88 | Value(versionColumn, func(object *cmv1.WifConfig) string { 89 | return fmt.Sprintf("%v", object.WifTemplates()) 90 | }). 91 | Build(ctx) 92 | if err != nil { 93 | return err 94 | } 95 | defer table.Close() 96 | 97 | // Unless noHeaders set, print header row: 98 | if !ListWorkloadIdentityConfigurationOpts.noHeaders { 99 | table.WriteHeaders() 100 | } 101 | 102 | // Create the request 103 | request := connection.ClustersMgmt().V1().GCP().WifConfigs().List() 104 | 105 | size := 100 106 | index := 1 107 | for { 108 | // Fetch the next page: 109 | request.Size(size) 110 | request.Page(index) 111 | response, err := request.Send() 112 | if err != nil { 113 | return errors.Wrapf(err, "can't retrieve wif configs") 114 | } 115 | 116 | // Display the items of the fetched page: 117 | response.Items().Each(func(wc *cmv1.WifConfig) bool { 118 | err = table.WriteObject(wc) 119 | return err == nil 120 | }) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | // If the number of fetched items is less than requested, then this was the last 126 | // page, otherwise process the next one: 127 | if response.Size() < size { 128 | break 129 | } 130 | index++ 131 | } 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /pkg/provider/encryption.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/openshift-online/ocm-cli/pkg/cluster" 8 | cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func GetGcpKmsKeyLocations(client *cmv1.Client, ccs cluster.CCS, 13 | gcpAuth cluster.GcpAuthentication, region string) ([]string, error) { 14 | 15 | gcpDataBuilder, err := getGcpCloudProviderDataBuilder(gcpAuth, ccs) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | cloudProviderData, err := gcpDataBuilder.Build() 21 | if err != nil { 22 | return nil, errors.Wrapf(err, "failed to build GCP provider data") 23 | } 24 | 25 | response, err := client.GCPInquiries().Regions().Search(). 26 | Page(1).Size(-1).Body(cloudProviderData).Send() 27 | 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | var keyLocations []string 33 | for _, cloudRegion := range response.Items().Slice() { 34 | keyLocationsStr, ok := cloudRegion.GetKMSLocationID() 35 | if !ok { 36 | return nil, errors.Wrapf(err, "failed to build GCP provider data") 37 | } 38 | keyLocations = strings.Split(keyLocationsStr, ",") 39 | } 40 | 41 | return keyLocations, err 42 | } 43 | 44 | func GetGcpKmsKeyRings(client *cmv1.Client, ccs cluster.CCS, 45 | gcpAuth cluster.GcpAuthentication, keyLocation string) ([]*cmv1.KeyRing, error) { 46 | 47 | gcpDataBuilder, err := getGcpCloudProviderDataBuilder(gcpAuth, ccs) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | cloudProviderData, err := gcpDataBuilder. 53 | KeyLocation(keyLocation). 54 | Build() 55 | if err != nil { 56 | return nil, errors.Wrapf(err, "failed to build GCP provider data") 57 | } 58 | response, err := client.GCPInquiries().KeyRings().Search(). 59 | Page(1).Size(-1).Body(cloudProviderData).Send() 60 | 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return response.Items().Slice(), err 66 | } 67 | 68 | func GetGcpKmsKeys(client *cmv1.Client, ccs cluster.CCS, 69 | gcpAuth cluster.GcpAuthentication, keyLocation string, keyRing string) ([]*cmv1.EncryptionKey, error) { 70 | 71 | gcpDataBuilder, err := getGcpCloudProviderDataBuilder(gcpAuth, ccs) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | cloudProviderData, err := gcpDataBuilder. 77 | KeyLocation(keyLocation). 78 | KeyRingName(keyRing). 79 | Build() 80 | if err != nil { 81 | return nil, errors.Wrapf(err, "failed to build GCP provider data") 82 | } 83 | 84 | response, err := client.GCPInquiries().EncryptionKeys().Search(). 85 | Page(1).Size(-1).Body(cloudProviderData).Send() 86 | 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return response.Items().Slice(), err 92 | } 93 | 94 | func getGcpCloudProviderDataBuilder(gcpAuth cluster.GcpAuthentication, 95 | ccs cluster.CCS) (*cmv1.CloudProviderDataBuilder, error) { 96 | 97 | gcpBuilder := cmv1.NewGCP() 98 | 99 | switch gcpAuth.Type { 100 | case cluster.AuthenticationWif: 101 | gcpAuth := cmv1.NewGcpAuthentication(). 102 | Kind(cmv1.WifConfigKind). 103 | Id(gcpAuth.Id) 104 | gcpBuilder.Authentication(gcpAuth) 105 | case cluster.AuthenticationKey: 106 | gcpBuilder.ProjectID(ccs.GCP.ProjectID). 107 | ClientEmail(ccs.GCP.ClientEmail). 108 | Type(ccs.GCP.Type). 109 | PrivateKey(ccs.GCP.PrivateKey). 110 | PrivateKeyID(ccs.GCP.PrivateKeyID). 111 | AuthProviderX509CertURL(ccs.GCP.AuthProviderX509CertURL). 112 | AuthURI(ccs.GCP.AuthURI).TokenURI(ccs.GCP.TokenURI). 113 | ClientX509CertURL(ccs.GCP.ClientX509CertURL). 114 | ClientID(ccs.GCP.ClientID).TokenURI(ccs.GCP.TokenURI) 115 | default: 116 | return nil, errors.New( 117 | fmt.Sprintf("failed to build GCP provider data, unexpected GCP authentication method %q", gcpAuth.Type)) 118 | } 119 | 120 | return cmv1.NewCloudProviderData().GCP(gcpBuilder), nil 121 | } 122 | --------------------------------------------------------------------------------