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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ Cloud | Mint | Mint + Remove Admin Cred | Passthrough | Manual | Token
AWS | Y | 4.4+ | Y | 4.3+ | 4.6+ (expected)
Azure | Y | N | Y | Y | N
GCP | Y | 4.7+ | Y | Y | N
IBMCloud | N | N | N | Y | N
KubeVirt | N | N | Y | N | N
OpenStack | N | N | Y | N | N
oVirt | N | N | Y | N | N
Expand Down
2 changes: 2 additions & 0 deletions cmd/ccoctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/spf13/cobra"

"github.com/openshift/cloud-credential-operator/pkg/cmd/provisioning/aws"
"github.com/openshift/cloud-credential-operator/pkg/cmd/provisioning/ibmcloud"
)

func main() {
Expand All @@ -15,6 +16,7 @@ func main() {
}

rootCmd.AddCommand(aws.NewAWSCmd())
rootCmd.AddCommand(ibmcloud.NewIBMCloudCmd())

if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/cloudcredential/v1/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&AWSProviderStatus{}, &AWSProviderSpec{},
&AzureProviderStatus{}, &AzureProviderSpec{},
&GCPProviderStatus{}, &GCPProviderSpec{},
&IBMCloudProviderStatus{}, &IBMCloudProviderSpec{},
&VSphereProviderStatus{}, &VSphereProviderSpec{},
&KubevirtProviderStatus{}, &KubevirtProviderSpec{},
)
Expand Down
35 changes: 35 additions & 0 deletions pkg/apis/cloudcredential/v1/types_ibmcloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
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 is the specification of the credentials request in IBM Cloud.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type IBMCloudProviderSpec struct {
metav1.TypeMeta `json:",inline"`
}
Comment thread
BobbyRadford marked this conversation as resolved.

// IBMCloudProviderStatus contains the status of the IBM Cloud credentials request.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type IBMCloudProviderStatus struct {
metav1.TypeMeta `json:",inline"`
}
50 changes: 50 additions & 0 deletions pkg/apis/cloudcredential/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

177 changes: 177 additions & 0 deletions pkg/cmd/provisioning/ibmcloud/create.go
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by command conventions that we follow, this should be create-secrets I feel. @dgoodwin WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's no problem. Changed to create-secrets

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)
}
}
Loading