Add names to init output (#428)

pull/429/head
Jeff Mitchell 6 years ago committed by GitHub
parent 7fcbffbe88
commit 41a3c66ea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,36 +15,39 @@ import (
"github.com/hashicorp/vault/sdk/helper/base62"
)
func (b *Server) CreateInitialAuthMethod(ctx context.Context) error {
func (b *Server) CreateInitialAuthMethod(ctx context.Context) (*password.AuthMethod, *iam.User, error) {
rw := db.New(b.Database)
kmsRepo, err := kms.NewRepository(rw, rw)
if err != nil {
return fmt.Errorf("error creating kms repository: %w", err)
return nil, nil, fmt.Errorf("error creating kms repository: %w", err)
}
kmsCache, err := kms.NewKms(kmsRepo, kms.WithLogger(b.Logger.Named("kms")))
if err != nil {
return fmt.Errorf("error creating kms cache: %w", err)
return nil, nil, fmt.Errorf("error creating kms cache: %w", err)
}
if err := kmsCache.AddExternalWrappers(
kms.WithRootWrapper(b.RootKms),
); err != nil {
return fmt.Errorf("error adding config keys to kms: %w", err)
return nil, nil, fmt.Errorf("error adding config keys to kms: %w", err)
}
// Create the dev auth method
pwRepo, err := password.NewRepository(rw, rw, kmsCache)
if err != nil {
return fmt.Errorf("error creating password repo: %w", err)
return nil, nil, fmt.Errorf("error creating password repo: %w", err)
}
authMethod, err := password.NewAuthMethod(scope.Global.String())
authMethod, err := password.NewAuthMethod(scope.Global.String(),
password.WithName("Generated global scope initial auth method"),
password.WithDescription("Provides initial administrative authentication into Boundary"),
)
if err != nil {
return fmt.Errorf("error creating new in memory auth method: %w", err)
return nil, nil, fmt.Errorf("error creating new in memory auth method: %w", err)
}
if b.DevAuthMethodId == "" {
b.DevAuthMethodId, err = db.NewPublicId(password.AuthMethodPrefix)
if err != nil {
return fmt.Errorf("error generating initial auth method id: %w", err)
return nil, nil, fmt.Errorf("error generating initial auth method id: %w", err)
}
}
@ -54,12 +57,10 @@ func (b *Server) CreateInitialAuthMethod(ctx context.Context) error {
cancel()
}()
_, err = pwRepo.CreateAuthMethod(cancelCtx, authMethod,
password.WithName("Generated global scope initial auth method"),
password.WithDescription("Provides initial administrative authentication into Boundary"),
am, err := pwRepo.CreateAuthMethod(cancelCtx, authMethod,
password.WithPublicId(b.DevAuthMethodId))
if err != nil {
return fmt.Errorf("error saving auth method to the db: %w", err)
return nil, nil, fmt.Errorf("error saving auth method to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "generated auth method id")
b.Info["generated auth method id"] = b.DevAuthMethodId
@ -68,14 +69,14 @@ func (b *Server) CreateInitialAuthMethod(ctx context.Context) error {
if b.DevLoginName == "" {
b.DevLoginName, err = base62.Random(10)
if err != nil {
return fmt.Errorf("unable to generate login name: %w", err)
return nil, nil, fmt.Errorf("unable to generate login name: %w", err)
}
b.DevLoginName = strings.ToLower(b.DevLoginName)
}
if b.DevPassword == "" {
b.DevPassword, err = base62.Random(20)
if err != nil {
return fmt.Errorf("unable to generate password: %w", err)
return nil, nil, fmt.Errorf("unable to generate password: %w", err)
}
}
b.InfoKeys = append(b.InfoKeys, "generated auth method password")
@ -83,25 +84,25 @@ func (b *Server) CreateInitialAuthMethod(ctx context.Context) error {
acct, err := password.NewAccount(b.DevAuthMethodId, password.WithLoginName(b.DevLoginName))
if err != nil {
return fmt.Errorf("error creating new in memory auth account: %w", err)
return nil, nil, fmt.Errorf("error creating new in memory auth account: %w", err)
}
acct, err = pwRepo.CreateAccount(cancelCtx, scope.Global.String(), acct, password.WithPassword(b.DevPassword))
if err != nil {
return fmt.Errorf("error saving auth account to the db: %w", err)
return nil, nil, fmt.Errorf("error saving auth account to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "generated auth method login name")
b.Info["generated auth method login name"] = acct.GetLoginName()
iamRepo, err := iam.NewRepository(rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader))
if err != nil {
return fmt.Errorf("unable to create repo for org id: %w", err)
return nil, nil, fmt.Errorf("unable to create repo for org id: %w", err)
}
// Create a new user and associate it with the account
if b.DevUserId == "" {
b.DevUserId, err = db.NewPublicId(iam.UserPrefix)
if err != nil {
return fmt.Errorf("error generating initial user id: %w", err)
return nil, nil, fmt.Errorf("error generating initial user id: %w", err)
}
}
opts := []iam.Option{
@ -111,13 +112,13 @@ func (b *Server) CreateInitialAuthMethod(ctx context.Context) error {
}
u, err := iam.NewUser(scope.Global.String(), opts...)
if err != nil {
return fmt.Errorf("error creating in memory user: %w", err)
return nil, nil, fmt.Errorf("error creating in memory user: %w", err)
}
if u, err = iamRepo.CreateUser(cancelCtx, u, opts...); err != nil {
return fmt.Errorf("error creating initial admin user: %w", err)
return nil, nil, fmt.Errorf("error creating initial admin user: %w", err)
}
if u, err = iamRepo.AssociateUserWithAccount(cancelCtx, u.GetPublicId(), acct.GetPublicId()); err != nil {
return fmt.Errorf("error associating initial admin user with account: %w", err)
return nil, nil, fmt.Errorf("error associating initial admin user with account: %w", err)
}
// Create a role tying them together
@ -126,37 +127,37 @@ func (b *Server) CreateInitialAuthMethod(ctx context.Context) error {
iam.WithDescription(`Provides admin grants within the "global" scope to the initial user`),
)
if err != nil {
return fmt.Errorf("error creating in memory role for generated grants: %w", err)
return nil, nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
}
defPermsRole, err := iamRepo.CreateRole(cancelCtx, pr)
if err != nil {
return fmt.Errorf("error creating role for default generated grants: %w", err)
return nil, nil, fmt.Errorf("error creating role for default generated grants: %w", err)
}
if _, err := iamRepo.AddRoleGrants(cancelCtx, defPermsRole.PublicId, defPermsRole.Version, []string{"id=*;actions=*"}); err != nil {
return fmt.Errorf("error creating grant for default generated grants: %w", err)
return nil, nil, fmt.Errorf("error creating grant for default generated grants: %w", err)
}
if _, err := iamRepo.AddPrincipalRoles(cancelCtx, defPermsRole.PublicId, defPermsRole.Version+1, []string{u.GetPublicId()}, nil); err != nil {
return fmt.Errorf("error adding principal to role for default generated grants: %w", err)
return nil, nil, fmt.Errorf("error adding principal to role for default generated grants: %w", err)
}
return nil
return am, u, nil
}
func (b *Server) CreateInitialScopes(ctx context.Context) error {
func (b *Server) CreateInitialScopes(ctx context.Context) (*iam.Scope, *iam.Scope, error) {
rw := db.New(b.Database)
kmsRepo, err := kms.NewRepository(rw, rw)
if err != nil {
return fmt.Errorf("error creating kms repository: %w", err)
return nil, nil, fmt.Errorf("error creating kms repository: %w", err)
}
kmsCache, err := kms.NewKms(kmsRepo, kms.WithLogger(b.Logger.Named("kms")))
if err != nil {
return fmt.Errorf("error creating kms cache: %w", err)
return nil, nil, fmt.Errorf("error creating kms cache: %w", err)
}
if err := kmsCache.AddExternalWrappers(
kms.WithRootWrapper(b.RootKms),
); err != nil {
return fmt.Errorf("error adding config keys to kms: %w", err)
return nil, nil, fmt.Errorf("error adding config keys to kms: %w", err)
}
cancelCtx, cancel := context.WithCancel(ctx)
@ -167,14 +168,14 @@ func (b *Server) CreateInitialScopes(ctx context.Context) error {
iamRepo, err := iam.NewRepository(rw, rw, kmsCache)
if err != nil {
return fmt.Errorf("error creating scopes repository: %w", err)
return nil, nil, fmt.Errorf("error creating scopes repository: %w", err)
}
// Create the scopes
if b.DevOrgId == "" {
b.DevOrgId, err = db.NewPublicId(scope.Org.Prefix())
if err != nil {
return fmt.Errorf("error generating initial org id: %w", err)
return nil, nil, fmt.Errorf("error generating initial org id: %w", err)
}
}
opts := []iam.Option{
@ -185,11 +186,11 @@ func (b *Server) CreateInitialScopes(ctx context.Context) error {
}
orgScope, err := iam.NewOrg(opts...)
if err != nil {
return fmt.Errorf("error creating new in memory org scope: %w", err)
return nil, nil, fmt.Errorf("error creating new in memory org scope: %w", err)
}
_, err = iamRepo.CreateScope(cancelCtx, orgScope, b.DevUserId, opts...)
orgScope, err = iamRepo.CreateScope(cancelCtx, orgScope, b.DevUserId, opts...)
if err != nil {
return fmt.Errorf("error saving org scope to the db: %w", err)
return nil, nil, fmt.Errorf("error saving org scope to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "generated org scope id")
b.Info["generated org scope id"] = b.DevOrgId
@ -197,7 +198,7 @@ func (b *Server) CreateInitialScopes(ctx context.Context) error {
if b.DevProjectId == "" {
b.DevProjectId, err = db.NewPublicId(scope.Project.Prefix())
if err != nil {
return fmt.Errorf("error generating initial project id: %w", err)
return nil, nil, fmt.Errorf("error generating initial project id: %w", err)
}
}
opts = []iam.Option{
@ -208,33 +209,33 @@ func (b *Server) CreateInitialScopes(ctx context.Context) error {
}
projScope, err := iam.NewProject(b.DevOrgId, opts...)
if err != nil {
return fmt.Errorf("error creating new in memory project scope: %w", err)
return nil, nil, fmt.Errorf("error creating new in memory project scope: %w", err)
}
_, err = iamRepo.CreateScope(cancelCtx, projScope, b.DevUserId, opts...)
projScope, err = iamRepo.CreateScope(cancelCtx, projScope, b.DevUserId, opts...)
if err != nil {
return fmt.Errorf("error saving project scope to the db: %w", err)
return nil, nil, fmt.Errorf("error saving project scope to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "generated project scope id")
b.Info["generated project scope id"] = b.DevProjectId
return nil
return orgScope, projScope, nil
}
func (b *Server) CreateInitialHostResources(ctx context.Context) error {
func (b *Server) CreateInitialHostResources(ctx context.Context) (*static.HostCatalog, *static.HostSet, *static.Host, error) {
rw := db.New(b.Database)
kmsRepo, err := kms.NewRepository(rw, rw)
if err != nil {
return fmt.Errorf("error creating kms repository: %w", err)
return nil, nil, nil, fmt.Errorf("error creating kms repository: %w", err)
}
kmsCache, err := kms.NewKms(kmsRepo, kms.WithLogger(b.Logger.Named("kms")))
if err != nil {
return fmt.Errorf("error creating kms cache: %w", err)
return nil, nil, nil, fmt.Errorf("error creating kms cache: %w", err)
}
if err := kmsCache.AddExternalWrappers(
kms.WithRootWrapper(b.RootKms),
); err != nil {
return fmt.Errorf("error adding config keys to kms: %w", err)
return nil, nil, nil, fmt.Errorf("error adding config keys to kms: %w", err)
}
cancelCtx, cancel := context.WithCancel(ctx)
@ -245,14 +246,14 @@ func (b *Server) CreateInitialHostResources(ctx context.Context) error {
staticRepo, err := static.NewRepository(rw, rw, kmsCache)
if err != nil {
return fmt.Errorf("error creating static repository: %w", err)
return nil, nil, nil, fmt.Errorf("error creating static repository: %w", err)
}
// Host Catalog
if b.DevHostCatalogId == "" {
b.DevHostCatalogId, err = db.NewPublicId(static.HostCatalogPrefix)
if err != nil {
return fmt.Errorf("error generating initial host catalog id: %w", err)
return nil, nil, nil, fmt.Errorf("error generating initial host catalog id: %w", err)
}
}
opts := []static.Option{
@ -262,10 +263,10 @@ func (b *Server) CreateInitialHostResources(ctx context.Context) error {
}
hc, err := static.NewHostCatalog(b.DevProjectId, opts...)
if err != nil {
return fmt.Errorf("error creating in memory host catalog: %w", err)
return nil, nil, nil, fmt.Errorf("error creating in memory host catalog: %w", err)
}
if hc, err = staticRepo.CreateCatalog(cancelCtx, hc, opts...); err != nil {
return fmt.Errorf("error saving host catalog to the db: %w", err)
return nil, nil, nil, fmt.Errorf("error saving host catalog to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "generated host catalog id")
b.Info["generated host catalog id"] = b.DevHostCatalogId
@ -274,7 +275,7 @@ func (b *Server) CreateInitialHostResources(ctx context.Context) error {
if b.DevHostId == "" {
b.DevHostId, err = db.NewPublicId(static.HostPrefix)
if err != nil {
return fmt.Errorf("error generating initial host id: %w", err)
return nil, nil, nil, fmt.Errorf("error generating initial host id: %w", err)
}
}
if b.DevHostAddress == "" {
@ -288,10 +289,10 @@ func (b *Server) CreateInitialHostResources(ctx context.Context) error {
}
h, err := static.NewHost(hc.PublicId, opts...)
if err != nil {
return fmt.Errorf("error creating in memory host: %w", err)
return nil, nil, nil, fmt.Errorf("error creating in memory host: %w", err)
}
if h, err = staticRepo.CreateHost(cancelCtx, b.DevProjectId, h, opts...); err != nil {
return fmt.Errorf("error saving host to the db: %w", err)
return nil, nil, nil, fmt.Errorf("error saving host to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "generated host id")
b.Info["generated host id"] = b.DevHostId
@ -300,7 +301,7 @@ func (b *Server) CreateInitialHostResources(ctx context.Context) error {
if b.DevHostSetId == "" {
b.DevHostSetId, err = db.NewPublicId(static.HostSetPrefix)
if err != nil {
return fmt.Errorf("error generating initial host set id: %w", err)
return nil, nil, nil, fmt.Errorf("error generating initial host set id: %w", err)
}
}
opts = []static.Option{
@ -310,20 +311,20 @@ func (b *Server) CreateInitialHostResources(ctx context.Context) error {
}
hs, err := static.NewHostSet(hc.PublicId, opts...)
if err != nil {
return fmt.Errorf("error creating in memory host set: %w", err)
return nil, nil, nil, fmt.Errorf("error creating in memory host set: %w", err)
}
if hs, err = staticRepo.CreateSet(cancelCtx, b.DevProjectId, hs, opts...); err != nil {
return fmt.Errorf("error saving host set to the db: %w", err)
return nil, nil, nil, fmt.Errorf("error saving host set to the db: %w", err)
}
b.InfoKeys = append(b.InfoKeys, "generated host set id")
b.Info["generated host set id"] = b.DevHostSetId
// Associate members
if _, err := staticRepo.AddSetMembers(cancelCtx, b.DevProjectId, b.DevHostSetId, hs.GetVersion(), []string{h.GetPublicId()}); err != nil {
return fmt.Errorf("error associating host set to host in the db: %w", err)
return nil, nil, nil, fmt.Errorf("error associating host set to host in the db: %w", err)
}
return nil
return hc, hs, h, nil
}
func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error) {

@ -481,7 +481,7 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error {
return nil
}
if err := b.CreateInitialAuthMethod(context.Background()); err != nil {
if _, _, err := b.CreateInitialAuthMethod(context.Background()); err != nil {
return err
}
@ -492,7 +492,7 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error {
return nil
}
if err := b.CreateInitialScopes(context.Background()); err != nil {
if _, _, err := b.CreateInitialScopes(context.Background()); err != nil {
return err
}
@ -503,7 +503,7 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error {
return nil
}
if err := b.CreateInitialHostResources(context.Background()); err != nil {
if _, _, _, err := b.CreateInitialHostResources(context.Background()); err != nil {
return err
}

@ -7,20 +7,24 @@ import (
)
type AuthMethodInfo struct {
AuthMethodId string `json:"auth_method_id"`
LoginName string `json:"login_name"`
Password string `json:"password"`
ScopeId string `json:"scope_id"`
UserId string `json:"user_id"`
AuthMethodId string `json:"auth_method_id"`
AuthMethodName string `json:"auth_method_name"`
LoginName string `json:"login_name"`
Password string `json:"password"`
ScopeId string `json:"scope_id"`
UserId string `json:"user_id"`
UserName string `json:"user_name"`
}
func generateInitialAuthMethodTableOutput(in *AuthMethodInfo) string {
nonAttributeMap := map[string]interface{}{
"Scope ID": in.ScopeId,
"Auth Method ID": in.AuthMethodId,
"Login Name": in.LoginName,
"Password": in.Password,
"User ID": in.UserId,
"Scope ID": in.ScopeId,
"Auth Method ID": in.AuthMethodId,
"Auth Method Name": in.AuthMethodName,
"Login Name": in.LoginName,
"Password": in.Password,
"User ID": in.UserId,
"User Name": in.UserName,
}
maxLength := 0
@ -42,12 +46,14 @@ func generateInitialAuthMethodTableOutput(in *AuthMethodInfo) string {
type ScopeInfo struct {
ScopeId string `json:"scope_id"`
Type string `json:"type"`
Name string `json:"name"`
}
func generateInitialScopeTableOutput(in *ScopeInfo) string {
nonAttributeMap := map[string]interface{}{
"Scope ID": in.ScopeId,
"Type": in.Type,
"Name": in.Name,
}
maxLength := 0
@ -67,20 +73,26 @@ func generateInitialScopeTableOutput(in *ScopeInfo) string {
}
type HostInfo struct {
HostCatalogId string `json:"host_catalog_id"`
HostSetId string `json:"host_set_id"`
HostId string `json:"host_id"`
Type string `json:"type"`
ScopeId string `json:"scope_id"`
HostCatalogId string `json:"host_catalog_id"`
HostSetId string `json:"host_set_id"`
HostId string `json:"host_id"`
Type string `json:"type"`
ScopeId string `json:"scope_id"`
HostCatalogName string `json:"host_catalog_name"`
HostSetName string `json:"host_set_name"`
HostName string `json:"host_name"`
}
func generateInitialHostResourcesTableOutput(in *HostInfo) string {
nonAttributeMap := map[string]interface{}{
"Host Catalog ID": in.HostCatalogId,
"Host Set ID": in.HostSetId,
"Host ID": in.HostId,
"Type": in.Type,
"Scope ID": in.ScopeId,
"Host Catalog ID": in.HostCatalogId,
"Host Catalog Name": in.HostCatalogName,
"Host Set ID": in.HostSetId,
"Host Set Name": in.HostSetName,
"Host ID": in.HostId,
"Host Name": in.HostName,
"Type": in.Type,
"Scope ID": in.ScopeId,
}
maxLength := 0
@ -106,6 +118,7 @@ type TargetInfo struct {
SessionConnectionLimit int32 `json:"session_connection_limit"`
Type string `json:"type"`
ScopeId string `json:"scope_id"`
Name string `json:"name"`
}
func generateInitialTargetTableOutput(in *TargetInfo) string {
@ -116,6 +129,7 @@ func generateInitialTargetTableOutput(in *TargetInfo) string {
"Session Connection Limit": in.SessionConnectionLimit,
"Type": in.Type,
"Scope ID": in.ScopeId,
"Name": in.Name,
}
maxLength := 0

@ -303,17 +303,20 @@ func (c *InitCommand) Run(args []string) (retCode int) {
// Use an easy name, at least
c.srv.DevLoginName = "admin"
if err := c.srv.CreateInitialAuthMethod(c.Context); err != nil {
am, user, err := c.srv.CreateInitialAuthMethod(c.Context)
if err != nil {
c.UI.Error(fmt.Errorf("Error creating initial auth method and user: %w", err).Error())
return 1
}
authMethodInfo := &AuthMethodInfo{
AuthMethodId: c.srv.DevAuthMethodId,
LoginName: c.srv.DevLoginName,
Password: c.srv.DevPassword,
ScopeId: scope.Global.String(),
UserId: c.srv.DevUserId,
AuthMethodId: c.srv.DevAuthMethodId,
AuthMethodName: am.Name,
LoginName: c.srv.DevLoginName,
Password: c.srv.DevPassword,
ScopeId: scope.Global.String(),
UserId: c.srv.DevUserId,
UserName: user.Name,
}
switch base.Format(c.UI) {
case "table":
@ -326,7 +329,8 @@ func (c *InitCommand) Run(args []string) (retCode int) {
return 0
}
if err := c.srv.CreateInitialScopes(c.Context); err != nil {
orgScope, projScope, err := c.srv.CreateInitialScopes(c.Context)
if err != nil {
c.UI.Error(fmt.Errorf("Error creating initial scopes: %w", err).Error())
return 1
}
@ -334,6 +338,7 @@ func (c *InitCommand) Run(args []string) (retCode int) {
orgScopeInfo := &ScopeInfo{
ScopeId: c.srv.DevOrgId,
Type: scope.Org.String(),
Name: orgScope.Name,
}
switch base.Format(c.UI) {
case "table":
@ -345,6 +350,7 @@ func (c *InitCommand) Run(args []string) (retCode int) {
projScopeInfo := &ScopeInfo{
ScopeId: c.srv.DevProjectId,
Type: scope.Project.String(),
Name: projScope.Name,
}
switch base.Format(c.UI) {
case "table":
@ -357,17 +363,21 @@ func (c *InitCommand) Run(args []string) (retCode int) {
return 0
}
if err := c.srv.CreateInitialHostResources(c.Context); err != nil {
hc, hs, h, err := c.srv.CreateInitialHostResources(c.Context)
if err != nil {
c.UI.Error(fmt.Errorf("Error creating initial host resources: %w", err).Error())
return 1
}
hostInfo := &HostInfo{
HostCatalogId: c.srv.DevHostCatalogId,
HostSetId: c.srv.DevHostSetId,
HostId: c.srv.DevHostId,
Type: "static",
ScopeId: c.srv.DevProjectId,
HostCatalogId: c.srv.DevHostCatalogId,
HostCatalogName: hc.GetName(),
HostSetId: c.srv.DevHostSetId,
HostSetName: hs.GetName(),
HostId: c.srv.DevHostId,
HostName: h.GetName(),
Type: "static",
ScopeId: c.srv.DevProjectId,
}
switch base.Format(c.UI) {
case "table":
@ -393,6 +403,7 @@ func (c *InitCommand) Run(args []string) (retCode int) {
SessionConnectionLimit: t.GetSessionConnectionLimit(),
Type: "tcp",
ScopeId: c.srv.DevProjectId,
Name: t.GetName(),
}
switch base.Format(c.UI) {
case "table":

@ -395,15 +395,15 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
t.Fatal(err)
}
if !opts.DisableAuthMethodCreation {
if err := tc.b.CreateInitialAuthMethod(ctx); err != nil {
if _, _, err := tc.b.CreateInitialAuthMethod(ctx); err != nil {
t.Fatal(err)
}
if !opts.DisableScopesCreation {
if err := tc.b.CreateInitialScopes(ctx); err != nil {
if _, _, err := tc.b.CreateInitialScopes(ctx); err != nil {
t.Fatal(err)
}
if !opts.DisableHostResourcesCreation {
if err := tc.b.CreateInitialHostResources(ctx); err != nil {
if _, _, _, err := tc.b.CreateInitialHostResources(ctx); err != nil {
t.Fatal(err)
}
if !opts.DisableTargetCreation {

Loading…
Cancel
Save