nebi currently treats a server different from an OCI registry. Part of the UX rework is to make them peers in terms of the functionality a nebi instance that connects to them. Here is an example interface
import "errors"
type AuthenticationCredentialType string
type AuthenticationCredential struct {
Type AuthenticationCredentialType
Username string
Password string
Token string
}
func (ac *AuthenticationCredential) Validate() error {
switch ac.Type {
case BasicAuthenticationCredentialType:
if ac.Username == "" || ac.Password == "" {
return errors.New("basic auth requires username and password")
}
case TokenAuthenticationCredentialType:
if ac.Token == "" {
return errors.New("token auth requires token")
}
default:
return errors.New("unknown auth type")
}
return nil
}
const (
BasicAuthenticationCredentialType = "basic"
TokenAuthenticationCredentialType = "token"
)
type WorkspacePreview struct {
ID string
Name string
Version uint
}
type Workspace struct {
WorkspacePreview
Data any
}
type Remote interface {
SupportedAuthentication() []AuthenticationCredentialType
Authenticate(ac AuthenticationCredential) error
List() ([]WorkspacePreview, error)
Pull(id string) (Workspace, error)
Push(Workspace) error
}
SupportedAuthentication() []AuthenticationCredentialType is used for the client to know which authentication methods to offer to the user. If empty, the client can assume that no authentication is required. That is true for example for public, read-only OCI registries.
Authenticate(ac AuthenticationCredential) error is used for the initial authentication. If an error is returned, the client should refuse to add the remote.
List() ([]WorkspacePreview, error) list all workspaces that the authenticated user has access to. Error if the user is not authenticated.
Pull(id string) (Workspace, error) pulls a workspace from the remote. Error if the user is not authenticated or the workspace doesn't exist or the user has no permissions to read it.
Push(Workspace) error pushes a new workspace or a new version to the remote. Error if the user is not authenticated, if the user has no write permissions, or if the push would result in a conflicting state (user tries to push version 18, but 18 already exists on the remote). Conflict resolution is out of scope for now.
In the example above WorkspacePreview and Workspace are just placeholders and need to be filled with the actual fields. We also probably should have some custom error types so the client can better react to the different error reasons.
Important: This is exploratory work. It can happen as part of the current code base, or it can happen alongside it. Whatever is easier. Goal is to find out whether the interface above is sufficient for a client (CLI, web UI with NEBI_MODE=local) to interact with an arbitrary remote and whether the remotes are able to implement the interface. This can be achieved for example by implementing a type OCIRemote struct {} that implements the Remote interface and is demonstrated to work.
nebicurrently treats a server different from an OCI registry. Part of the UX rework is to make them peers in terms of the functionality a nebi instance that connects to them. Here is an example interfaceSupportedAuthentication() []AuthenticationCredentialTypeis used for the client to know which authentication methods to offer to the user. If empty, the client can assume that no authentication is required. That is true for example for public, read-only OCI registries.Authenticate(ac AuthenticationCredential) erroris used for the initial authentication. If an error is returned, the client should refuse to add the remote.List() ([]WorkspacePreview, error)list all workspaces that the authenticated user has access to. Error if the user is not authenticated.Pull(id string) (Workspace, error)pulls a workspace from the remote. Error if the user is not authenticated or the workspace doesn't exist or the user has no permissions to read it.Push(Workspace) errorpushes a new workspace or a new version to the remote. Error if the user is not authenticated, if the user has no write permissions, or if the push would result in a conflicting state (user tries to push version 18, but 18 already exists on the remote). Conflict resolution is out of scope for now.In the example above
WorkspacePreviewandWorkspaceare just placeholders and need to be filled with the actual fields. We also probably should have some custom error types so the client can better react to the different error reasons.Important: This is exploratory work. It can happen as part of the current code base, or it can happen alongside it. Whatever is easier. Goal is to find out whether the interface above is sufficient for a client (CLI, web UI with NEBI_MODE=local) to interact with an arbitrary remote and whether the remotes are able to implement the interface. This can be achieved for example by implementing a
type OCIRemote struct {}that implements theRemoteinterface and is demonstrated to work.