Merge pull request 'Flist CLI Updated for Linux, MacOS, Windows' (#7) from development_mac_2 into main

Reviewed-on: #7
This commit is contained in:
mik-tf 2024-10-11 04:50:58 +00:00
commit 1580602523
4 changed files with 211 additions and 138 deletions

2
.gitignore vendored
View File

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

View File

@ -4,12 +4,24 @@ build:
sudo ./flist install
rebuild:
sudo rm flist
sudo flist uninstall
v fmt -w flist.v
v -o flist .
sudo ./flist install
delete:
sudo rm flist
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

View File

@ -8,7 +8,6 @@
- [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)
@ -29,34 +28,52 @@ Flist CLI is a tool that turns Dockerfiles and Docker images directly into Flist
### Prerequisites
- [V programming language](https://vlang.io/) (latest version) installed on your system
- Docker installed and running
- Docker Engine installed and running (Linux)
- Docker Desktop installed and running (MacOS+Windows)
- Docker Hub account
- TF Hub account and token
### Building and Installing
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
```
- 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 `flist` executable and install it to the appropriate system location.
This will build the executable and install it to the appropriate system location.
### Rebuild and Uninstall
You can use the following Makefile commands:
- To rebuild and reinstall:
- MacOS and Linux
```
make rebuild
```
- Windows
```
make rebuild-win
```
- To uninstall and remove the binary:
- MacOS and Linux
```
make delete
```
- Windows
```
make delete-win
```
## Usage
@ -66,24 +83,13 @@ After installation, you can use the `flist` command followed by various subcomma
flist <command> [arguments]
```
## 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 <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
Run `flist` or `flist help` to see all available commands for your specific OS.
## OS-Specific Instructions
### Linux
1. Ensure Docker is installed and the Docker daemon is running.
1. Ensure Docker Engine is installed and running.
2. The `flist` executable will be installed to:
```
/usr/local/bin/flist
@ -99,21 +105,25 @@ flist <command> [arguments]
### Windows
For now, we recommend to use WSL and follow the Linux steps.
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
```
## 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.
- If you encounter compilation errors, ensure you have the latest version of V installed. To update v, run `v up`.
## Development
To modify the Flist CLI:
1. Make your changes to the `flist.v` file.
2. Rebuild the project using `make rebuild`
2. Rebuild the project using using the appropriate Make command.
3. Test your changes thoroughly across different operating systems if possible.
## Contributing

249
flist.v
View File

@ -2,15 +2,31 @@ import os
import net.http
import term
import json
import x.json2
const token_file = os.join_path(os.home_dir(), '.config', 'tfhubtoken')
const binary_location = if os.user_os() == 'windows' {
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 {
'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: Make sure to use an admin PowerShell. Docker Desktop must be running to use the push function.\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'
}
struct FlistItem {
name string
}
@ -23,6 +39,64 @@ 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 := 'setx PATH "${new_env_path}"'
// Execute the command to update the PATH
exit_code := os.system(cmd.replace('"$', '${new_env_path}"'))
if exit_code == 0 {
println('PATH updated successfully.')
} else {
println('Failed to update 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 := 'setx PATH "${new_env_path}"'
// Execute the command to update the PATH
exit_code := os.system(cmd.replace('"$', '${new_env_path}"'))
if exit_code == 0 {
println('PATH updated successfully.')
} else {
println('Failed to update 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"))
@ -33,7 +107,7 @@ fn success_message(msg string) {
}
fn info_message(msg string) {
println(term.blue('\n' + msg + '\n'))
println(term.cyan('\n' + msg + '\n'))
}
fn create_box(content []string, padding int) string {
@ -47,17 +121,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.blue('${separator}') + '\n'
mut box_content := term.cyan('${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.blue('┃') + padding_left + line + padding_right + term.blue('┃') +
box_content += term.cyan('┃') + padding_left + line + padding_right + term.cyan('┃') +
'\n'
}
box_content += term.blue('${separator}')
box_content += term.cyan('${separator}')
return box_content
}
@ -68,8 +142,11 @@ fn install() {
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) }
$if windows {
add_path_windows()
}
success_message('Flist CLI has been installed to ' + binary_location)
info_message("You can now use it by running 'flist help'")
info_message("Run 'flist help' to see all commands.")
} else {
error_message('Cannot find the executable file')
exit(1)
@ -78,26 +155,43 @@ fn install() {
fn uninstall() {
info_message('Uninstalling Flist CLI...')
if os.exists(binary_location) {
// Remove the binary file
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)
}
// Uncomment the following block if you want to remove from PATH for Windows.
// if os.user_os() == 'windows' {
// remove_path_windows()
// }
}
fn login() {
mut token_exists := os.exists(token_file)
os.mkdir_all(config_dir) or { panic(err) }
if !token_exists {
tfhub_token := os.input('Please enter your tfhub token: ')
tfhub_token := os.input('Please enter your TF Hub token: ')
os.write_file(token_file, tfhub_token) or { panic(err) }
success_message('Token saved in ' + token_file)
success_message('TF Hub token saved in ' + token_file)
} else {
info_message('Your Flist Hub token is already saved.')
info_message('Your TF Hub token is already saved.')
}
result := os.system('sudo docker login')
mut result := 0
mut dockername_exists := os.exists(docker_username_file)
if !dockername_exists {
docker_username := os.input('Please enter your Docker username: ')
os.write_file(docker_username_file, docker_username) or { panic(err) }
success_message('Docker username saved in ' + docker_username_file)
info_message('Enter your Docker password.')
result = os.system('${docker_cmd} login -u ${docker_username}')
}
if result == 0 {
info_message('\nYou are already logged in to Docker.')
@ -109,82 +203,29 @@ fn login() {
fn logout() {
if os.exists(token_file) {
os.rm(token_file) or { panic(err) }
success_message('Your Flist Hub Token has been removed')
success_message('Your TF Hub token has been removed')
} else {
info_message('Your Flist Hub Token was already not present.')
info_message('Your TF Hub token was already not present.')
}
exit_code := os.system('sudo docker logout')
if os.exists(docker_username_file) {
os.rm(docker_username_file) or { panic(err) }
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')
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 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')
success_message('You are now logged out of Docker Hub and your TF Hub token has been removed.')
}
fn push(tag string) {
docker_user := get_docker_credential() or {
error_message('Failed to get Docker username: ${err}')
docker_user := os.read_file(docker_username_file) or {
error_message("No Docker username found. Please run 'flist login' first.")
exit(1)
}
@ -193,18 +234,18 @@ fn push(tag string) {
full_tag := '${docker_user}/${tag}'
tfhub_token := os.read_file(token_file) or {
error_message("No token found. Please run 'flist login' first.")
error_message("No TF Hub token found. Please run 'flist login' first.")
exit(1)
}
info_message('Starting Docker build')
if os.system('sudo docker buildx build -t ${full_tag} .') != 0 {
if os.system('${docker_cmd} 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('sudo docker push ${full_tag}') != 0 {
if os.system('${docker_cmd} push ${full_tag}') != 0 {
error_message('Docker push failed')
exit(1)
}
@ -236,7 +277,7 @@ fn push(tag string) {
if response.status_code == 200 {
hub_user := get_hub_username(tfhub_token) or {
error_message('Failed to get hub username')
error_message('Failed to get TF Hub username')
exit(1)
}
@ -254,9 +295,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))
@ -270,7 +311,7 @@ fn push(tag string) {
fn delete(flist_name string) {
tfhub_token := os.read_file(token_file) or {
error_message("No token found. Please run 'flist login' first.")
error_message("No TF Hub token found. Please run 'flist login' first.")
exit(1)
}
@ -296,7 +337,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 token found. Please run 'flist login' first.")
error_message("No TF Hub token found. Please run 'flist login' first.")
exit(1)
}
@ -357,7 +398,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 token found. Please run 'flist login' first.")
error_message("No TF Hub token found. Please run 'flist login' first.")
exit(1)
}
@ -409,21 +450,29 @@ 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.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')
$if !windows {
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.bold('Usage:'))
$if linux {
println(term.yellow(' sudo ./flist install'))
println(term.yellow(' sudo flist uninstall'))
} $else $if macos {
println(term.yellow(' sudo ./flist install'))
println(term.yellow(' flist uninstall'))
}
println(term.yellow(' flist login'))
println(term.yellow(' flist logout'))
println(term.yellow(' flist push <image>:<tag>'))