diff --git a/.gitignore b/.gitignore index 6b7ce1b..0ef9f17 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -flist \ No newline at end of file +flist +flist.exe +test/ \ No newline at end of file diff --git a/Makefile b/Makefile index 1efe5e7..130875c 100644 --- a/Makefile +++ b/Makefile @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/README.md b/README.md index f16a56a..ab99d23 100644 --- a/README.md +++ b/README.md @@ -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: - ``` - make rebuild - ``` + - MacOS and Linux + ``` + make rebuild + ``` + - Windows + ``` + make rebuild-win + ``` - To uninstall and remove the binary: - ``` - make delete - ``` + - 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 [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 :`: Build and push a Docker image, then convert and push it as an flist -- `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 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 [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 diff --git a/flist.v b/flist.v index 66076e8..5ffe45c 100644 --- a/flist.v +++ b/flist.v @@ -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:')) - println(term.yellow(' sudo ./flist install')) - println(term.yellow(' sudo flist uninstall')) + $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 :'))