From c8191a19e15c43c1871880af478f0fc0bbf9c0b9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 11:52:07 +0100 Subject: [PATCH 1/9] refactor: removed custom error type; greatly increased internal api --- Makefile | 6 +++- containers.v | 48 ++++---------------------------- docker.v | 77 ++++++++++++++++++++++++++-------------------------- errors.v | 22 --------------- images.v | 20 +++----------- 5 files changed, 54 insertions(+), 119 deletions(-) delete mode 100644 errors.v diff --git a/Makefile b/Makefile index b25f105..7a86c0e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # =====CONFIG===== V_PATH ?= v -V := $(V_PATH) -showcc +V := $(V_PATH) -showcc -d use_openssl all: vdocker @@ -10,6 +10,10 @@ all: vdocker vdocker: $(V) -g -shared . +.PHONY: c +c: + $(V) -o docker.c . + # =====DOCS===== .PHONY: api-docs diff --git a/containers.v b/containers.v index 809ef38..7b0af3a 100644 --- a/containers.v +++ b/containers.v @@ -1,6 +1,5 @@ module docker -import json import time import net.http { Method } import types { ContainerListItem } @@ -38,29 +37,14 @@ pub: pub fn (mut d DockerConn) container_create(c NewContainer) !CreatedContainer { d.send_request_with_json(Method.post, '/containers/create', c)! - head, res := d.read_response()! - if head.status_code != 201 { - data := json.decode(DockerError, res)! - - return error(data.message) - } - - data := json.decode(CreatedContainer, res)! - - return data + return d.read_json_response() } // start_container starts the container with the given id. pub fn (mut d DockerConn) container_start(id string) ! { d.send_request(Method.post, '/containers/$id/start')! - head, body := d.read_response()! - - if head.status_code != 204 { - data := json.decode(DockerError, body)! - - return error(data.message) - } + d.read_response()! } struct ContainerInspect { @@ -83,15 +67,8 @@ pub mut: pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { d.send_request(Method.get, '/containers/$id/json')! - head, body := d.read_response()! - if head.status_code != 200 { - data := json.decode(DockerError, body)! - - return error(data.message) - } - - mut data := json.decode(ContainerInspect, body)! + mut data := d.read_json_response()! // The Docker engine API *should* always return UTC time. data.state.start_time = time.parse_rfc3339(data.state.start_time_str)! @@ -105,26 +82,13 @@ pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { pub fn (mut d DockerConn) container_remove(id string) ! { d.send_request(Method.delete, '/containers/$id')! - head, body := d.read_response()! - - if head.status_code != 204 { - data := json.decode(DockerError, body)! - - return error(data.message) - } + d.read_response()! } pub fn (mut d DockerConn) container_get_logs(id string) !&StreamFormatReader { d.send_request(Method.get, '/containers/$id/logs?stdout=true&stderr=true')! - head := d.read_response_head()! - - if head.status_code != 200 { - content_length := head.header.get(http.CommonHeader.content_length)!.int() - body := d.read_response_body(content_length)! - data := json.decode(DockerError, body)! - - return error(data.message) - } + d.read_response_head()! + d.check_error()! return d.get_stream_format_reader() } diff --git a/docker.v b/docker.v index 7eb5ea2..d1180f8 100644 --- a/docker.v +++ b/docker.v @@ -27,6 +27,7 @@ mut: url string params map[string]string content_type string + head http.Response body string } @@ -81,79 +82,61 @@ fn (mut d DockerConn) send_request_with_json(method http.Method, url_str stri // '\r\n\r\n', after which it parses the response as an HTTP response. // Importantly, this function never consumes the reader past the HTTP // separator, so the body can be read fully later on. -fn (mut d DockerConn) read_response_head() !http.Response { +fn (mut d DockerConn) read_response_head() ! { mut res := []u8{} util.read_until_separator(mut d.reader, mut res, docker.http_separator)! - return http.parse_response(res.bytestr()) + d.head = http.parse_response(res.bytestr())! } -// read_response_body reads `length` bytes from the stream. It can be used when -// the response encoding isn't chunked to fully read it. -fn (mut d DockerConn) read_response_body(length int) !string { - if length == 0 { - return '' +fn (mut d DockerConn) read_response_body() ! { + content_length := d.head.header.get(.content_length)!.int() + + if content_length == 0 { + return } mut buf := []u8{len: docker.buf_len} mut c := 0 mut builder := strings.new_builder(docker.buf_len) - for builder.len < length { + for builder.len < content_length { c = d.reader.read(mut buf) or { break } builder.write(buf[..c])! } - return builder.str() + d.body = builder.str() } // read_response is a convenience function which always consumes the entire // response & returns it. It should only be used when we're certain that the // result isn't too large. -fn (mut d DockerConn) read_response() !(http.Response, string) { - head := d.read_response_head()! - - if head.status().is_error() { - content_length := head.header.get(.content_length)!.int() - body := d.read_response_body(content_length)! - mut err := json.decode(DockerError, body)! - err.status = head.status_code - - return err - } +fn (mut d DockerConn) read_response() ! { + d.read_response_head()! + d.check_error()! // 204 means "No Content", so we can assume nothing follows after this - if head.status() == .no_content { - return head, '' + if d.head.status() == .no_content { + return } - if head.header.get(http.CommonHeader.transfer_encoding) or { '' } == 'chunked' { + if d.head.header.get(http.CommonHeader.transfer_encoding) or { '' } == 'chunked' { mut builder := strings.new_builder(1024) mut body := d.get_chunked_response_reader() util.reader_to_writer(mut body, mut builder)! - - return head, builder.str() + d.body = builder.str() + } else { + d.read_response_body()! } - - content_length := head.header.get(http.CommonHeader.content_length)!.int() - body := d.read_response_body(content_length)! - - return head, body } fn (mut d DockerConn) read_json_response() !T { - head, body := d.read_response()! + d.read_response()! - if head.status_code < 200 || head.status_code > 300 { - data := json.decode(DockerError, body)! - - return docker_error(head.status_code, data.message) - } - - mut data := json.decode(T, body)! + mut data := json.decode(T, d.body)! //$for field in T.fields { //$if field.typ is time.Time { @@ -180,3 +163,21 @@ fn (mut d DockerConn) get_stream_format_reader() &StreamFormatReader { return r2 } + +struct DockerError { +pub: + message string +} + +// check_error should be called after read_response_head. If the status code of +// the response is an error, the body is consumed and the Docker HTTP error is +// returned as a V error. If the status isn't the error, this function is a +// no-op. +fn (mut d DockerConn) check_error() ! { + if d.head.status().is_error() { + d.read_response_body()! + d_err := json.decode(DockerError, d.body)! + + return error_with_code('$d.head.status(): $d_err.message', d.head.status_code) + } +} diff --git a/errors.v b/errors.v deleted file mode 100644 index 31ed81a..0000000 --- a/errors.v +++ /dev/null @@ -1,22 +0,0 @@ -module docker - -struct DockerError { -pub mut: - status int [skip] - message string -} - -fn (err DockerError) code() int { - return err.status -} - -fn (err DockerError) msg() string { - return err.message -} - -fn docker_error(status int, message string) DockerError { - return DockerError{ - status: status - message: message - } -} diff --git a/images.v b/images.v index a6f2a23..03f9387 100644 --- a/images.v +++ b/images.v @@ -2,13 +2,11 @@ module docker import net.http { Method } import types { Image } -import json pub fn (mut d DockerConn) image_inspect(image string) !Image { d.send_request(.get, '/images/$image/json')! - _, body := d.read_response()! - data := json.decode(Image, body)! + data := d.read_json_response()! return data } @@ -16,16 +14,8 @@ pub fn (mut d DockerConn) image_inspect(image string) !Image { // pull_image pulls the given image:tag. pub fn (mut d DockerConn) pull_image(image string, tag string) ! { d.send_request(Method.post, '/images/create?fromImage=$image&tag=$tag')! - head := d.read_response_head()! - - if head.status().is_error() { - content_length := head.header.get(.content_length)!.int() - body := d.read_response_body(content_length)! - mut err := json.decode(DockerError, body)! - err.status = head.status_code - - return err - } + d.read_response_head()! + d.check_error()! // Keep reading the body until the pull has completed mut body := d.get_chunked_response_reader() @@ -40,9 +30,7 @@ pub fn (mut d DockerConn) pull_image(image string, tag string) ! { // create_image_from_container creates a new image from a container. pub fn (mut d DockerConn) create_image_from_container(id string, repo string, tag string) !Image { d.send_request(.post, '/commit?container=$id&repo=$repo&tag=$tag')! - _, body := d.read_response()! - - data := json.decode(Image, body)! + data := d.read_json_response()! return data } From 7149c93b6fe09429c60781a21a61459b1a8b0198 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 12:02:51 +0100 Subject: [PATCH 2/9] refactor: clean up read_response function --- docker.v | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/docker.v b/docker.v index d1180f8..8e6fdac 100644 --- a/docker.v +++ b/docker.v @@ -91,6 +91,20 @@ fn (mut d DockerConn) read_response_head() ! { } fn (mut d DockerConn) read_response_body() ! { + if d.head.status() == .no_content { + return + } + + if d.head.header.get(http.CommonHeader.transfer_encoding) or { '' } == 'chunked' { + mut builder := strings.new_builder(1024) + mut body := d.get_chunked_response_reader() + + util.reader_to_writer(mut body, mut builder)! + d.body = builder.str() + + return + } + content_length := d.head.header.get(.content_length)!.int() if content_length == 0 { @@ -102,7 +116,7 @@ fn (mut d DockerConn) read_response_body() ! { mut builder := strings.new_builder(docker.buf_len) for builder.len < content_length { - c = d.reader.read(mut buf) or { break } + c = d.reader.read(mut buf)! builder.write(buf[..c])! } @@ -111,32 +125,21 @@ fn (mut d DockerConn) read_response_body() ! { } // read_response is a convenience function which always consumes the entire -// response & returns it. It should only be used when we're certain that the -// result isn't too large. +// response and loads it into memory. It should only be used when we're certain +// that the result isn't too large, as even chunked responses will get fully +// loaded into memory. fn (mut d DockerConn) read_response() ! { d.read_response_head()! d.check_error()! - - // 204 means "No Content", so we can assume nothing follows after this - if d.head.status() == .no_content { - return - } - - if d.head.header.get(http.CommonHeader.transfer_encoding) or { '' } == 'chunked' { - mut builder := strings.new_builder(1024) - mut body := d.get_chunked_response_reader() - - util.reader_to_writer(mut body, mut builder)! - d.body = builder.str() - } else { - d.read_response_body()! - } + d.read_response_body()! } +// read_json_response is a convenience function that runs read_response +// before parsing its contents, which is assumed to be JSON, into a struct. fn (mut d DockerConn) read_json_response() !T { d.read_response()! - mut data := json.decode(T, d.body)! + data := json.decode(T, d.body)! //$for field in T.fields { //$if field.typ is time.Time { From 2db4afc226d03c8f60976eebf72c8d0a9f5089cc Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 12:53:30 +0100 Subject: [PATCH 3/9] refactor: use unified internal api for requests --- containers.v | 23 ++++++++++----- docker.v | 81 ++++++++++++++++------------------------------------ images.v | 24 +++++++++++----- request.v | 22 ++++++++++---- volumes.v | 5 ++-- 5 files changed, 78 insertions(+), 77 deletions(-) diff --git a/containers.v b/containers.v index 7b0af3a..14d295f 100644 --- a/containers.v +++ b/containers.v @@ -1,7 +1,7 @@ module docker import time -import net.http { Method } +import net.http import types { ContainerListItem } [params] @@ -13,7 +13,7 @@ pub struct ContainerListConfig { } pub fn (mut d DockerConn) container_list(c ContainerListConfig) ![]ContainerListItem { - d.get('/containers/json') + d.request(.get, '/containers/json', {}) d.params(c) d.send()! @@ -36,14 +36,17 @@ pub: } pub fn (mut d DockerConn) container_create(c NewContainer) !CreatedContainer { - d.send_request_with_json(Method.post, '/containers/create', c)! + d.request(.post, '/containers/create', {}) + d.body_json(c) + d.send()! return d.read_json_response() } // start_container starts the container with the given id. pub fn (mut d DockerConn) container_start(id string) ! { - d.send_request(Method.post, '/containers/$id/start')! + d.request(.post, '/containers/$id/start', {}) + d.send()! d.read_response()! } @@ -66,7 +69,8 @@ pub mut: } pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { - d.send_request(Method.get, '/containers/$id/json')! + d.request(.get, '/containers/$id/json', {}) + d.send()! mut data := d.read_json_response()! @@ -81,12 +85,17 @@ pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { } pub fn (mut d DockerConn) container_remove(id string) ! { - d.send_request(Method.delete, '/containers/$id')! + d.request(.delete, '/containers/$id', {}) + d.send()! d.read_response()! } pub fn (mut d DockerConn) container_get_logs(id string) !&StreamFormatReader { - d.send_request(Method.get, '/containers/$id/logs?stdout=true&stderr=true')! + d.request(.get, '/containers/$id/logs', { + 'stdout': 'true' + 'stderr': 'true' + }) + d.send()! d.read_response_head()! d.check_error()! diff --git a/docker.v b/docker.v index 8e6fdac..093e657 100644 --- a/docker.v +++ b/docker.v @@ -4,7 +4,6 @@ import net.unix import io import net.http import strings -import net.urllib import json import util @@ -27,8 +26,11 @@ mut: url string params map[string]string content_type string - head http.Response - body string + // Before send: body of the request + // After send: body of response + body string + // HTTP head of the response + head http.Response } // new_conn creates a new connection to the Docker daemon. @@ -48,36 +50,6 @@ pub fn (mut d DockerConn) close() ! { d.socket.close()! } -// send_request sends an HTTP request without body. -fn (mut d DockerConn) send_request(method http.Method, url_str string) ! { - url := urllib.parse('/$docker.api_version$url_str')! - req := '$method $url.request_uri() HTTP/1.1\nHost: localhost\n\n' - - d.socket.write_string(req)! - - // When starting a new request, the reader needs to be reset. - d.reader = io.new_buffered_reader(reader: d.socket) -} - -// send_request_with_body sends an HTTP request with the given body. -fn (mut d DockerConn) send_request_with_body(method http.Method, url_str string, content_type string, body string) ! { - url := urllib.parse('/$docker.api_version$url_str')! - req := '$method $url.request_uri() HTTP/1.1\nHost: localhost\nContent-Type: $content_type\nContent-Length: $body.len\n\n$body\n\n' - - d.socket.write_string(req)! - - // When starting a new request, the reader needs to be reset. - d.reader = io.new_buffered_reader(reader: d.socket) -} - -// send_request_with_json is a convenience wrapper around -// send_request_with_body that encodes the input as JSON. -fn (mut d DockerConn) send_request_with_json(method http.Method, url_str string, data &T) ! { - body := json.encode(data) - - return d.send_request_with_body(method, url_str, 'application/json', body) -} - // read_response_head consumes the socket's contents until it encounters // '\r\n\r\n', after which it parses the response as an HTTP response. // Importantly, this function never consumes the reader past the HTTP @@ -91,34 +63,31 @@ fn (mut d DockerConn) read_response_head() ! { } fn (mut d DockerConn) read_response_body() ! { - if d.head.status() == .no_content { - return - } - - if d.head.header.get(http.CommonHeader.transfer_encoding) or { '' } == 'chunked' { - mut builder := strings.new_builder(1024) - mut body := d.get_chunked_response_reader() - - util.reader_to_writer(mut body, mut builder)! - d.body = builder.str() - - return - } - - content_length := d.head.header.get(.content_length)!.int() - - if content_length == 0 { + if d.head.status() == .no_content { return } - mut buf := []u8{len: docker.buf_len} - mut c := 0 mut builder := strings.new_builder(docker.buf_len) - for builder.len < content_length { - c = d.reader.read(mut buf)! + if d.head.header.get(.transfer_encoding) or { '' } == 'chunked' { + mut body_stream := d.get_chunked_response_reader() - builder.write(buf[..c])! + util.reader_to_writer(mut body_stream, mut builder)! + } else { + content_length := d.head.header.get(.content_length)!.int() + + if content_length == 0 { + return + } + + mut buf := []u8{len: docker.buf_len} + mut c := 0 + + for builder.len < content_length { + c = d.reader.read(mut buf)! + + builder.write(buf[..c])! + } } d.body = builder.str() @@ -131,7 +100,7 @@ fn (mut d DockerConn) read_response_body() ! { fn (mut d DockerConn) read_response() ! { d.read_response_head()! d.check_error()! - d.read_response_body()! + d.read_response_body()! } // read_json_response is a convenience function that runs read_response diff --git a/images.v b/images.v index 03f9387..d879cd9 100644 --- a/images.v +++ b/images.v @@ -1,10 +1,11 @@ module docker -import net.http { Method } +import net.http import types { Image } pub fn (mut d DockerConn) image_inspect(image string) !Image { - d.send_request(.get, '/images/$image/json')! + d.request(.get, '/images/$image/json', {}) + d.send()! data := d.read_json_response()! @@ -13,7 +14,11 @@ pub fn (mut d DockerConn) image_inspect(image string) !Image { // pull_image pulls the given image:tag. pub fn (mut d DockerConn) pull_image(image string, tag string) ! { - d.send_request(Method.post, '/images/create?fromImage=$image&tag=$tag')! + d.request(.post, '/images/create', { + 'fromImage': image + 'tag': tag + }) + d.send()! d.read_response_head()! d.check_error()! @@ -29,14 +34,19 @@ pub fn (mut d DockerConn) pull_image(image string, tag string) ! { // create_image_from_container creates a new image from a container. pub fn (mut d DockerConn) create_image_from_container(id string, repo string, tag string) !Image { - d.send_request(.post, '/commit?container=$id&repo=$repo&tag=$tag')! - data := d.read_json_response()! + d.request(.post, '/commit', { + 'container': id + 'repo': repo + 'tag': tag + }) + d.send()! - return data + return d.read_json_response()! } // remove_image removes the image with the given id. pub fn (mut d DockerConn) remove_image(id string) ! { - d.send_request(.delete, '/images/$id')! + d.request(.delete, '/images/$id', {}) + d.send()! d.read_response()! } diff --git a/request.v b/request.v index fdee0d4..f844717 100644 --- a/request.v +++ b/request.v @@ -3,17 +3,29 @@ module docker import net.http import net.urllib import io +import json -fn (mut d DockerConn) request(method http.Method, url_str string) { +fn (mut d DockerConn) request(method http.Method, url string, params map[string]string) { d.method = method - d.url = url_str - d.params.clear() + d.url = url d.content_type = '' d.body = '' + + d.params.clear() + + for key, value in params { + d.params[key] = urllib.query_escape(value.replace("'", '"')) + } } -fn (mut d DockerConn) get(url_str string) { - d.request(http.Method.get, url_str) +fn (mut d DockerConn) body(content_type string, body string) { + d.content_type = content_type + d.body = body +} + +fn (mut d DockerConn) body_json(data T) { + d.content_type = 'application/json' + d.body = json.encode(data) } fn (mut d DockerConn) params(o T) { diff --git a/volumes.v b/volumes.v index 5bd9936..6ce9905 100644 --- a/volumes.v +++ b/volumes.v @@ -1,6 +1,6 @@ module docker -import net.http { Method } +import net.http import time struct UsageData { @@ -36,7 +36,8 @@ struct VolumeListResponse { } pub fn (mut d DockerConn) volume_list() !VolumeListResponse { - d.send_request(Method.get, '/volumes')! + d.request(.get, '/volumes', {}) + d.send()! mut data := d.read_json_response()! From 6b578a80cd53116c8344a5d413e832685fcc99e7 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 13:00:18 +0100 Subject: [PATCH 4/9] refactor: rename some image functions --- images.v | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/images.v b/images.v index d879cd9..15b6861 100644 --- a/images.v +++ b/images.v @@ -12,8 +12,8 @@ pub fn (mut d DockerConn) image_inspect(image string) !Image { return data } -// pull_image pulls the given image:tag. -pub fn (mut d DockerConn) pull_image(image string, tag string) ! { +// image_pull pulls the given image:tag. +pub fn (mut d DockerConn) image_pull(image string, tag string) ! { d.request(.post, '/images/create', { 'fromImage': image 'tag': tag @@ -33,7 +33,7 @@ pub fn (mut d DockerConn) pull_image(image string, tag string) ! { } // create_image_from_container creates a new image from a container. -pub fn (mut d DockerConn) create_image_from_container(id string, repo string, tag string) !Image { +pub fn (mut d DockerConn) image_from_container(id string, repo string, tag string) !Image { d.request(.post, '/commit', { 'container': id 'repo': repo @@ -45,7 +45,7 @@ pub fn (mut d DockerConn) create_image_from_container(id string, repo string, ta } // remove_image removes the image with the given id. -pub fn (mut d DockerConn) remove_image(id string) ! { +pub fn (mut d DockerConn) image_remove(id string) ! { d.request(.delete, '/images/$id', {}) d.send()! d.read_response()! From 9258bb81465fc09dbe434dd0341a287b1aa2e94d Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 13:11:41 +0100 Subject: [PATCH 5/9] refactor: moving some stuff --- containers.v | 1 - images.v | 1 - types/volume.v | 22 ++++++++++++++++++++++ volumes.v | 21 +-------------------- 4 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 types/volume.v diff --git a/containers.v b/containers.v index 14d295f..545cce1 100644 --- a/containers.v +++ b/containers.v @@ -1,7 +1,6 @@ module docker import time -import net.http import types { ContainerListItem } [params] diff --git a/images.v b/images.v index 15b6861..ec328bd 100644 --- a/images.v +++ b/images.v @@ -1,6 +1,5 @@ module docker -import net.http import types { Image } pub fn (mut d DockerConn) image_inspect(image string) !Image { diff --git a/types/volume.v b/types/volume.v new file mode 100644 index 0000000..14bc012 --- /dev/null +++ b/types/volume.v @@ -0,0 +1,22 @@ +module types + +import time + +pub struct UsageData { + size int [json: Size] + ref_count int [json: RefCount] +} + +pub struct Volume { +pub mut: + created_at_str string [json: CreatedAt] + created_at time.Time [skip] + name string [json: Name] + driver string [json: Driver] + mountpoint string [json: Mountpoint] + status map[string]string [json: Status] + labels map[string]string [json: Labels] + scope string [json: Scope] + options map[string]string [json: Options] + usage_data UsageData [json: UsageData] +} diff --git a/volumes.v b/volumes.v index 6ce9905..8442ef0 100644 --- a/volumes.v +++ b/volumes.v @@ -1,26 +1,7 @@ module docker -import net.http import time - -struct UsageData { - size int [json: Size] - ref_count int [json: RefCount] -} - -struct Volume { - created_at_str string [json: CreatedAt] -pub mut: - created_at time.Time [skip] - name string [json: Name] - driver string [json: Driver] - mountpoint string [json: Mountpoint] - status map[string]string [json: Status] - labels map[string]string [json: Labels] - scope string [json: Scope] - options map[string]string [json: Options] - usage_data UsageData [json: UsageData] -} +import types { Volume } [params] pub struct VolumeListFilter { From 5bc54c37f3e4dbaa25408747d950f4060eee996e Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 13:25:58 +0100 Subject: [PATCH 6/9] feat: implement image_tag --- ROADMAP.md | 2 +- images.v | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 52e22a2..648d4c5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -42,7 +42,7 @@ reference](https://docs.docker.com/engine/api/v1.41/). - [ ] Inspect an image - [ ] Get the history of an image - [ ] Push an image - - [ ] Tag an image + - [x] Tag an image - [ ] Remove an image - [ ] Search images - [ ] Delete unused images diff --git a/images.v b/images.v index ec328bd..1da31c7 100644 --- a/images.v +++ b/images.v @@ -49,3 +49,9 @@ pub fn (mut d DockerConn) image_remove(id string) ! { d.send()! d.read_response()! } + +pub fn (mut d DockerConn) image_tag(name string, repo string, tag string) ! { + d.request(.post, '/images/$name/tag', {'repo': repo, 'tag': tag}) + d.send()! + d.read_response()! +} From d31681843c58f4afc91fc1cd3b124b6f05d44e92 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 16:31:48 +0100 Subject: [PATCH 7/9] refactor: another change to internal api --- containers.v | 13 +++++++------ images.v | 20 +++++++++++++------- request.v | 20 +++++++++++--------- volumes.v | 2 +- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/containers.v b/containers.v index 545cce1..6981a2c 100644 --- a/containers.v +++ b/containers.v @@ -12,7 +12,7 @@ pub struct ContainerListConfig { } pub fn (mut d DockerConn) container_list(c ContainerListConfig) ![]ContainerListItem { - d.request(.get, '/containers/json', {}) + d.request(.get, '/containers/json') d.params(c) d.send()! @@ -35,7 +35,7 @@ pub: } pub fn (mut d DockerConn) container_create(c NewContainer) !CreatedContainer { - d.request(.post, '/containers/create', {}) + d.request(.post, '/containers/create') d.body_json(c) d.send()! @@ -44,7 +44,7 @@ pub fn (mut d DockerConn) container_create(c NewContainer) !CreatedContainer { // start_container starts the container with the given id. pub fn (mut d DockerConn) container_start(id string) ! { - d.request(.post, '/containers/$id/start', {}) + d.request(.post, '/containers/$id/start') d.send()! d.read_response()! } @@ -68,7 +68,7 @@ pub mut: } pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { - d.request(.get, '/containers/$id/json', {}) + d.request(.get, '/containers/$id/json') d.send()! mut data := d.read_json_response()! @@ -84,13 +84,14 @@ pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { } pub fn (mut d DockerConn) container_remove(id string) ! { - d.request(.delete, '/containers/$id', {}) + d.request(.delete, '/containers/$id') d.send()! d.read_response()! } pub fn (mut d DockerConn) container_get_logs(id string) !&StreamFormatReader { - d.request(.get, '/containers/$id/logs', { + d.request(.get, '/containers/$id/logs') + d.params({ 'stdout': 'true' 'stderr': 'true' }) diff --git a/images.v b/images.v index 1da31c7..76df20d 100644 --- a/images.v +++ b/images.v @@ -3,7 +3,7 @@ module docker import types { Image } pub fn (mut d DockerConn) image_inspect(image string) !Image { - d.request(.get, '/images/$image/json', {}) + d.request(.get, '/images/$image/json') d.send()! data := d.read_json_response()! @@ -13,7 +13,8 @@ pub fn (mut d DockerConn) image_inspect(image string) !Image { // image_pull pulls the given image:tag. pub fn (mut d DockerConn) image_pull(image string, tag string) ! { - d.request(.post, '/images/create', { + d.request(.post, '/images/create') + d.params({ 'fromImage': image 'tag': tag }) @@ -33,7 +34,8 @@ pub fn (mut d DockerConn) image_pull(image string, tag string) ! { // create_image_from_container creates a new image from a container. pub fn (mut d DockerConn) image_from_container(id string, repo string, tag string) !Image { - d.request(.post, '/commit', { + d.request(.post, '/commit') + d.params({ 'container': id 'repo': repo 'tag': tag @@ -45,13 +47,17 @@ pub fn (mut d DockerConn) image_from_container(id string, repo string, tag strin // remove_image removes the image with the given id. pub fn (mut d DockerConn) image_remove(id string) ! { - d.request(.delete, '/images/$id', {}) + d.request(.delete, '/images/$id') d.send()! d.read_response()! } pub fn (mut d DockerConn) image_tag(name string, repo string, tag string) ! { - d.request(.post, '/images/$name/tag', {'repo': repo, 'tag': tag}) - d.send()! - d.read_response()! + d.request(.post, '/images/$name/tag') + d.params({ + 'repo': repo + 'tag': tag + }) + d.send()! + d.read_response()! } diff --git a/request.v b/request.v index f844717..92d7073 100644 --- a/request.v +++ b/request.v @@ -5,17 +5,13 @@ import net.urllib import io import json -fn (mut d DockerConn) request(method http.Method, url string, params map[string]string) { +fn (mut d DockerConn) request(method http.Method, url string) { d.method = method d.url = url d.content_type = '' d.body = '' d.params.clear() - - for key, value in params { - d.params[key] = urllib.query_escape(value.replace("'", '"')) - } } fn (mut d DockerConn) body(content_type string, body string) { @@ -29,11 +25,17 @@ fn (mut d DockerConn) body_json(data T) { } fn (mut d DockerConn) params(o T) { - $for field in T.fields { - v := o.$(field.name) + $if T is map[string]string { + for key, value in o { + d.params[key] = urllib.query_escape(value.replace("'", '"')) + } + } $else { + $for field in T.fields { + v := o.$(field.name) - if !isnil(v) { - d.params[field.name] = urllib.query_escape(v.str().replace("'", '"')) + if !isnil(v) { + d.params[field.name] = urllib.query_escape(v.str().replace("'", '"')) + } } } } diff --git a/volumes.v b/volumes.v index 8442ef0..be648c8 100644 --- a/volumes.v +++ b/volumes.v @@ -17,7 +17,7 @@ struct VolumeListResponse { } pub fn (mut d DockerConn) volume_list() !VolumeListResponse { - d.request(.get, '/volumes', {}) + d.request(.get, '/volumes') d.send()! mut data := d.read_json_response()! From ea8e49d55f5d449235cfd1b1d6eb22f5a08807f9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 16:48:17 +0100 Subject: [PATCH 8/9] refactor: comments are important --- docker.v | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docker.v b/docker.v index 093e657..f6c1361 100644 --- a/docker.v +++ b/docker.v @@ -62,6 +62,10 @@ fn (mut d DockerConn) read_response_head() ! { d.head = http.parse_response(res.bytestr())! } +// read_response_body consumes the rest of the HTTP response and stores it as +// the response body. This function should only be called after +// read_response_head. This function always reads the entire response into +// memory, even if it's chunked. fn (mut d DockerConn) read_response_body() ! { if d.head.status() == .no_content { return @@ -93,7 +97,7 @@ fn (mut d DockerConn) read_response_body() ! { d.body = builder.str() } -// read_response is a convenience function which always consumes the entire +// read_response is a convenience function that always consumes the entire // response and loads it into memory. It should only be used when we're certain // that the result isn't too large, as even chunked responses will get fully // loaded into memory. @@ -120,7 +124,7 @@ fn (mut d DockerConn) read_json_response() !T { } // get_chunked_response_reader returns a ChunkedResponseReader using the socket -// as reader. +// as reader. This function should only be called after check_error. fn (mut d DockerConn) get_chunked_response_reader() &ChunkedResponseReader { r := new_chunked_response_reader(d.reader) @@ -128,7 +132,7 @@ fn (mut d DockerConn) get_chunked_response_reader() &ChunkedResponseReader { } // get_stream_format_reader returns a StreamFormatReader using the socket as -// reader. +// reader. This function should only be called after check_error. fn (mut d DockerConn) get_stream_format_reader() &StreamFormatReader { r := new_chunked_response_reader(d.reader) r2 := new_stream_format_reader(r) @@ -144,7 +148,7 @@ pub: // check_error should be called after read_response_head. If the status code of // the response is an error, the body is consumed and the Docker HTTP error is // returned as a V error. If the status isn't the error, this function is a -// no-op. +// no-op, and the body can be read. fn (mut d DockerConn) check_error() ! { if d.head.status().is_error() { d.read_response_body()! From 0743085bac2ba4a62f7fc4c9cbf37abc128faf06 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 16:57:55 +0100 Subject: [PATCH 9/9] refactor: final reread --- docker.v | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker.v b/docker.v index f6c1361..cd30b60 100644 --- a/docker.v +++ b/docker.v @@ -81,6 +81,8 @@ fn (mut d DockerConn) read_response_body() ! { content_length := d.head.header.get(.content_length)!.int() if content_length == 0 { + d.body = '' + return }