Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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"`
}

// 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.

182 changes: 182 additions & 0 deletions pkg/cmd/provisioning/ibmcloud/create_secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package ibmcloud

import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/yaml"

credreqv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1"
"github.com/openshift/cloud-credential-operator/pkg/cmd/provisioning"
)

const (
secretManifestsTemplate = `apiVersion: v1
stringData:
ibmcloud_api_key: %s
kind: Secret
metadata:
name: %s
namespace: %s
type: Opaque`

manifestsDirName = "manifests"
)

// APIKeyEnvVar is the environment variable name containing an IBM Cloud API key
const APIKeyEnvVar = "IC_API_KEY"

var (
// CreateOpts captures the options that affect creation of the generated
// objects.
CreateOpts = options{
TargetDir: "",
}
)

// NewCreateSecretsCmd implements the "create-secrets" command for the credentials provisioning
func NewCreateSecretsCmd() *cobra.Command {
createSecretsCmd := &cobra.Command{
Use: "create-secrets",
Copy link
Contributor

Choose a reason for hiding this comment

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

@BobbyRadford I was discussing this with the team and I was wondering if a name like create-shared-secrets would be better.
I'm comparing this to the existing aws support and there is no equivalent create-secrets subcommand (today) for AWS (which isn't surprising b/c of lack of support for passthrough-equivalent mode). So this subcommand feels specific to implementing Passthrough mode and the name of the command should reflect that.

Could we rename this to something like create-shared-secrets and have the long description make it clear that this subcommand just takes the API key from an env var and uses it to satisfy all the CredentialsRequests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey @joelddiaz no problem. Will make that happen.

Short: "Create credentials objects",
Long: "Creating objects related to cloud credentials",
RunE: createSecretsCmd,
PersistentPreRun: initEnvForCreateCmd,
}

createSecretsCmd.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)")
createSecretsCmd.MarkPersistentFlagRequired("credentials-requests-dir")
createSecretsCmd.PersistentFlags().StringVar(&CreateOpts.TargetDir, "output-dir", "", "Directory to place generated files (defaults to current directory)")

return createSecretsCmd
}

func createSecretsCmd(cmd *cobra.Command, args []string) error {
apiKey := os.Getenv(APIKeyEnvVar)
if apiKey == "" {
return fmt.Errorf("%s environment variable not set", APIKeyEnvVar)
}

err := createSecrets(CreateOpts.CredRequestDir, CreateOpts.TargetDir, apiKey)
if err != nil {
return err
}
return nil
}

func createSecrets(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