forked from rook/rook
For all of the controllers besides the cluster controller, the logging now includes the namespaced name of the resource that is being reconciled. This will help with log troubleshooting to help analyze logs consistently for the resource being reconciled. Signed-off-by: Travis Nielsen <tnielsen@redhat.com>
238 lines
7.5 KiB
Go
238 lines
7.5 KiB
Go
/*
|
|
Copyright 2016 The Rook Authors. All rights reserved.
|
|
|
|
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 object
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/ceph/go-ceph/rgw/admin"
|
|
"github.com/pkg/errors"
|
|
"github.com/rook/rook/pkg/util/exec"
|
|
"github.com/rook/rook/pkg/util/log"
|
|
)
|
|
|
|
const (
|
|
RGWErrorNone = iota
|
|
RGWErrorUnknown
|
|
RGWErrorNotFound
|
|
RGWErrorBadData
|
|
RGWErrorParse
|
|
ErrorCodeFileExists = 17
|
|
)
|
|
|
|
// An ObjectUser defines the details of an object store user.
|
|
type ObjectUser struct {
|
|
UserID string `json:"userId"`
|
|
DisplayName *string `json:"displayName"`
|
|
Email *string `json:"email"`
|
|
AccessKey *string `json:"accessKey"`
|
|
SecretKey *string `json:"secretKey"`
|
|
SystemUser bool `json:"systemuser"`
|
|
AdminOpsUser bool `json:"adminopsuser"`
|
|
MaxBuckets int `json:"max_buckets"`
|
|
UserQuota admin.QuotaSpec `json:"user_quota"`
|
|
Caps []admin.UserCapSpec `json:"caps"`
|
|
}
|
|
|
|
// func decodeUser(data string) (*ObjectUser, int, error) {
|
|
func decodeUser(data string) (*ObjectUser, int, error) {
|
|
var user admin.User
|
|
err := json.Unmarshal([]byte(data), &user)
|
|
if err != nil {
|
|
return nil, RGWErrorParse, errors.Wrapf(err, "failed to unmarshal json. %s", data)
|
|
}
|
|
|
|
rookUser := ObjectUser{UserID: user.ID, DisplayName: &user.DisplayName, Email: &user.Email}
|
|
|
|
if len(user.Caps) > 0 {
|
|
rookUser.Caps = user.Caps
|
|
}
|
|
|
|
if user.MaxBuckets != nil {
|
|
rookUser.MaxBuckets = *user.MaxBuckets
|
|
}
|
|
|
|
if user.UserQuota.Enabled != nil {
|
|
rookUser.UserQuota = user.UserQuota
|
|
}
|
|
|
|
if len(user.Keys) > 0 {
|
|
rookUser.AccessKey = &user.Keys[0].AccessKey
|
|
rookUser.SecretKey = &user.Keys[0].SecretKey
|
|
} else {
|
|
return nil, RGWErrorBadData, errors.New("AccessKey and SecretKey are missing")
|
|
}
|
|
|
|
return &rookUser, RGWErrorNone, nil
|
|
}
|
|
|
|
// GetUser returns the user with the given ID.
|
|
// The function is used **ONCE** only to provision so the RGW Admin Ops User
|
|
// Subsequent interaction with the API will be done with the created user
|
|
func GetUser(c *Context, id string) (*ObjectUser, int, error) {
|
|
log.NamedDebug(c.NsName(), logger, "getting s3 user %q", id)
|
|
// note: err is set for non-existent user but result output is also empty
|
|
result, err := runAdminCommand(c, false, "user", "info", "--uid", id)
|
|
if strings.Contains(result, "no user info saved") {
|
|
return nil, RGWErrorNotFound, errors.New("warn: s3 user not found")
|
|
}
|
|
if err != nil {
|
|
return nil, RGWErrorUnknown, errors.Wrapf(err, "radosgw-admin command err. %s", result)
|
|
}
|
|
match, err := extractJSON(result)
|
|
if err != nil {
|
|
return nil, RGWErrorParse, errors.Wrap(err, "failed to get json")
|
|
}
|
|
return decodeUser(match)
|
|
}
|
|
|
|
// CreateUser creates a new user with the information given.
|
|
// The function is used **ONCE** only to provision so the RGW Admin Ops User
|
|
// Subsequent interaction with the API will be done with the created user
|
|
func CreateUser(c *Context, user ObjectUser, force bool) (*ObjectUser, int, error) {
|
|
log.NamedDebug(c.NsName(), logger, "creating s3 user %q", user.UserID)
|
|
timeout := exec.CephCommandsTimeout
|
|
if user.UserID == RGWAdminOpsUserSecretName {
|
|
// Setting a really long timeout for the command that creates the admin user
|
|
// This is a workaround until we find what is the root cause that the creation
|
|
// is commonly taking about two minutes, instead of sub-second as in previous releases.
|
|
timeout = 10 * time.Minute
|
|
}
|
|
|
|
if strings.TrimSpace(user.UserID) == "" {
|
|
return nil, RGWErrorBadData, errors.New("userId cannot be empty")
|
|
}
|
|
|
|
if user.DisplayName == nil {
|
|
return nil, RGWErrorBadData, errors.New("displayName is required")
|
|
}
|
|
|
|
args := []string{
|
|
"user",
|
|
"create",
|
|
"--uid", user.UserID,
|
|
"--display-name", *user.DisplayName,
|
|
}
|
|
|
|
if user.Email != nil {
|
|
args = append(args, "--email", *user.Email)
|
|
}
|
|
|
|
if user.SystemUser {
|
|
args = append(args, "--system")
|
|
}
|
|
|
|
if user.AdminOpsUser {
|
|
args = append(args, "--caps", rgwAdminOpsUserCaps)
|
|
}
|
|
|
|
if user.AccessKey != nil {
|
|
args = append(args, "--access-key", *user.AccessKey)
|
|
}
|
|
|
|
if user.SecretKey != nil {
|
|
args = append(args, "--secret", *user.SecretKey)
|
|
}
|
|
|
|
if force {
|
|
args = append(args, "--yes-i-really-mean-it")
|
|
}
|
|
|
|
result, err := runAdminCommandWithTimeout(c, true, timeout, args...)
|
|
if err != nil {
|
|
if code, err := exec.ExtractExitCode(err); err == nil && code == int(syscall.EEXIST) {
|
|
return nil, ErrorCodeFileExists, errors.New("s3 user already exists")
|
|
}
|
|
|
|
if strings.Contains(result, "could not create user: unable to create user, user: ") {
|
|
return nil, ErrorCodeFileExists, errors.New("s3 user already exists")
|
|
}
|
|
|
|
if strings.Contains(result, "could not create user: unable to create user, email: ") && strings.Contains(result, " is the email address an existing user") {
|
|
return nil, RGWErrorBadData, errors.New("email already in use")
|
|
}
|
|
|
|
if strings.Contains(result, "global_init: unable to open config file from search list") {
|
|
return nil, RGWErrorUnknown, errors.New("skipping reconcile since operator is still initializing")
|
|
}
|
|
|
|
// We don't know what happened
|
|
return nil, RGWErrorUnknown, errors.Wrapf(err, "failed to create s3 user. %s", result)
|
|
}
|
|
return decodeUser(result)
|
|
}
|
|
|
|
// CreateOrRecreateUserIfExists if the user doesn't exist, it is created, should it already exist it is deleted and re-created
|
|
// It is called from the rgw dashboard setup logic.
|
|
func CreateOrRecreateUserIfExists(c *Context, user ObjectUser, force bool) (*ObjectUser, int, error) {
|
|
objUser, errCode, err := CreateUser(c, user, force)
|
|
if err != nil || (errCode != ErrorCodeFileExists && errCode != RGWErrorNone) {
|
|
return nil, errCode, err
|
|
}
|
|
|
|
switch errCode {
|
|
case RGWErrorNone:
|
|
return objUser, errCode, err
|
|
case ErrorCodeFileExists:
|
|
// If the user already exists, delete and re-create it
|
|
_, err := DeleteUser(c, user.UserID)
|
|
if err != nil {
|
|
return nil, RGWErrorUnknown, err
|
|
}
|
|
}
|
|
|
|
return CreateUser(c, user, force)
|
|
}
|
|
|
|
func ListUserBuckets(c *Context, id string, opts ...string) (string, error) {
|
|
args := []string{"bucket", "list", "--uid", id}
|
|
if opts != nil {
|
|
args = append(args, opts...)
|
|
}
|
|
|
|
result, err := runAdminCommand(c, false, args...)
|
|
|
|
return result, errors.Wrapf(err, "failed to list buckets for user uid=%q", id)
|
|
}
|
|
|
|
// DeleteUser deletes the user with the given ID.
|
|
// Even though we should be using the Admin Ops API, we keep this on purpose until the entire migration is completed
|
|
// Used for the dashboard user
|
|
func DeleteUser(c *Context, id string, opts ...string) (string, error) {
|
|
args := []string{"user", "rm", "--uid", id}
|
|
if opts != nil {
|
|
args = append(args, opts...)
|
|
}
|
|
result, err := runAdminCommand(c, false, args...)
|
|
if err != nil {
|
|
// If User does not exist return success
|
|
if code, ok := exec.ExitStatus(err); ok && code == int(syscall.ENOENT) {
|
|
return result, nil
|
|
}
|
|
|
|
res, innerErr := ListUserBuckets(c, id)
|
|
if innerErr == nil && res != "" && res != "[]" {
|
|
return result, errors.Wrapf(err, "s3 user uid=%q have following buckets %q", id, res)
|
|
}
|
|
}
|
|
|
|
return result, errors.Wrapf(err, "failed to delete s3 user uid=%q", id)
|
|
}
|