Skip to content

Decouple keypair preparation logic from GraphQL #4359

Closed
@seedspirit

Description

@seedspirit

Motivation  

  • The Action, Service structure defines common behaviors that do not have REST or GraphQL dependencies. However, in the create_user method of the UserService, you notice that a graphql-defined behavior is being used, and you want to remove it
  • As prepare_new_keypair is common behavior, it needs to be extract as standalone utility function

Required Features

async def _post_func(conn: SAConnection, result: Result) -> Optional[Row]:
            if result.rowcount == 0:
                return None
            created_user = result.first()

            # Create a default keypair for the user.
            email = action.input.email
            kp_data = CreateKeyPair.prepare_new_keypair(
                email,
                {
                    "is_active": _status == UserStatus.ACTIVE,
                    "is_admin": user_data["role"] in [UserRole.SUPERADMIN, UserRole.ADMIN],
                    "resource_policy": DEFAULT_KEYPAIR_RESOURCE_POLICY_NAME,
                    "rate_limit": DEFAULT_KEYPAIR_RATE_LIMIT,
                },
            )
            kp_insert_query = sa.insert(keypairs).values(
                **kp_data,
                user=created_user.uuid,
            )

class CreateKeyPair(graphene.Mutation):
    allowed_roles = (UserRole.SUPERADMIN,)

    class Arguments:
        user_id = graphene.String(required=True)
        props = KeyPairInput(required=True)

    ok = graphene.Boolean()
    msg = graphene.String()
    keypair = graphene.Field(lambda: KeyPair, required=False)

    @classmethod
    async def mutate(
        cls,
        root,
        info: graphene.ResolveInfo,
        user_id: str,
        props: KeyPairInput,
    ) -> CreateKeyPair:
        from .user import users  # noqa

        graph_ctx: GraphQueryContext = info.context
        data = cls.prepare_new_keypair(
            user_id,
            {
                "is_active": props.is_active,
                "is_admin": props.is_admin,
                "resource_policy": props.resource_policy,
                "rate_limit": props.rate_limit,
                # props.concurrency_limit is always ignored
            },
        )
        insert_query = sa.insert(keypairs).values(
            **data,
            user=sa.select([users.c.uuid]).where(users.c.email == user_id).as_scalar(),
        )
        return await simple_db_mutate_returning_item(cls, graph_ctx, insert_query, item_cls=KeyPair)

    @classmethod
    def prepare_new_keypair(cls, user_email: str, props: KeyPairInputTD) -> Dict[str, Any]:
        ak, sk = generate_keypair()
        pubkey, privkey = generate_ssh_keypair()
        data = {
            "user_id": user_email,
            "access_key": ak,
            "secret_key": sk,
            "is_active": props["is_active"],
            "is_admin": props["is_admin"],
            "resource_policy": props["resource_policy"],
            "rate_limit": props["rate_limit"],
            "num_queries": 0,
            "ssh_public_key": pubkey,
            "ssh_private_key": privkey,
        }
        return data

Impact  

  • Organize code better fit the original intent of Action / Service structure

Testing Scenarios  

  • Verify that keypairs are created correctly when a user is created

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions