generated from vshn/go-bootstrap
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from vshn/cloudscale-create-user
Create Objects users on cloudscale.ch
- Loading branch information
Showing
26 changed files
with
988 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,7 @@ jobs: | |
${{ runner.os }}-go- | ||
- name: Run tests | ||
run: make test | ||
run: make test-integration | ||
|
||
- name: Upload code coverage report to Code Climate | ||
uses: paambaati/[email protected] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package conditions | ||
|
||
import ( | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
// ConditionBuilder builds Conditions using various properties. | ||
type ConditionBuilder struct { | ||
condition metav1.Condition | ||
} | ||
|
||
// Builder returns a new ConditionBuilder instance. | ||
func Builder() *ConditionBuilder { | ||
return &ConditionBuilder{} | ||
} | ||
|
||
// With initializes the condition with the given value. | ||
// Returns itself for convenience. | ||
func (b *ConditionBuilder) With(condition metav1.Condition) *ConditionBuilder { | ||
b.condition = condition | ||
return b | ||
} | ||
|
||
// WithMessage sets the condition message. | ||
// Returns itself for convenience. | ||
func (b *ConditionBuilder) WithMessage(message string) *ConditionBuilder { | ||
b.condition.Message = message | ||
return b | ||
} | ||
|
||
// Build returns the condition. | ||
func (b *ConditionBuilder) Build() metav1.Condition { | ||
return b.condition | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package conditions | ||
|
||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
||
// Reasons that give more context to conditions | ||
const ( | ||
ReasonAvailable = "Available" | ||
ReasonProvisioningFailed = "ProvisioningFailed" | ||
) | ||
|
||
const ( | ||
// TypeReady indicates that a resource is ready for use. | ||
TypeReady = "Ready" | ||
// TypeFailed indicates that a resource has failed the provisioning. | ||
TypeFailed = "Failed" | ||
) | ||
|
||
// Ready creates a condition with TypeReady, ReasonAvailable and empty message. | ||
func Ready() metav1.Condition { | ||
return metav1.Condition{ | ||
Type: TypeReady, | ||
Status: metav1.ConditionTrue, | ||
LastTransitionTime: metav1.Now(), | ||
Reason: ReasonAvailable, | ||
} | ||
} | ||
|
||
// NotReady creates a condition with TypeReady, ReasonAvailable and empty message. | ||
func NotReady() metav1.Condition { | ||
return metav1.Condition{ | ||
Type: TypeReady, | ||
Status: metav1.ConditionFalse, | ||
LastTransitionTime: metav1.Now(), | ||
Reason: ReasonAvailable, | ||
} | ||
} | ||
|
||
// Failed creates a condition with TypeFailed, ReasonProvisioningFailed and the error message. | ||
func Failed(err error) metav1.Condition { | ||
return metav1.Condition{ | ||
Type: TypeFailed, | ||
Status: metav1.ConditionTrue, | ||
LastTransitionTime: metav1.Now(), | ||
Reason: ReasonProvisioningFailed, | ||
Message: err.Error(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package cloudscale | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
pipeline "github.com/ccremer/go-command-pipeline" | ||
cloudscalesdk "github.com/cloudscale-ch/cloudscale-go-sdk/v2" | ||
cloudscalev1 "github.com/vshn/appcat-service-s3/apis/cloudscale/v1" | ||
"github.com/vshn/appcat-service-s3/operator/steps" | ||
"golang.org/x/oauth2" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/labels" | ||
controllerruntime "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" | ||
) | ||
|
||
// APIToken is the authentication token to use against cloudscale.ch API | ||
var APIToken string | ||
|
||
// CloudscaleClientKey identifies the cloudscale client in the context. | ||
type CloudscaleClientKey struct{} | ||
|
||
// CreateCloudscaleClientFn creates a new client using the API token provided. | ||
func CreateCloudscaleClientFn(apiToken string) func(ctx context.Context) error { | ||
return func(ctx context.Context) error { | ||
tc := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: apiToken})) | ||
csClient := cloudscalesdk.NewClient(tc) | ||
pipeline.StoreInContext(ctx, CloudscaleClientKey{}, csClient) | ||
return nil | ||
} | ||
} | ||
|
||
// CloudscaleUserKey identifies the User object from cloudscale SDK in the context. | ||
type CloudscaleUserKey struct{} | ||
|
||
// CreateObjectsUser creates a new objects user in the project associated with the API token. | ||
func CreateObjectsUser(ctx context.Context) error { | ||
csClient := steps.GetFromContextOrPanic(ctx, CloudscaleClientKey{}).(*cloudscalesdk.Client) | ||
user := steps.GetFromContextOrPanic(ctx, ObjectsUserKey{}).(*cloudscalev1.ObjectsUser) | ||
log := controllerruntime.LoggerFrom(ctx) | ||
|
||
displayName := fmt.Sprintf("%s.%s", user.Namespace, user.Name) | ||
|
||
csUser, err := csClient.ObjectsUsers.Create(ctx, &cloudscalesdk.ObjectsUserRequest{ | ||
DisplayName: displayName, | ||
}) | ||
user.Status.UserID = csUser.ID | ||
|
||
pipeline.StoreInContext(ctx, CloudscaleUserKey{}, csUser) | ||
return logIfNotError(err, log, 1, "Created objects user in cloudscale") | ||
} | ||
|
||
// GetObjectsUser fetches an existing objects user from the project associated with the API token. | ||
func GetObjectsUser(ctx context.Context) error { | ||
csClient := steps.GetFromContextOrPanic(ctx, CloudscaleClientKey{}).(*cloudscalesdk.Client) | ||
user := steps.GetFromContextOrPanic(ctx, ObjectsUserKey{}).(*cloudscalev1.ObjectsUser) | ||
log := controllerruntime.LoggerFrom(ctx) | ||
|
||
csUser, err := csClient.ObjectsUsers.Get(ctx, user.Status.UserID) | ||
|
||
pipeline.StoreInContext(ctx, CloudscaleUserKey{}, csUser) | ||
return logIfNotError(err, log, 1, "Fetched objects user in cloudscale") | ||
} | ||
|
||
// UserCredentialSecretKey identifies the credential Secret in the context. | ||
type UserCredentialSecretKey struct{} | ||
|
||
// EnsureCredentialSecret creates the credential secret. | ||
func EnsureCredentialSecret(ctx context.Context) error { | ||
kube := steps.GetClientFromContext(ctx) | ||
user := steps.GetFromContextOrPanic(ctx, ObjectsUserKey{}).(*cloudscalev1.ObjectsUser) | ||
csUser := steps.GetFromContextOrPanic(ctx, CloudscaleUserKey{}).(*cloudscalesdk.ObjectsUser) | ||
log := controllerruntime.LoggerFrom(ctx) | ||
|
||
secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: user.Spec.SecretRef, Namespace: user.Namespace}} | ||
|
||
if keyErr := checkUserForKeys(csUser); keyErr != nil { | ||
return keyErr | ||
} | ||
|
||
// See https://www.cloudscale.ch/en/api/v1#objects-users | ||
|
||
_, err := controllerruntime.CreateOrUpdate(ctx, kube, secret, func() error { | ||
secret.Labels = labels.Merge(secret.Labels, getCommonLabels(user.Name)) | ||
if secret.StringData == nil { | ||
secret.StringData = map[string]string{} | ||
} | ||
secret.StringData["AWS_ACCESS_KEY_ID"] = csUser.Keys[0]["access_key"] | ||
secret.StringData["AWS_SECRET_ACCESS_KEY"] = csUser.Keys[0]["secret_key"] | ||
controllerutil.AddFinalizer(secret, userFinalizer) | ||
return controllerutil.SetOwnerReference(user, secret, kube.Scheme()) | ||
}) | ||
|
||
pipeline.StoreInContext(ctx, UserCredentialSecretKey{}, secret) | ||
return logIfNotError(err, log, 1, "Ensured credential secret", "secretName", user.Spec.SecretRef) | ||
} | ||
|
||
func checkUserForKeys(user *cloudscalesdk.ObjectsUser) error { | ||
if len(user.Keys) == 0 { | ||
return fmt.Errorf("the returned objects user has no key pairs: %q", user.ID) | ||
} | ||
if val, exists := user.Keys[0]["secret_key"]; exists && val == "" { | ||
return fmt.Errorf("the returned objects user %q has no secret_key. Does the API token have enough permissions?", user.ID) | ||
} | ||
return nil | ||
} |
Oops, something went wrong.