feat: added bootstrap to CLI

This commit is contained in:
Sheen Capadngan
2025-03-18 02:27:42 +08:00
parent 2d1d6f5ce8
commit 64569ab44b
6 changed files with 149 additions and 14 deletions

View File

@@ -401,22 +401,22 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/initialize",
url: "/bootstrap",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
password: z.string().trim(),
organizationName: z.string().trim()
email: z.string().email().trim().min(1),
password: z.string().trim().min(1),
organization: z.string().trim().min(1)
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
organization: OrganizationsSchema,
machineIdentity: IdentitiesSchema.extend({
identity: IdentitiesSchema.extend({
credentials: z.object({
token: z.string()
}) // would just be Token AUTH for now
@@ -425,8 +425,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { user, organization, machineIdentity } = await server.services.superAdmin.initializeInstance({
...req.body
const { user, organization, machineIdentity } = await server.services.superAdmin.bootstrapInstance({
...req.body,
organizationName: req.body.organization
});
await server.services.telemetry.sendPostHogEvents({
@@ -441,10 +442,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
});
return {
message: "Successfully initialized instance",
message: "Successfully boostrapped instance",
user: user.user,
organization,
machineIdentity
identity: machineIdentity
};
}
});

View File

@@ -27,9 +27,9 @@ import { UserAliasType } from "../user-alias/user-alias-types";
import { TSuperAdminDALFactory } from "./super-admin-dal";
import {
LoginMethod,
TAdminBoostrapInstanceDTO,
TAdminGetIdentitiesDTO,
TAdminGetUsersDTO,
TAdminInitializeInstanceDTO,
TAdminSignUpDTO
} from "./super-admin-types";
@@ -291,7 +291,7 @@ export const superAdminServiceFactory = ({
return { token, user: userInfo, organization };
};
const initializeInstance = async ({ email, password, organizationName }: TAdminInitializeInstanceDTO) => {
const bootstrapInstance = async ({ email, password, organizationName }: TAdminBoostrapInstanceDTO) => {
const appCfg = getConfig();
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
if (serverCfg?.initialized) {
@@ -352,7 +352,7 @@ export const superAdminServiceFactory = ({
});
const { identity, credentials } = await identityDAL.transaction(async (tx) => {
const newIdentity = await identityDAL.create({ name: "Admin Identity" }, tx);
const newIdentity = await identityDAL.create({ name: "Instance Admin Identity" }, tx);
await identityOrgMembershipDAL.create(
{
identityId: newIdentity.id,
@@ -547,7 +547,7 @@ export const superAdminServiceFactory = ({
initServerCfg,
updateServerCfg,
adminSignUp,
initializeInstance,
bootstrapInstance,
getUsers,
deleteUser,
getIdentities,

View File

@@ -16,7 +16,7 @@ export type TAdminSignUpDTO = {
userAgent: string;
};
export type TAdminInitializeInstanceDTO = {
export type TAdminBoostrapInstanceDTO = {
email: string;
password: string;
organizationName: string;

View File

@@ -600,3 +600,23 @@ func CallGatewayHeartBeatV1(httpClient *resty.Client) error {
return nil
}
func CallBootstrapInstance(httpClient *resty.Client, request BootstrapInstanceRequest) (*BootstrapInstanceResponse, error) {
var resBody BootstrapInstanceResponse
response, err := httpClient.
R().
SetResult(&resBody).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v1/admin/bootstrap", request.Domain))
if err != nil {
return nil, fmt.Errorf("CallBootstrapInstance: Unable to complete api request [err=%w]", err)
}
if response.IsError() {
return nil, fmt.Errorf("CallBootstrapInstance: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
}
return &resBody, nil
}

View File

@@ -654,3 +654,27 @@ type ExchangeRelayCertResponseV1 struct {
Certificate string `json:"certificate"`
CertificateChain string `json:"certificateChain"`
}
type BootstrapInstanceRequest struct {
Email string `json:"email"`
Password string `json:"password"`
Organization string `json:"organization"`
Domain string `json:"domain"`
}
type BootstrapInstanceResponseOrganization struct {
ID string `json:"id"`
Name string `json:"name"`
}
type BootstrapInstanceResponseIdentity struct {
ID string `json:"id"`
Name string `json:"name"`
Credentials interface{} `json:"credentials"`
}
type BootstrapInstanceResponse struct {
Message string `json:"message"`
Organization BootstrapInstanceResponseOrganization `json:"organization"`
Identity BootstrapInstanceResponseIdentity `json:"identity"`
}

View File

@@ -0,0 +1,90 @@
/*
Copyright (c) 2023 Infisical Inc.
*/
package cmd
import (
"encoding/json"
"fmt"
"os"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/go-resty/resty/v2"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var bootstrapCmd = &cobra.Command{
Use: "bootstrap",
Short: "Used to bootstrap your Infisical instance",
DisableFlagsInUseLine: true,
Example: "infisical bootstrap",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
email, _ := cmd.Flags().GetString("email")
if email == "" {
if envEmail, ok := os.LookupEnv("INFISICAL_ADMIN_EMAIL"); ok {
email = envEmail
}
}
if email == "" {
log.Error().Msg("email is required")
return
}
password, _ := cmd.Flags().GetString("password")
if password == "" {
if envPassword, ok := os.LookupEnv("INFISICAL_ADMIN_PASSWORD"); ok {
password = envPassword
}
}
if password == "" {
log.Error().Msg("password is required")
return
}
organization, _ := cmd.Flags().GetString("organization")
if organization == "" {
log.Error().Msg("organization is required")
return
}
domain, _ := cmd.Flags().GetString("domain")
if domain == "" {
log.Error().Msg("domain is required")
return
}
httpClient := resty.New().
SetHeader("Accept", "application/json")
bootstrapResponse, err := api.CallBootstrapInstance(httpClient, api.BootstrapInstanceRequest{
Domain: util.AppendAPIEndpoint(domain),
Email: email,
Password: password,
Organization: organization,
})
if err != nil {
log.Error().Msgf("Failed to bootstrap instance: %v", err)
}
responseJSON, err := json.MarshalIndent(bootstrapResponse, "", " ")
if err != nil {
log.Fatal().Msgf("Failed to convert response to JSON: %v", err)
}
fmt.Println(string(responseJSON))
},
}
func init() {
bootstrapCmd.Flags().String("domain", "", "The domain of your self-hosted Infisical instance")
bootstrapCmd.Flags().String("email", "", "The desired email address of the instance admin")
bootstrapCmd.Flags().String("password", "", "The desired password of the instance admin")
bootstrapCmd.Flags().String("organization", "", "The name of the organization to create for the instance")
rootCmd.AddCommand(bootstrapCmd)
}