Compare commits

..

No commits in common. "main" and "main_linux_archive" have entirely different histories.

4 changed files with 158 additions and 335 deletions

4
.gitignore vendored
View File

@ -1,3 +1 @@
flist
flist.exe
test/
flist

View File

@ -4,24 +4,12 @@ build:
sudo ./flist install
rebuild:
sudo rm flist
sudo flist uninstall
v fmt -w flist.v
v -o flist .
sudo ./flist install
delete:
sudo flist uninstall
build-win:
v fmt -w flist.v
v -o flist .
./flist.exe install
rebuild-win:
./flist.exe uninstall
v fmt -w flist.v
v -o flist .
./flist.exe install
delete-win:
./flist.exe uninstall
sudo rm flist
sudo flist uninstall

170
README.md
View File

@ -3,13 +3,12 @@
<h2>Table of Contents</h2>
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Makefile Installation](#makefile-installation)
- [Building and Installing with Makefile](#building-and-installing-with-makefile)
- [Rebuilding and Uninstalling with Makefile](#rebuilding-and-uninstalling-with-makefile)
- [Manual Installation](#manual-installation)
- [Available Commands](#available-commands)
- [Installation](#installation)
- [Prerequisites](#prerequisites)
- [Building and Installing](#building-and-installing)
- [Rebuild and Uninstall](#rebuild-and-uninstall)
- [Usage](#usage)
- [Commands](#commands)
- [OS-Specific Instructions](#os-specific-instructions)
- [Linux](#linux)
- [macOS](#macos)
@ -25,103 +24,41 @@
Flist CLI is a tool that turns Dockerfiles and Docker images directly into Flist on the TF Flist Hub, passing through Docker Hub.
## Prerequisites
## Installation
### Prerequisites
- [V programming language](https://vlang.io/) (latest version) installed on your system
- [Docker Engine](https://docs.docker.com/engine/install/) installed and running (Linux)
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed and running (MacOS+Windows)
- [Docker Hub](https://hub.docker.com/) account
- [TF Hub](https://hub.grid.tf/) account and token
- Makefile (optional)
- Docker installed and running
- Docker Hub account
- TF Hub account and token
Read more on the TF Hub and Flist on the ThreeFold Manual [here](https://manual.grid.tf/documentation/developers/flist/flist.html).
### Building and Installing
## Makefile Installation
Clone this repository, build the project, and install the CLI:
```
git clone https://git.ourworld.tf/tfgrid/flist_cli_v
cd flist_cli_v
make build
```
### Building and Installing with Makefile
This will build the `flist` executable and install it to the appropriate system location.
- To clone this repository, build the project, and install the CLI:
- MacOS and Linux
```
git clone https://git.ourworld.tf/tfgrid/flist_cli_v
cd flist_cli_v
make build
```
- Windows
```
git clone https://git.ourworld.tf/tfgrid/flist_cli_v
cd flist_cli_v
make build-win
```
This will build the executable and install it to the appropriate system location.
### Rebuilding and Uninstalling with Makefile
### Rebuild and Uninstall
You can use the following Makefile commands:
- To rebuild and reinstall:
- MacOS and Linux
```
make rebuild
```
- Windows
```
make rebuild-win
```
```
make rebuild
```
- To uninstall and remove the binary:
- MacOS and Linux
```
make delete
```
- Windows
```
make delete-win
```
```
make delete
```
## Manual Installation
You can install the Flist with the following commands. You do not need Makefile to use the Flist CLI.
- Linux and MacOS
- Build
```
v fmt -w flist.v
v -o flist .
sudo ./flist install
```
- Rebuild
```
sudo flist uninstall
v fmt -w flist.v
v -o flist .
sudo ./flist install
```
- Delete
```
sudo flist uninstall
```
- Windows
- Build
```
v fmt -w flist.v
v -o flist .
./flist.exe install
```
- Rebuild
```
./flist.exe uninstall
v fmt -w flist.v
v -o flist .
./flist.exe install
```
- Delete
```
./flist.exe uninstall
```
## Available Commands
## Usage
After installation, you can use the `flist` command followed by various subcommands:
@ -129,41 +66,24 @@ After installation, you can use the `flist` command followed by various subcomma
flist <command> [arguments]
```
Run `flist` or `flist help` to see all available commands for your specific OS.
## Commands
- `install` - Install the Flist CLI
- `uninstall` - Uninstall the Flist CLI
- `login` - Log in to Docker Hub and save the Flist Hub token
- `logout` - Log out of Docker Hub and remove the Flist Hub token
- `push` - Build and push a Docker image to Docker Hub, then convert and push it as an Flist to Flist Hub
- `delete` - Delete an Flist from Flist Hub
- `rename` - Rename an Flist in Flist Hub
- `ls` - List all Flists of the current user
- `ls url` - List all Flists of the current user with full URLs
- `help` - Display this help message
## Usage
A Linux user would use the following commands:
```
sudo ./flist install
sudo flist uninstall
flist login
flist logout
flist push <image>:<tag>
flist delete <flist_name>
flist rename <flist_name> <new_flist_name>
flist ls
flist ls url
flist help
```
- `install`: Install the Flist CLI
- `uninstall`: Uninstall the Flist CLI
- `login`: Log in to Docker Hub and save the Flist Hub token
- `logout`: Log out of Docker Hub and remove the Flist Hub token
- `push <image>:<tag>`: Build and push a Docker image, then convert and push it as an flist
- `delete <flist_name>`: Delete an flist from Flist Hub
- `rename <flist_name> <new_flist_name>`: Rename an flist in Flist Hub
- `ls`: List all flists of the current user
- `ls url`: List all flists of the current user with full URLs
- `help`: Display help information
## OS-Specific Instructions
### Linux
1. Ensure Docker Engine is installed and running.
1. Ensure Docker is installed and the Docker daemon is running.
2. The `flist` executable will be installed to:
```
/usr/local/bin/flist
@ -179,25 +99,21 @@ flist help
### Windows
1. Ensure Docker Desktop is installed and running.
2. Run the program and installer in an admin PowerShell.
3. The `flist.exe` executable will be installed to:
```
C:\\Program Files\\flist\\flist.exe
```
For now, we recommend to use WSL and follow the Linux steps.
## Troubleshooting
- If you encounter permission issues, ensure you're running the command with appropriate privileges (e.g., as administrator on Windows or with `sudo` on Unix-like systems).
- Make sure Docker is running before using Flist CLI commands.
- If you face issues with Docker commands, try logging out and logging back in to refresh your Docker credentials.
- If you encounter compilation errors, ensure you have the latest version of V installed. To update v, run `v up`.
- If you encounter compilation errors, ensure you have the latest version of V installed.
## Development
To modify the Flist CLI:
1. Make your changes to the `flist.v` file.
2. Rebuild the project using using the appropriate Make command.
2. Rebuild the project using `make rebuild`
3. Test your changes thoroughly across different operating systems if possible.
## Contributing

301
flist.v
View File

@ -2,33 +2,15 @@ import os
import net.http
import term
import json
import x.json2
const token_file = os.join_path(os.home_dir(), '.config', 'tfhubtoken')
const docker_username_file = os.join_path(os.home_dir(), '.config', 'dockerusername')
const config_dir = os.join_path(os.home_dir(), '.config')
const binary_location = $if windows {
const binary_location = if os.user_os() == 'windows' {
'C:\\Program Files\\flist\\flist.exe'
} $else {
} else {
'/usr/local/bin/flist'
}
const docker_cmd = $if windows {
'docker'
} $else {
'sudo docker'
}
const info_msg = $if windows {
'Note: Docker Desktop must be running to use the push function.\nInstall and uninstall functions require PowerShell with administrator privileges.\nOther functions require PowerShell (without admin privileges).\n'
} $else $if linux {
'Note: Docker Engine must be running to use the push function.\n'
} $else $if macos {
'Note: Docker Desktop must be running to use the push function.\n'
}
const flist_repo_folder = (' # Run this line in the Flist CLI repo folder')
struct FlistItem {
name string
}
@ -41,64 +23,6 @@ struct Response {
payload Payload
}
fn add_path_windows() {
// Define the new directory path to add
new_path := r'C:\Program Files\flist'
// Get the current PATH environment variable
current_path := os.getenv('PATH')
// Check if the new_path is already in the current PATH
if current_path.contains(new_path) {
println('The directory is already in the PATH.')
return
}
// Construct the new PATH by appending the new_path
new_env_path := '${current_path};${new_path}'
// Prepare the command to set the new PATH
cmd := r'setx PATH "' + new_env_path + '"'
// Execute the command to update the PATH
exit_code := os.system(cmd)
if exit_code == 0 {
success_message('\nFlist CLI directory added to the path. \nMake sure to load a new admin PowerShell to use the CLI.')
} else {
error_message('\nFailed to add the Flist CLI to the PATH. Please try running the script as an administrator.')
}
}
fn remove_path_windows() {
// Define the directory path to remove
path_to_remove := r'C:\Program Files\flist'
// Get the current PATH environment variable
current_path := os.getenv('PATH')
// Check if the path_to_remove is in the current PATH
if !current_path.contains(path_to_remove) {
println('The directory is not in the PATH.')
return
}
// Remove specified directory from the PATH
new_env_path := current_path.split(';').filter(it != path_to_remove).join(';')
// Prepare the command to set the new PATH
cmd := r'setx PATH "' + new_env_path + '"'
// Execute the command to update the PATH
exit_code := os.system(cmd)
if exit_code == 0 {
success_message('\nThe Flist CLI directory has been removed from the path.')
} else {
error_message('\nFailed to remove the Flist CLI from the PATH. Please try running the script as an administrator.')
}
}
fn error_message(msg string) {
println(term.red('\nError: ') + msg)
println(term.yellow("Run 'flist help' for usage information.\n"))
@ -109,7 +33,7 @@ fn success_message(msg string) {
}
fn info_message(msg string) {
println(term.cyan('\n' + msg + '\n'))
println(term.blue('\n' + msg + '\n'))
}
fn create_box(content []string, padding int) string {
@ -123,17 +47,17 @@ fn create_box(content []string, padding int) string {
max_width += padding * 2
separator := '━'.repeat(max_width + 2) // +2 for left and right borders
mut box_content := term.cyan('${separator}') + '\n'
mut box_content := term.blue('${separator}') + '\n'
for line in content {
clean_line := term.strip_ansi(line)
padding_left := ' '.repeat(padding)
padding_right := ' '.repeat(max_width - clean_line.len)
box_content += term.cyan('┃') + padding_left + line + padding_right + term.cyan('┃') +
box_content += term.blue('┃') + padding_left + line + padding_right + term.blue('┃') +
'\n'
}
box_content += term.cyan('${separator}')
box_content += term.blue('${separator}')
return box_content
}
@ -141,23 +65,11 @@ fn install() {
info_message('Installing Flist CLI...')
current_exe := os.executable()
if os.exists(current_exe) {
os.mkdir_all(os.dir(binary_location)) or {
error_message('Failed to create directory for binary: ${err}')
exit(1)
}
os.cp(current_exe, binary_location) or {
error_message('Failed to copy binary to path: ${err}')
exit(1)
}
os.chmod(binary_location, 0o755) or {
error_message('Failed to change permissions to binary at path: ${err}')
exit(1)
}
$if windows {
add_path_windows()
}
os.mkdir_all(os.dir(binary_location)) or { panic(err) }
os.cp(current_exe, binary_location) or { panic(err) }
os.chmod(binary_location, 0o755) or { panic(err) }
success_message('Flist CLI has been installed to ' + binary_location)
info_message("Run 'flist help' to see all commands.")
info_message("You can now use it by running 'flist help'")
} else {
error_message('Cannot find the executable file')
exit(1)
@ -166,95 +78,113 @@ fn install() {
fn uninstall() {
info_message('Uninstalling Flist CLI...')
if os.exists(binary_location) {
// Remove the binary file
os.rm(binary_location) or {
error_message('Failed to remove the binary at path: ${err}')
exit(1)
}
os.rm(binary_location) or { panic(err) }
success_message('Flist CLI has been removed from ' + binary_location)
} else {
info_message('Flist CLI is not installed at ' + binary_location)
}
$if windows {
remove_path_windows()
}
}
fn login() {
mut token_exists := os.exists(token_file)
os.mkdir_all(config_dir) or {
error_message('Failed to create config folder for token and Docker username files: ${err}')
exit(1)
}
if !token_exists {
tfhub_token := os.input('Please enter your TF Hub token: ')
os.write_file(token_file, tfhub_token) or {
error_message('Failed to write TF Hub token to file: ${err}')
exit(1)
}
success_message('TF Hub token saved in ' + token_file)
tfhub_token := os.input('Please enter your tfhub token: ')
os.write_file(token_file, tfhub_token) or { panic(err) }
success_message('Token saved in ' + token_file)
} else {
info_message('Your TF Hub token is already saved.')
info_message('Your Flist Hub token is already saved.')
}
mut dockername_exists := os.exists(docker_username_file)
mut docker_username := ''
result := os.system('sudo docker login')
if !dockername_exists {
docker_username = os.input('Please enter your Docker username: ')
os.write_file(docker_username_file, docker_username) or {
error_message('Failed to write Docker username to file: ${err}')
exit(1)
}
success_message('Docker username saved in ' + docker_username_file)
if result == 0 {
info_message('\nYou are already logged in to Docker.')
}
docker_username = os.read_file(docker_username_file) or {
error_message('Failed to read the Docker username from file: ${err}')
exit(1)
}
info_message('Enter your Docker password')
os.system('${docker_cmd} login -u ${docker_username}')
success_message('TF Hub and Docker Hub login process completed.')
success_message('Login process completed.')
}
fn logout() {
if os.exists(token_file) {
os.rm(token_file) or {
error_message('Failed to remove TF Hub token file at config directory: ${err}')
exit(1)
}
success_message('Your TF Hub token has been removed')
os.rm(token_file) or { panic(err) }
success_message('Your Flist Hub Token has been removed')
} else {
info_message('Your TF Hub token was already not present.')
info_message('Your Flist Hub Token was already not present.')
}
if os.exists(docker_username_file) {
os.rm(docker_username_file) or {
error_message('Failed to remove Docker username file in config folder: ${err}')
exit(1)
}
success_message('Your Docker username has been removed from the config folder.')
} else {
info_message('Your Docker username was already not present in the config folder.')
}
exit_code := os.system('${docker_cmd} logout')
exit_code := os.system('sudo docker logout')
if exit_code != 0 {
error_message('Failed to log out from Docker Hub.')
}
success_message('You are now logged out of Docker Hub and your TF Hub token has been removed.')
success_message('You are now logged out of Docker Hub and your Flist Hub token has been removed.')
}
fn get_docker_credential() !string {
// Try to get the Docker credential automatically
credential := get_docker_credential_auto() or {
// If automatic retrieval fails, prompt the user for input
println(term.yellow("\nCouldn't find your Docker username automatically."))
username := os.input('Please enter your Docker username and press ENTER: ')
if username.trim_space() == '' {
return error('No Docker username provided')
}
return username.trim_space()
}
return credential
}
fn get_docker_credential_auto() !string {
// First, try to get the Docker username using the system info command
system_info_result := os.execute("sudo docker system info | grep 'Username' | cut -d ' ' -f 3")
if system_info_result.exit_code == 0 && system_info_result.output.trim_space() != '' {
return system_info_result.output.trim_space()
}
// If the above method fails, proceed with the current method
// Read the Docker config file
config_path := os.join_path(os.home_dir(), '.docker', 'config.json')
config_content := os.read_file(config_path) or {
return error('Failed to read Docker config file: ${err}')
}
// Parse the JSON content
config := json2.raw_decode(config_content) or {
return error('Failed to parse Docker config: ${err}')
}
// Extract the credsStore value
creds_store := config.as_map()['credsStore'] or {
return error('credsStore not found in Docker config')
}.str()
// Execute the docker-credential command
cred_helper := 'docker-credential-${creds_store}'
cred_output := os.execute('${cred_helper} list')
if cred_output.exit_code != 0 {
return error('Failed to execute ${cred_helper}: ${cred_output.output}')
}
// Parse the credential list
cred_list := json2.raw_decode(cred_output.output) or {
return error('Failed to parse credential list: ${err}')
}
// Find the first docker.io entry
for key, value in cred_list.as_map() {
if key.contains('docker.io') {
return value.str()
}
}
return error('No docker.io credential found')
}
fn push(tag string) {
docker_user := os.read_file(docker_username_file) or {
error_message("No Docker username found. Please run 'flist login' first.")
docker_user := get_docker_credential() or {
error_message('Failed to get Docker username: ${err}')
exit(1)
}
@ -263,18 +193,18 @@ fn push(tag string) {
full_tag := '${docker_user}/${tag}'
tfhub_token := os.read_file(token_file) or {
error_message("No TF Hub token found. Please run 'flist login' first.")
error_message("No token found. Please run 'flist login' first.")
exit(1)
}
info_message('Starting Docker build')
if os.system('${docker_cmd} buildx build -t ${full_tag} .') != 0 {
if os.system('sudo docker buildx build -t ${full_tag} .') != 0 {
error_message('Docker build failed')
exit(1)
}
info_message('Finished local Docker build, now pushing to Docker Hub')
if os.system('${docker_cmd} push ${full_tag}') != 0 {
if os.system('sudo docker push ${full_tag}') != 0 {
error_message('Docker push failed')
exit(1)
}
@ -306,7 +236,7 @@ fn push(tag string) {
if response.status_code == 200 {
hub_user := get_hub_username(tfhub_token) or {
error_message('Failed to get TF Hub username')
error_message('Failed to get hub username')
exit(1)
}
@ -324,9 +254,9 @@ fn push(tag string) {
'',
'You can access your Flist using the URL above.',
'To manage your Flists, use the following commands:',
term.yellow(' flist ls ') + ' - List all your Flists',
term.yellow(' flist delete') + ' - Delete an Flist',
term.yellow(' flist rename') + ' - Rename an Flist',
term.yellow(' flist ls ') + '- List all your Flists',
term.yellow(' flist delete') + '- Delete an Flist',
term.yellow(' flist rename') + '- Rename an Flist',
]
println(create_box(success_content, 2))
@ -340,7 +270,7 @@ fn push(tag string) {
fn delete(flist_name string) {
tfhub_token := os.read_file(token_file) or {
error_message("No TF Hub token found. Please run 'flist login' first.")
error_message("No token found. Please run 'flist login' first.")
exit(1)
}
@ -366,7 +296,7 @@ fn delete(flist_name string) {
fn rename(flist_name string, new_flist_name string) {
tfhub_token := os.read_file(token_file) or {
error_message("No TF Hub token found. Please run 'flist login' first.")
error_message("No token found. Please run 'flist login' first.")
exit(1)
}
@ -427,7 +357,7 @@ fn get_hub_username(tfhub_token string) ?string {
fn ls(show_url bool) {
tfhub_token := os.read_file(token_file) or {
error_message("No TF Hub token found. Please run 'flist login' first.")
error_message("No token found. Please run 'flist login' first.")
exit(1)
}
@ -479,30 +409,21 @@ fn help() {
println(create_box([welcome_msg], 2))
println('This tool turns Dockerfiles and Docker images directly into Flists on the TF Flist Hub, passing by the Docker Hub.\n')
println(term.cyan(info_msg))
println(term.bold('Available commands:'))
println(term.cyan(' install ') + ' - Install the Flist CLI')
println(term.cyan(' uninstall') + ' - Uninstall the Flist CLI')
println(term.cyan(' login ') + ' - Log in to Docker Hub and save the Flist Hub token')
println(term.cyan(' logout ') + ' - Log out of Docker Hub and remove the Flist Hub token')
println(term.cyan(' push ') +
' - Build and push a Docker image to Docker Hub, then convert and push it as an Flist to Flist Hub')
println(term.cyan(' delete ') + ' - Delete an Flist from Flist Hub')
println(term.cyan(' rename ') + ' - Rename an Flist in Flist Hub')
println(term.cyan(' ls ') + ' - List all Flists of the current user')
println(term.cyan(' ls url ') + ' - List all Flists of the current user with full URLs')
println(term.cyan(' help ') + ' - Display this help message\n')
println(term.blue(' install ') + '- Install the Flist CLI')
println(term.blue(' uninstall ') + '- Uninstall the Flist CLI')
println(term.blue(' login ') + '- Log in to Docker Hub and save the Flist Hub token')
println(term.blue(' logout ') + '- Log out of Docker Hub and remove the Flist Hub token')
println(term.blue(' push ') +
'- Build and push a Docker image to Docker Hub, then convert and push it as an Flist to Flist Hub')
println(term.blue(' delete ') + '- Delete an Flist from Flist Hub')
println(term.blue(' rename ') + '- Rename an Flist in Flist Hub')
println(term.blue(' ls ') + '- List all Flists of the current user')
println(term.blue(' ls url ') + '- List all Flists of the current user with full URLs')
println(term.blue(' help ') + '- Display this help message\n')
println(term.bold('Usage:'))
$if linux {
println(term.yellow(' sudo ./flist install') + term.cyan(flist_repo_folder))
println(term.yellow(' sudo flist uninstall'))
} $else $if macos {
println(term.yellow(' sudo ./flist install') + term.cyan(flist_repo_folder))
println(term.yellow(' flist uninstall'))
} $else $if windows {
println(term.yellow(' ./flist.exe install') + term.cyan(flist_repo_folder))
println(term.yellow(' ./flist.exe uninstall') + term.cyan(flist_repo_folder))
}
println(term.yellow(' sudo ./flist install'))
println(term.yellow(' sudo flist uninstall'))
println(term.yellow(' flist login'))
println(term.yellow(' flist logout'))
println(term.yellow(' flist push <image>:<tag>'))