This commit is contained in:
mik-tf 2024-10-05 18:31:50 -04:00
parent b7669954d6
commit 3947c93e59

757
flist.v
View File

@ -4,484 +4,495 @@ import term
import json
import x.json2
const (
token_file = os.join_path(os.home_dir(), '.config', 'tfhubtoken')
binary_location = if os.user_os() == 'windows' {
'C:\\Program Files\\flist\\flist.exe'
} else {
'/usr/local/bin/flist'
}
)
const token_file = os.join_path(os.home_dir(), '.config', 'tfhubtoken')
const binary_location = if os.user_os() == 'windows' {
'C:\\Program Files\\flist\\flist.exe'
} else {
'/usr/local/bin/flist'
}
struct FlistItem {
name string
name string
}
struct Payload {
username string
username string
}
struct Response {
payload Payload
payload Payload
}
fn error_message(msg string) {
println(term.red('\nError: ') + msg)
println(term.yellow('Run \'flist help\' for usage information.\n'))
println(term.red('\nError: ') + msg)
println(term.yellow("Run 'flist help' for usage information.\n"))
}
fn success_message(msg string) {
println(term.green('\n' + msg + '\n'))
println(term.green('\n' + msg + '\n'))
}
fn info_message(msg string) {
println(term.blue('\n' + msg + '\n'))
println(term.blue('\n' + msg + '\n'))
}
fn create_box(content []string, padding int) string {
mut max_width := 0
for line in content {
clean_line := term.strip_ansi(line)
if clean_line.len > max_width {
max_width = clean_line.len
}
}
max_width += padding * 2
mut max_width := 0
for line in content {
clean_line := term.strip_ansi(line)
if clean_line.len > max_width {
max_width = clean_line.len
}
}
max_width += padding * 2
separator := '━'.repeat(max_width + 2) // +2 for left and right borders
mut box_content := term.blue('$separator') + '\n'
separator := '━'.repeat(max_width + 2) // +2 for left and right borders
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.blue('┃') + padding_left + line + padding_right + term.blue('┃') + '\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('┃') +
'\n'
}
box_content += term.blue('$separator')
return box_content
box_content += term.blue('${separator}')
return box_content
}
fn install() {
info_message('Installing Flist CLI...')
current_exe := os.executable()
if os.exists(current_exe) {
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('You can now use it by running \'flist help\'')
} else {
error_message('Cannot find the executable file')
exit(1)
}
info_message('Installing Flist CLI...')
current_exe := os.executable()
if os.exists(current_exe) {
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("You can now use it by running 'flist help'")
} else {
error_message('Cannot find the executable file')
exit(1)
}
}
fn uninstall() {
info_message('Uninstalling Flist CLI...')
if os.exists(binary_location) {
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)
}
info_message('Uninstalling Flist CLI...')
if os.exists(binary_location) {
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)
}
}
fn login() {
mut token_exists := os.exists(token_file)
mut token_exists := os.exists(token_file)
if !token_exists {
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 Flist Hub token is already saved.')
}
if !token_exists {
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 Flist Hub token is already saved.')
}
result := os.system('sudo docker login')
if result == 0 {
info_message('\nYou are already logged in to Docker.')
}
result := os.system('sudo docker login')
success_message('Login process completed.')
if result == 0 {
info_message('\nYou are already logged in to Docker.')
}
success_message('Login process completed.')
}
fn logout() {
if os.exists(token_file) {
os.rm(token_file) or {panic(err)}
success_message('Your Flist Hub Token has been removed')
} else {
info_message('Your Flist Hub Token was already not present.')
}
if os.exists(token_file) {
os.rm(token_file) or { panic(err) }
success_message('Your Flist Hub Token has been removed')
} else {
info_message('Your Flist Hub Token was already not present.')
}
exit_code := os.system('sudo docker logout')
if exit_code != 0 {
error_message('Failed to log out from Docker Hub.')
}
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 Flist 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
// 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()
}
// 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')
}
// 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')
}
// 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()
// 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}')
}
// 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')
}
// 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()
}
}
// 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')
return error('No docker.io credential found')
}
fn push(tag string) {
docker_user := get_docker_credential() or {
error_message('Failed to get Docker username: $err')
exit(1)
}
docker_user := get_docker_credential() or {
error_message('Failed to get Docker username: ${err}')
exit(1)
}
info_message('Docker username: $docker_user')
info_message('Docker username: ${docker_user}')
full_tag := '${docker_user}/${tag}'
full_tag := '${docker_user}/${tag}'
tfhub_token := os.read_file(token_file) or {
error_message('No token found. Please run \'flist login\' first.')
exit(1)
}
tfhub_token := os.read_file(token_file) or {
error_message("No 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 {
error_message('Docker build failed')
exit(1)
}
info_message('Starting Docker build')
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('sudo docker push ${full_tag}') != 0 {
error_message('Docker push failed')
exit(1)
}
info_message('Finished local Docker build, now pushing to Docker Hub')
if os.system('sudo docker push ${full_tag}') != 0 {
error_message('Docker push failed')
exit(1)
}
info_message('Converting Docker image to Flist now...')
info_message('Converting Docker image to Flist now...')
url := 'https://hub.grid.tf/api/flist/me/docker'
data := 'image=$full_tag'
url := 'https://hub.grid.tf/api/flist/me/docker'
data := 'image=${full_tag}'
mut config := http.FetchConfig{
url: url
method: .post
data: data
header: http.new_header(
key: .authorization
value: 'bearer $tfhub_token'
)
}
mut config := http.FetchConfig{
url: url
method: .post
data: data
header: http.new_header(
key: .authorization
value: 'bearer ${tfhub_token}'
)
}
config.header.add_custom('Content-Type', 'application/x-www-form-urlencoded') or {
error_message('Add custom failed: $err')
exit(1)
}
config.header.add_custom('Content-Type', 'application/x-www-form-urlencoded') or {
error_message('Add custom failed: ${err}')
exit(1)
}
response := http.fetch(config) or {
error_message('HTTP POST request failed: $err')
exit(1)
}
response := http.fetch(config) or {
error_message('HTTP POST request failed: ${err}')
exit(1)
}
if response.status_code == 200 {
hub_user := get_hub_username(tfhub_token) or {
error_message('Failed to get hub username')
exit(1)
}
if response.status_code == 200 {
hub_user := get_hub_username(tfhub_token) or {
error_message('Failed to get hub username')
exit(1)
}
flist_name := tag.replace(':', '-') + '.flist'
flist_url := 'https://hub.grid.tf/$hub_user/$flist_name'
success_content := [
term.bold(term.green('Success!') + ' Your Flist has been created and pushed to the TF Hub.'),
'',
term.bold('Flist Details:'),
term.yellow('Name: ') + flist_name,
term.yellow('User: ') + hub_user,
term.yellow('URL: ') + flist_url,
'',
'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'
]
println(create_box(success_content, 2))
} else {
error_message('Request failed with status code: ${response.status_code}')
println('Response body:')
println(response.body)
exit(1)
}
flist_name := tag.replace(':', '-') + '.flist'
flist_url := 'https://hub.grid.tf/${hub_user}/${flist_name}'
success_content := [
term.bold(term.green('Success!') +
' Your Flist has been created and pushed to the TF Hub.'),
'',
term.bold('Flist Details:'),
term.yellow('Name: ') + flist_name,
term.yellow('User: ') + hub_user,
term.yellow('URL: ') + flist_url,
'',
'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',
]
println(create_box(success_content, 2))
} else {
error_message('Request failed with status code: ${response.status_code}')
println('Response body:')
println(response.body)
exit(1)
}
}
fn delete(flist_name string) {
tfhub_token := os.read_file(token_file) or {
error_message('No token found. Please run \'flist login\' first.')
exit(1)
}
tfhub_token := os.read_file(token_file) or {
error_message("No token found. Please run 'flist login' first.")
exit(1)
}
info_message('Deleting Flist: ' + flist_name)
url := 'https://hub.grid.tf/api/flist/me/' + flist_name
config := http.FetchConfig{
url: url
method: .delete
header: http.new_header(key: .authorization, value: 'bearer ' + tfhub_token)
}
info_message('Deleting Flist: ' + flist_name)
url := 'https://hub.grid.tf/api/flist/me/' + flist_name
config := http.FetchConfig{
url: url
method: .delete
header: http.new_header(key: .authorization, value: 'bearer ' + tfhub_token)
}
response := http.fetch(config) or {
error_message('Failed to send delete request: ' + err.msg())
exit(1)
}
response := http.fetch(config) or {
error_message('Failed to send delete request: ' + err.msg())
exit(1)
}
if response.status_code == 200 {
success_message('Deletion request sent successfully.')
} else {
error_message('Deletion request failed with status code: ' + response.status_code.str())
}
if response.status_code == 200 {
success_message('Deletion request sent successfully.')
} else {
error_message('Deletion request failed with status code: ' + response.status_code.str())
}
}
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.')
exit(1)
}
tfhub_token := os.read_file(token_file) or {
error_message("No token found. Please run 'flist login' first.")
exit(1)
}
info_message('Renaming Flist: ' + flist_name + ' to ' + new_flist_name)
url := 'https://hub.grid.tf/api/flist/me/' + flist_name + '/rename/' + new_flist_name
config := http.FetchConfig{
url: url
method: .get
header: http.new_header(key: .authorization, value: 'bearer ' + tfhub_token)
}
info_message('Renaming Flist: ' + flist_name + ' to ' + new_flist_name)
url := 'https://hub.grid.tf/api/flist/me/' + flist_name + '/rename/' + new_flist_name
config := http.FetchConfig{
url: url
method: .get
header: http.new_header(key: .authorization, value: 'bearer ' + tfhub_token)
}
response := http.fetch(config) or {
error_message('Failed to send rename request: ' + err.msg())
exit(1)
}
response := http.fetch(config) or {
error_message('Failed to send rename request: ' + err.msg())
exit(1)
}
if response.status_code == 200 {
success_message('Rename request sent successfully.')
} else {
error_message('Rename request failed with status code: ' + response.status_code.str())
}
if response.status_code == 200 {
success_message('Rename request sent successfully.')
} else {
error_message('Rename request failed with status code: ' + response.status_code.str())
}
}
fn get_hub_username(tfhub_token string) ?string {
url := 'https://hub.grid.tf/api/flist/me'
url := 'https://hub.grid.tf/api/flist/me'
config := http.FetchConfig{
url: url
method: .get
header: http.new_header(
key: .authorization
value: 'bearer $tfhub_token'
)
}
config := http.FetchConfig{
url: url
method: .get
header: http.new_header(
key: .authorization
value: 'bearer ${tfhub_token}'
)
}
response := http.fetch(config) or {
error_message('Failed to fetch hub username: $err')
return none
}
response := http.fetch(config) or {
error_message('Failed to fetch hub username: ${err}')
return none
}
if response.status_code != 200 {
error_message('Failed to fetch hub username. Status code: $response.status_code')
return none
}
if response.status_code != 200 {
error_message('Failed to fetch hub username. Status code: ${response.status_code}')
return none
}
parsed_response := json.decode(Response, response.body) or {
error_message('Failed to parse JSON response: $err')
return none
}
parsed_response := json.decode(Response, response.body) or {
error_message('Failed to parse JSON response: ${err}')
return none
}
if parsed_response.payload.username != '' {
return parsed_response.payload.username
}
if parsed_response.payload.username != '' {
return parsed_response.payload.username
}
error_message('Username not found in response')
return none
error_message('Username not found in response')
return none
}
fn ls(show_url bool) {
tfhub_token := os.read_file(token_file) or {
error_message('No token found. Please run \'flist login\' first.')
exit(1)
}
tfhub_token := os.read_file(token_file) or {
error_message("No token found. Please run 'flist login' first.")
exit(1)
}
hub_user := get_hub_username(tfhub_token) or {
error_message('Failed to get hub username')
exit(1)
}
hub_user := get_hub_username(tfhub_token) or {
error_message('Failed to get hub username')
exit(1)
}
url := 'https://hub.grid.tf/api/flist/$hub_user'
url := 'https://hub.grid.tf/api/flist/${hub_user}'
config := http.FetchConfig{
url: url
method: .get
header: http.new_header(
key: .authorization
value: 'bearer $tfhub_token'
)
}
config := http.FetchConfig{
url: url
method: .get
header: http.new_header(
key: .authorization
value: 'bearer ${tfhub_token}'
)
}
response := http.fetch(config) or {
error_message('Failed to fetch data: $err')
exit(1)
}
response := http.fetch(config) or {
error_message('Failed to fetch data: ${err}')
exit(1)
}
if response.status_code != 200 {
error_message('Failed to fetch data. Status code: $response.status_code')
exit(1)
}
if response.status_code != 200 {
error_message('Failed to fetch data. Status code: ${response.status_code}')
exit(1)
}
data := json.decode([]FlistItem, response.body) or {
error_message('Failed to parse JSON: $err')
exit(1)
}
data := json.decode([]FlistItem, response.body) or {
error_message('Failed to parse JSON: ${err}')
exit(1)
}
mut content := [term.bold('Flists for user ' + term.green(hub_user) + ':')]
for item in data {
if show_url {
content << term.yellow('> ') + 'https://hub.grid.tf/' + hub_user + '/' + item.name
} else {
content << term.yellow('> ') + item.name
}
}
mut content := [term.bold('Flists for user ' + term.green(hub_user) + ':')]
for item in data {
if show_url {
content << term.yellow('> ') + 'https://hub.grid.tf/' + hub_user + '/' + item.name
} else {
content << term.yellow('> ') + item.name
}
}
println(create_box(content, 2))
println(create_box(content, 2))
}
fn help() {
welcome_msg := term.bold(term.green('Welcome to the Flist CLI!'))
println(create_box([welcome_msg], 2))
welcome_msg := term.bold(term.green('Welcome to the Flist CLI!'))
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.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')
println(term.bold('Usage:'))
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>'))
println(term.yellow(' flist delete <flist_name>'))
println(term.yellow(' flist rename <flist_name> <new_flist_name>'))
println(term.yellow(' flist ls'))
println(term.yellow(' flist ls url'))
println(term.yellow(' flist help\n'))
println('This tool turns Dockerfiles and Docker images directly into Flists on the TF Flist Hub, passing by the Docker Hub.\n')
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')
println(term.bold('Usage:'))
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>'))
println(term.yellow(' flist delete <flist_name>'))
println(term.yellow(' flist rename <flist_name> <new_flist_name>'))
println(term.yellow(' flist ls'))
println(term.yellow(' flist ls url'))
println(term.yellow(' flist help\n'))
}
fn main() {
if os.args.len == 1 {
help()
return
}
if os.args.len == 1 {
help()
return
}
match os.args[1] {
'install' { install() }
'uninstall' { uninstall() }
'push' {
if os.args.len == 3 {
push(os.args[2])
} else {
error_message('Incorrect number of arguments for \'push\'.')
exit(1)
}
}
'login' { login() }
'logout' { logout() }
'delete' {
if os.args.len == 3 {
delete(os.args[2])
} else {
error_message('Incorrect number of arguments for \'delete\'.')
exit(1)
}
}
'rename' {
if os.args.len == 4 {
rename(os.args[2], os.args[3])
} else {
error_message('Incorrect number of arguments for \'rename\'.')
exit(1)
}
}
'ls' {
if os.args.len == 2 {
ls(false)
} else if os.args.len == 3 && os.args[2] == 'url' {
ls(true)
} else {
error_message('Incorrect usage of \'ls\'. Use \'ls\' or \'ls url\'.')
exit(1)
}
}
'help' { help() }
else {
error_message('Unknown command: ' + os.args[1])
exit(1)
}
}
}
match os.args[1] {
'install' {
install()
}
'uninstall' {
uninstall()
}
'push' {
if os.args.len == 3 {
push(os.args[2])
} else {
error_message("Incorrect number of arguments for 'push'.")
exit(1)
}
}
'login' {
login()
}
'logout' {
logout()
}
'delete' {
if os.args.len == 3 {
delete(os.args[2])
} else {
error_message("Incorrect number of arguments for 'delete'.")
exit(1)
}
}
'rename' {
if os.args.len == 4 {
rename(os.args[2], os.args[3])
} else {
error_message("Incorrect number of arguments for 'rename'.")
exit(1)
}
}
'ls' {
if os.args.len == 2 {
ls(false)
} else if os.args.len == 3 && os.args[2] == 'url' {
ls(true)
} else {
error_message("Incorrect usage of 'ls'. Use 'ls' or 'ls url'.")
exit(1)
}
}
'help' {
help()
}
else {
error_message('Unknown command: ' + os.args[1])
exit(1)
}
}
}