DodoApp: Implement user synchronization API
Change-Id: Id38c96f379832d2d5034e215de2e51d28a25634c
diff --git a/charts/dodo-app/templates/install.yaml b/charts/dodo-app/templates/install.yaml
index d49a6d1..58c920c 100644
--- a/charts/dodo-app/templates/install.yaml
+++ b/charts/dodo-app/templates/install.yaml
@@ -129,6 +129,7 @@
- --db=/dodo-app/db/apps.db
- --networks={{ .Values.allowedNetworks }}
- --external={{ .Values.external }}
+ - --fetch-users-addr={{ .Values.fetchUsersAddr }}
volumeMounts:
- name: ssh-key
readOnly: true
diff --git a/charts/dodo-app/values.yaml b/charts/dodo-app/values.yaml
index 2fba879..b1d57c4 100644
--- a/charts/dodo-app/values.yaml
+++ b/charts/dodo-app/values.yaml
@@ -16,3 +16,4 @@
persistentVolumeClaimName: ""
allowedNetworks: ""
external: false
+fetchUsersAddr: ""
diff --git a/core/installer/cmd/dodo_app.go b/core/installer/cmd/dodo_app.go
index f6d9c43..ef62f5c 100644
--- a/core/installer/cmd/dodo_app.go
+++ b/core/installer/cmd/dodo_app.go
@@ -30,6 +30,7 @@
gitRepoPublicKey string
db string
networks []string
+ fetchUsersAddr string
}
func dodoAppCmd() *cobra.Command {
@@ -80,6 +81,12 @@
"",
)
cmd.Flags().StringVar(
+ &dodoAppFlags.fetchUsersAddr,
+ "fetch-users-addr",
+ "",
+ "",
+ )
+ cmd.Flags().StringVar(
&dodoAppFlags.repoPublicAddr,
"repo-public-addr",
"",
@@ -195,7 +202,8 @@
nsc,
jc,
env,
- !dodoAppFlags.external,
+ dodoAppFlags.external,
+ dodoAppFlags.fetchUsersAddr,
)
if err != nil {
return err
diff --git a/core/installer/soft/client.go b/core/installer/soft/client.go
index a5cfa31..3e58279 100644
--- a/core/installer/soft/client.go
+++ b/core/installer/soft/client.go
@@ -28,6 +28,7 @@
GetPublicKeys() ([]string, error)
RepoExists(name string) (bool, error)
GetRepo(name string) (RepoIO, error)
+ GetAllRepos() ([]string, error)
GetRepoAddress(name string) string
AddRepository(name string) error
UserExists(name string) (bool, error)
@@ -222,6 +223,15 @@
return NewRepoIO(r, ss.signer)
}
+func (ss *realClient) GetAllRepos() ([]string, error) {
+ log.Printf("Getting all repos")
+ out, err := ss.RunCommand("repo", "list")
+ if err != nil {
+ return nil, err
+ }
+ return strings.Fields(out), nil
+}
+
type RepositoryAddress struct {
Addr string
Name string
diff --git a/core/installer/values-tmpl/dodo-app.cue b/core/installer/values-tmpl/dodo-app.cue
index 7e2a1a7..cfd0933 100644
--- a/core/installer/values-tmpl/dodo-app.cue
+++ b/core/installer/values-tmpl/dodo-app.cue
@@ -129,6 +129,7 @@
persistentVolumeClaimName: volumes.db.name
allowedNetworks: strings.Join([for n in input.allowedNetworks { n.name }], ",")
external: input.external
+ fetchUsersAddr: "http://memberships-api.\(global.namespacePrefix)core-auth-memberships.svc.cluster.local/api/users"
}
}
}
diff --git a/core/installer/welcome/dodo-app-tmpl/base.html b/core/installer/welcome/dodo-app-tmpl/base.html
index 85b480e..9cb4e07 100644
--- a/core/installer/welcome/dodo-app-tmpl/base.html
+++ b/core/installer/welcome/dodo-app-tmpl/base.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ block "title" . }}{{ end }}</title>
<link rel="stylesheet" href="/static/pico.2.0.6.min.css">
- <link rel="stylesheet" href="/static/dodo_app.css?v=0.0.3">
+ <link rel="stylesheet" href="/static/dodo_app.css?v=0.0.4">
</head>
<body class="container">
{{- block "content" . }}
diff --git a/core/installer/welcome/dodo-app-tmpl/index.html b/core/installer/welcome/dodo-app-tmpl/index.html
index ff53b2a..9874d12 100644
--- a/core/installer/welcome/dodo-app-tmpl/index.html
+++ b/core/installer/welcome/dodo-app-tmpl/index.html
@@ -15,7 +15,6 @@
<option value="{{ . }}">{{ . }}</option>
{{- end -}}
</select>
- <input type="text" name="admin-public-key" placeholder="Admin Public Key" />
<button type="submit" name="create-app">Create App</button>
</fieldset>
</form>
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 87b003a..488047c 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -15,6 +15,7 @@
"slices"
"strings"
"sync"
+ "time"
"github.com/giolekva/pcloud/core/installer"
"github.com/giolekva/pcloud/core/installer/soft"
@@ -93,7 +94,8 @@
appConfigs map[string]appConfig
tmplts dodoAppTmplts
appTmpls AppTmplStore
- allowNetworkReuse bool
+ external bool
+ fetchUsersAddr string
}
type appConfig struct {
@@ -118,7 +120,8 @@
nsc installer.NamespaceCreator,
jc installer.JobCreator,
env installer.EnvConfig,
- allowNetworkReuse bool,
+ external bool,
+ fetchUsersAddr string,
) (*DodoAppServer, error) {
tmplts, err := parseTemplatesDodoApp(dodoAppTmplFS)
if err != nil {
@@ -153,7 +156,8 @@
map[string]appConfig{},
tmplts,
appTmpls,
- allowNetworkReuse,
+ external,
+ fetchUsersAddr,
}
config, err := client.GetRepo(ConfigRepoName)
if err != nil {
@@ -192,8 +196,23 @@
r.HandleFunc("/update", s.handleAPIUpdate)
r.HandleFunc("/api/apps/{app-name}/workers", s.handleAPIRegisterWorker).Methods(http.MethodPost)
r.HandleFunc("/api/add-admin-key", s.handleAPIAddAdminKey).Methods(http.MethodPost)
+ if !s.external {
+ r.HandleFunc("/api/sync-users", s.handleAPISyncUsers).Methods(http.MethodGet)
+ }
e <- http.ListenAndServe(fmt.Sprintf(":%d", s.apiPort), r)
}()
+ if !s.external {
+ go func() {
+ s.syncUsers()
+ // TODO(dtabidze): every sync delay should be randomized to avoid all client
+ // applications hitting memberships service at the same time.
+ // For every next sync new delay should be randomly generated from scratch.
+ // We can choose random delay from 1 to 2 minutes.
+ for range time.Tick(1 * time.Minute) {
+ s.syncUsers()
+ }
+ }()
+ }
return <-e
}
@@ -533,11 +552,6 @@
http.Error(w, "missing type", http.StatusBadRequest)
return
}
- adminPublicKey := r.FormValue("admin-public-key")
- if adminPublicKey == "" {
- http.Error(w, "missing admin public key", http.StatusBadRequest)
- return
- }
g := installer.NewFixedLengthRandomNameGenerator(3)
appName, err := g.Generate()
if err != nil {
@@ -548,12 +562,10 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} else if !ok {
- if err := s.client.AddUser(user, adminPublicKey); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
+ http.Error(w, "user sync has not finished, please try again in few minutes", http.StatusFailedDependency)
+ return
}
- if err := s.st.CreateUser(user, nil, adminPublicKey, network); err != nil && !errors.Is(err, ErrorAlreadyExists) {
+ if err := s.st.CreateUser(user, nil, network); err != nil && !errors.Is(err, ErrorAlreadyExists) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -613,7 +625,7 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- if err := s.st.CreateUser(user, hashed, req.AdminPublicKey, req.Network); err != nil {
+ if err := s.st.CreateUser(user, hashed, req.Network); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -636,7 +648,7 @@
}
func (s *DodoAppServer) isNetworkUseAllowed(network string) bool {
- if s.allowNetworkReuse {
+ if !s.external {
return true
}
for _, cfg := range s.appConfigs {
@@ -1020,3 +1032,71 @@
}
return ret, nil
}
+
+type user struct {
+ Username string `json:"username"`
+ Email string `json:"email"`
+ SSHPublicKeys []string `json:"sshPublicKeys,omitempty"`
+}
+
+func (s *DodoAppServer) handleAPISyncUsers(_ http.ResponseWriter, _ *http.Request) {
+ go s.syncUsers()
+}
+
+func (s *DodoAppServer) syncUsers() {
+ if s.external {
+ panic("MUST NOT REACH!")
+ }
+ resp, err := http.Get(fmt.Sprintf("%s?selfAddress=%s/api/sync-users", s.fetchUsersAddr, s.self))
+ if err != nil {
+ return
+ }
+ users := []user{}
+ if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
+ fmt.Println(err)
+ return
+ }
+ for _, u := range users {
+ if len(u.SSHPublicKeys) == 0 {
+ continue
+ }
+ if ok, err := s.client.UserExists(u.Username); err != nil {
+ fmt.Println(err)
+ return
+ } else if !ok {
+ for i, k := range u.SSHPublicKeys {
+ if i == 0 {
+ if err := s.client.AddUser(u.Username, k); err != nil {
+ fmt.Println(err)
+ return
+ }
+ } else {
+ if err := s.client.AddPublicKey(u.Username, k); err != nil {
+ fmt.Println(err)
+ // TODO(dtabidze): If current public key is already registered
+ // with Git server, this method call will return an error.
+ // We need to differentiate such errors, and only add key which
+ // are missing.
+ continue // return
+ }
+ // TODO(dtabidze): Implement RemovePublicKey
+ }
+ }
+ }
+ }
+ repos, err := s.client.GetAllRepos()
+ if err != nil {
+ return
+ }
+ for _, r := range repos {
+ if r == ConfigRepoName {
+ continue
+ }
+ for _, u := range users {
+ if err := s.client.AddReadWriteCollaborator(r, u.Username); err != nil {
+ fmt.Println(err)
+ return
+ }
+ }
+ }
+}
diff --git a/core/installer/welcome/env_test.go b/core/installer/welcome/env_test.go
index 1c18470..414fac8 100644
--- a/core/installer/welcome/env_test.go
+++ b/core/installer/welcome/env_test.go
@@ -118,6 +118,10 @@
return mockRepoIO{soft.NewBillyRepoFS(f.envFS), "foo.bar", f.t, &l}, nil
}
+func (f fakeSoftServeClient) GetAllRepos() ([]string, error) {
+ return []string{}, nil
+}
+
func (f fakeSoftServeClient) GetRepoAddress(name string) string {
return ""
}
diff --git a/core/installer/welcome/static/dodo_app.css b/core/installer/welcome/static/dodo_app.css
index 7381a9b..320f940 100644
--- a/core/installer/welcome/static/dodo_app.css
+++ b/core/installer/welcome/static/dodo_app.css
@@ -78,6 +78,6 @@
@media (min-width: 768px) {
fieldset.grid {
- grid-template-columns: 1fr 1fr 1fr 1fr 200px;
+ grid-template-columns: 1fr 1fr 1fr 200px;
}
};
diff --git a/core/installer/welcome/store.go b/core/installer/welcome/store.go
index e93c03e..8ea0860 100644
--- a/core/installer/welcome/store.go
+++ b/core/installer/welcome/store.go
@@ -23,8 +23,7 @@
}
type Store interface {
- // TODO(gio): Remove publicKey once auto user sync is implemented
- CreateUser(username string, password []byte, publicKey, network string) error
+ CreateUser(username string, password []byte, network string) error
GetUserPassword(username string) ([]byte, error)
GetUserNetwork(username string) (string, error)
GetApps() ([]string, error)
@@ -53,7 +52,6 @@
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
password BLOB,
- public_key TEXT,
network TEXT
);
CREATE TABLE IF NOT EXISTS apps (
@@ -70,9 +68,9 @@
}
-func (s *storeImpl) CreateUser(username string, password []byte, publicKey, network string) error {
- query := `INSERT INTO users (username, password, public_key, network) VALUES (?, ?, ?, ?)`
- _, err := s.db.Exec(query, username, password, publicKey, network)
+func (s *storeImpl) CreateUser(username string, password []byte, network string) error {
+ query := `INSERT INTO users (username, password, network) VALUES (?, ?, ?)`
+ _, err := s.db.Exec(query, username, password, network)
if err != nil {
sqliteErr, ok := err.(*sqlite3.Error)
if ok && sqliteErr.ExtendedCode() == errorConstraintPrimaryKeyViolation {