-
Notifications
You must be signed in to change notification settings - Fork 184
Add IBMCloud manual mode #356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
8e856e3
fb49b2a
014ef1b
7696d7c
5843a17
38595a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /* | ||
| Copyright 2021 The OpenShift Authors. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package v1 | ||
|
|
||
| import ( | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| ) | ||
|
|
||
| // TODO: these types should eventually be broken out, along with the actuator, to a separate repo. | ||
|
|
||
| // IBMCloudProviderSpec the specification of the credentials request in IBM Cloud. | ||
|
BobbyRadford marked this conversation as resolved.
Outdated
|
||
| // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object | ||
| type IBMCloudProviderSpec struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
| } | ||
|
BobbyRadford marked this conversation as resolved.
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| package ibmcloud | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io" | ||
| "io/ioutil" | ||
| "log" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| credreqv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1" | ||
| "github.com/openshift/cloud-credential-operator/pkg/cmd/provisioning" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To keep consistency, these imports should come in a separate block at the end
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Hopefully I understood you correctly. |
||
| "github.com/pkg/errors" | ||
| "github.com/spf13/cobra" | ||
| "k8s.io/apimachinery/pkg/util/yaml" | ||
| ) | ||
|
|
||
| const ( | ||
| secretManifestsTemplate = `apiVersion: v1 | ||
| stringData: | ||
| ibmcloud_api_key: %s | ||
| kind: Secret | ||
| metadata: | ||
| name: %s | ||
| namespace: %s | ||
| type: Opaque` | ||
|
|
||
| manifestsDirName = "manifests" | ||
| ) | ||
|
|
||
| var ( | ||
| // CreateOpts captures the options that affect creation of the generated | ||
| // objects. | ||
| CreateOpts = options{ | ||
| TargetDir: "", | ||
| } | ||
| ) | ||
|
|
||
| // NewCreateCmd implements the "create" command for the credentials provisioning | ||
| func NewCreateCmd() *cobra.Command { | ||
| createCmd := &cobra.Command{ | ||
| Use: "create", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. by command conventions that we follow, this should be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's no problem. Changed to |
||
| Short: "Create credentials objects", | ||
| Long: "Creating objects related to cloud credentials", | ||
| Run: createCmd, | ||
| PersistentPreRun: initEnvForCreateCmd, | ||
| } | ||
|
|
||
| createCmd.PersistentFlags().StringVar(&CreateOpts.CredRequestDir, "credentials-requests-dir", "", "Directory containing files of CredentialsRequests (can be created by running 'oc adm release extract --credentials-requests --cloud=ibmcloud' against an OpenShift release image)") | ||
| createCmd.MarkPersistentFlagRequired("credentials-requests-dir") | ||
| createCmd.PersistentFlags().StringVar(&CreateOpts.TargetDir, "output-dir", "", "Directory to place generated files (defaults to current directory)") | ||
|
|
||
| return createCmd | ||
| } | ||
|
|
||
| func createCmd(cmd *cobra.Command, args []string) { | ||
| apiKey := os.Getenv("IC_API_KEY") | ||
| if apiKey == "" { | ||
| log.Fatal(fmt.Errorf("IC_API_KEY environment variable not set")) | ||
| } | ||
|
|
||
| err := create(CreateOpts.CredRequestDir, CreateOpts.TargetDir, apiKey) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| } | ||
|
|
||
| func create(credReqDir string, targetDir string, apiKey string) error { | ||
| credRequests, err := getListOfCredentialsRequests(credReqDir) | ||
| if err != nil { | ||
| return errors.Wrap(err, "Failed to process files containing CredentialsRequests") | ||
| } | ||
|
|
||
| for _, cr := range credRequests { | ||
| if err := processCredReq(cr, targetDir, apiKey); err != nil { | ||
| return errors.Wrap(err, "Failed to process CredentialsReqeust") | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func getListOfCredentialsRequests(dir string) ([]*credreqv1.CredentialsRequest, error) { | ||
| credRequests := []*credreqv1.CredentialsRequest{} | ||
| files, err := ioutil.ReadDir(dir) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| for _, file := range files { | ||
| f, err := os.Open(filepath.Join(dir, file.Name())) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "Failed to open file") | ||
| } | ||
| defer f.Close() | ||
| decoder := yaml.NewYAMLOrJSONDecoder(f, 4096) | ||
| for { | ||
| cr := &credreqv1.CredentialsRequest{} | ||
| if err := decoder.Decode(cr); err != nil { | ||
| if err == io.EOF { | ||
| break | ||
| } | ||
| return nil, errors.Wrap(err, "Failed to decode to CredentialsRequest") | ||
| } | ||
| credRequests = append(credRequests, cr) | ||
| } | ||
|
|
||
| } | ||
|
|
||
| return credRequests, nil | ||
| } | ||
|
|
||
| func processCredReq(cr *credreqv1.CredentialsRequest, targetDir, apiKey string) error { | ||
| // Decode IBMCloudProviderSpec | ||
| codec, err := credreqv1.NewCodec() | ||
| if err != nil { | ||
| return errors.Wrap(err, "Failed to create credReq codec") | ||
| } | ||
|
|
||
| ibmcloudProviderProviderSpec := credreqv1.IBMCloudProviderSpec{} | ||
| if err := codec.DecodeProviderSpec(cr.Spec.ProviderSpec, &ibmcloudProviderProviderSpec); err != nil { | ||
| return errors.Wrap(err, "Failed to decode the provider spec") | ||
| } | ||
|
|
||
| if ibmcloudProviderProviderSpec.Kind != "IBMCloudProviderSpec" { | ||
| return fmt.Errorf("CredentialsRequest %s/%s is not of type IBM Cloud", cr.Namespace, cr.Name) | ||
| } | ||
|
|
||
| return writeCredReqSecret(cr, targetDir, apiKey) | ||
| } | ||
|
|
||
| func writeCredReqSecret(cr *credreqv1.CredentialsRequest, targetDir, apiKey string) error { | ||
| manifestsDir := filepath.Join(targetDir, manifestsDirName) | ||
|
|
||
| fileName := fmt.Sprintf("%s-%s-credentials.yaml", cr.Spec.SecretRef.Namespace, cr.Spec.SecretRef.Name) | ||
| filePath := filepath.Join(manifestsDir, fileName) | ||
|
|
||
| fileData := fmt.Sprintf(secretManifestsTemplate, apiKey, cr.Spec.SecretRef.Name, cr.Spec.SecretRef.Namespace) | ||
|
|
||
| if err := ioutil.WriteFile(filePath, []byte(fileData), 0600); err != nil { | ||
| return errors.Wrap(err, "Failed to save Secret file") | ||
| } | ||
|
|
||
| log.Printf("Saved credentials configuration to: %s", filePath) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // initEnvForCreateCmd will ensure the destination directory is ready to | ||
| // receive the generated files, and will create the directory if necessary. | ||
| func initEnvForCreateCmd(cmd *cobra.Command, args []string) { | ||
| if CreateOpts.TargetDir == "" { | ||
| pwd, err := os.Getwd() | ||
| if err != nil { | ||
| log.Fatalf("Failed to get current directory: %s", err) | ||
| } | ||
|
|
||
| CreateOpts.TargetDir = pwd | ||
| } | ||
|
|
||
| fPath, err := filepath.Abs(CreateOpts.TargetDir) | ||
| if err != nil { | ||
| log.Fatalf("Failed to resolve full path: %s", err) | ||
| } | ||
|
|
||
| // create target dir if necessary | ||
| err = provisioning.EnsureDir(fPath) | ||
| if err != nil { | ||
| log.Fatalf("failed to create target directory at %s", fPath) | ||
| } | ||
|
|
||
| // create manifests dir if necessary | ||
| manifestsDir := filepath.Join(fPath, manifestsDirName) | ||
| err = provisioning.EnsureDir(manifestsDir) | ||
| if err != nil { | ||
| log.Fatalf("failed to create manifests directory at %s", manifestsDir) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| package ibmcloud | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io/ioutil" | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| "github.com/openshift/cloud-credential-operator/pkg/cmd/provisioning" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| const ( | ||
| apiKey = "testapiKey" | ||
| testDirPrefix = "createtestdir" | ||
| ) | ||
|
|
||
| func TestIAMRoles(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| setup func(*testing.T) string | ||
| verify func(t *testing.T, tempDirName string) | ||
| cleanup func(*testing.T) | ||
| expectError bool | ||
| }{ | ||
| { | ||
| name: "Generate Secret for one CredentialsRequest", | ||
| setup: func(t *testing.T) string { | ||
| tempDirName, err := ioutil.TempDir(os.TempDir(), testDirPrefix) | ||
| require.NoError(t, err, "Failed to create temp directory") | ||
|
|
||
| err = testCredentialsRequest(t, "firstcredreq", "namespace1", "secretName1", tempDirName) | ||
| require.NoError(t, err, "Errored while setting up test CredReq files") | ||
|
|
||
| return tempDirName | ||
| }, | ||
| verify: func(t *testing.T, targetDir string) { | ||
| files, err := ioutil.ReadDir(targetDir) | ||
| require.NoError(t, err, "Unexpected error listing files in targetDir") | ||
|
|
||
| assert.Equal(t, 1, len(files), "Should be exactly 1 Secret generated for 1 CredentialsRequest") | ||
| }, | ||
| cleanup: func(t *testing.T) { | ||
| return | ||
| }, | ||
| expectError: false, | ||
|
Comment on lines
+28
to
+
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have a test that does setting IC_API_KEY in setup and verify if generated secret actually has that key? Also, one negative test where env var is not set and error is thrown. |
||
| }, | ||
| } | ||
|
|
||
| for _, test := range tests { | ||
| t.Run(test.name, func(t *testing.T) { | ||
| credReqDir := test.setup(t) | ||
| defer os.RemoveAll(credReqDir) | ||
|
|
||
| targetDir, err := ioutil.TempDir(os.TempDir(), "ibmcloudcreatetest") | ||
| require.NoError(t, err, "Unexpected error creating temp dir for test") | ||
|
|
||
| manifestsDir := filepath.Join(targetDir, manifestsDirName) | ||
| err = provisioning.EnsureDir(manifestsDir) | ||
| require.NoError(t, err, "Unexpected error creating manifests dir for test") | ||
|
|
||
| err = create(credReqDir, targetDir, apiKey) | ||
|
|
||
| if test.expectError { | ||
| require.Error(t, err, "Expected error returned") | ||
| } else { | ||
| require.NoError(t, err, "Unexpected error creating secrets") | ||
| test.verify(t, targetDir) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func testCredentialsRequest(t *testing.T, crName, targetSecretNamespace, targetSecretName, targetDir string) error { | ||
| credReqTemplate := `--- | ||
| apiVersion: cloudcredential.openshift.io/v1 | ||
| kind: CredentialsRequest | ||
| metadata: | ||
| name: %s | ||
| namespace: openshift-cloud-credential-operator | ||
| spec: | ||
| providerSpec: | ||
| apiVersion: cloudcredential.openshift.io/v1 | ||
| kind: IBMCloudProviderSpec | ||
| secretRef: | ||
| namespace: %s | ||
| name: %s | ||
| serviceAccountNames: | ||
| - testServiceAccount1` | ||
|
|
||
| credReq := fmt.Sprintf(credReqTemplate, crName, targetSecretNamespace, targetSecretName) | ||
|
|
||
| f, err := ioutil.TempFile(targetDir, "testCredReq") | ||
| require.NoError(t, err, "error creating temp file for CredentialsRequest") | ||
| defer f.Close() | ||
|
|
||
| _, err = f.Write([]byte(credReq)) | ||
| require.NoError(t, err, "error while writing out contents of CredentialsRequest file") | ||
|
|
||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package ibmcloud | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| type options struct { | ||
| TargetDir string | ||
| CredRequestDir string | ||
| DryRun bool | ||
|
BobbyRadford marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // NewIBMCloudCmd implements the "ibmcloud" subcommand for the credentials provisioning | ||
| func NewIBMCloudCmd() *cobra.Command { | ||
| createCmd := &cobra.Command{ | ||
| Use: "ibmcloud", | ||
| Short: "Manage credentials objects for IBM Cloud", | ||
| Long: "Creating/deleting cloud credentials objects for IBM Cloud", | ||
| } | ||
|
|
||
| createCmd.AddCommand(NewCreateCmd()) | ||
|
|
||
| return createCmd | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.