提交 24d22371 编写于 作者: W WangFengTu 提交者: lifeng68

support registry mirrors

Signed-off-by: NWangFengTu <wangfengtu@huawei.com>
上级 624f8d8d
/******************************************************************************
* Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved.
* iSulad licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: liuhao
* Create: 2019-07-15
* Description: isula image pull operator implement
*******************************************************************************/
#include "isula_image_pull.h"
#include "isula_libutils/log.h"
#include "utils.h"
#include "oci_common_operators.h"
#include "registry.h"
int isula_pull_image(const im_pull_request *request, im_pull_response **response)
{
int ret = -1;
registry_pull_options *options = NULL;
if (request == NULL || request->image == NULL || response == NULL) {
ERROR("Invalid NULL param");
return -1;
}
options = (registry_pull_options *)util_common_calloc_s(sizeof(registry_pull_options));
if (options == NULL) {
ERROR("Out of memory");
goto err_out;
}
options->image_name = util_strdup_s(request->image);
options->dest_image_name = util_strdup_s(request->image);
ret = registry_pull(options);
if (ret != 0) {
ERROR("registry pull failed");
goto err_out;
}
*response = (im_pull_response *)util_common_calloc_s(sizeof(im_pull_response));
if (*response == NULL) {
ERROR("Out of memory");
goto err_out;
}
(*response)->image_ref = util_strdup_s(request->image);
goto out;
err_out:
free_im_pull_response(*response);
*response = NULL;
ret = -1;
out:
free_registry_pull_options(options);
return ret;
}
......@@ -53,6 +53,105 @@ char *get_last_part(char **parts)
return last_part;
}
char *oci_get_host(const char *name)
{
char **parts = NULL;
char *host = NULL;
if (name == NULL) {
ERROR("Invalid NULL param");
return NULL;
}
parts = util_string_split(name, '/');
if ((parts != NULL && *parts != NULL && !strings_contains_any(*parts, ".:") &&
strcmp(*parts, "localhost")) || (strstr(name, "/") == NULL)) {
util_free_array(parts);
return NULL;
}
if (parts != NULL) {
host = util_strdup_s(parts[0]);
util_free_array(parts);
}
return host;
}
char *oci_default_tag(const char *name)
{
char temp[PATH_MAX] = { 0 };
char **parts = NULL;
char *last_part = NULL;
char *add_default_tag = "";
if (name == NULL) {
ERROR("Invalid NULL param");
return NULL;
}
parts = util_string_split(name, '/');
if (parts == NULL) {
ERROR("split %s by '/' failed", name);
return NULL;
}
last_part = get_last_part(parts);
if (last_part != NULL && strrchr(last_part, ':') == NULL) {
add_default_tag = DEFAULT_TAG;
}
util_free_array(parts);
// Add image's default tag
int nret = snprintf(temp, sizeof(temp), "%s%s", name, add_default_tag);
if (nret < 0 || (size_t)nret >= sizeof(temp)) {
ERROR("sprint temp image name failed");
return NULL;
}
return util_strdup_s(temp);
}
char *oci_host_from_mirror(const char *mirror)
{
const char *host = mirror;
if (mirror == NULL) {
ERROR("Invalid NULL param");
return NULL;
}
if (util_has_prefix(mirror, HTTPS_PREFIX)) {
host = mirror + strlen(HTTPS_PREFIX);
} else if (util_has_prefix(mirror, HTTPS_PREFIX)) {
host = mirror + strlen(HTTP_PREFIX);
}
return util_strdup_s(host);
}
char *oci_add_host(const char *host, const char *name)
{
char *with_host = NULL;
if (host == NULL || name == NULL) {
ERROR("Invalid NULL param");
return NULL;
}
with_host = util_common_calloc_s(strlen(host) + strlen("/") + strlen(name) + 1);
if (with_host == NULL) {
ERROR("out of memory");
return NULL;
}
(void)strcat(with_host, host);
(void)strcat(with_host, "/");
(void)strcat(with_host, name);
return with_host;
}
// normalize the unqualified image to be domain/repo/image...
char *oci_normalize_image_name(const char *name)
{
......
......@@ -20,10 +20,17 @@
#include "isula_libutils/imagetool_image.h"
#include "isula_libutils/oci_image_spec.h"
#define HTTPS_PREFIX "https://"
#define HTTP_PREFIX "http://"
#ifdef __cplusplus
extern "C" {
#endif
char *oci_get_host(const char *name);
char *oci_host_from_mirror(const char *mirror);
char *oci_default_tag(const char *name);
char *oci_add_host(const char *domain, const char *name);
char *oci_normalize_image_name(const char *name);
int oci_split_image_name(const char *image_name, char **host, char **name, char **tag);
char *oci_full_image_name(const char *host, const char *name, const char *tag);
......
......@@ -18,7 +18,10 @@
#include <semaphore.h>
#include "isula_libutils/log.h"
#include "isula_image_pull.h"
#include "log.h"
#include "oci_pull.h"
#include "oci_login.h"
#include "oci_logout.h"
#include "registry.h"
#include "containers_store.h"
......@@ -76,18 +79,16 @@ out:
int oci_init(const struct service_arguments *args)
{
int ret = -1;
registry_init_options options;
int ret = 0;
if (args == NULL) {
ERROR("Invalid image config");
return ret;
}
options.use_decrypted_key = conf_get_use_decrypted_key_flag();
options.skip_tls_verify = conf_get_skip_insecure_verify_flag();
ret = registry_init(&options);
ret = registry_init();
if (ret != 0) {
ret = -1;
goto out;
}
......@@ -97,12 +98,13 @@ int oci_init(const struct service_arguments *args)
}
out:
return ret;
}
int oci_pull_rf(const im_pull_request *request, im_pull_response **response)
{
return isula_pull_image(request, response);
return oci_do_pull_image(request, response);
}
int oci_prepare_rf(const im_prepare_request *request, char **real_rootfs)
......@@ -460,8 +462,7 @@ int oci_login(const im_login_request *request)
return -1;
}
// TODO call storage login load interface
//ret = isula_do_login(request->server, request->username, request->password);
ret = oci_do_login(request->server, request->username, request->password);
if (ret != 0) {
ERROR("Login failed");
}
......@@ -478,8 +479,7 @@ int oci_logout(const im_logout_request *request)
return -1;
}
// TODO call storage logout load interface
//ret = isula_do_logout(request->server);
ret = oci_do_logout(request->server);
if (ret != 0) {
ERROR("Logout failed");
}
......
/******************************************************************************
* Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved.
* iSulad licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: wangfengtu
* Create: 2020-05-07
* Description: isula login operator implement
*******************************************************************************/
#include "oci_login.h"
#include "utils.h"
#include "libisulad.h"
#include "log.h"
#include "registry.h"
#include "isulad_config.h"
static int is_valid_arguments(const char *server, const char *username, const char *password)
{
if (server == NULL) {
isulad_set_error_message("Login requires server address");
return -1;
}
if (username == NULL || password == NULL) {
isulad_set_error_message("Missing username or password");
return -1;
}
return 0;
}
int oci_do_login(const char *server, const char *username, const char *password)
{
int ret = -1;
registry_login_options options;
char **insecure_registries = NULL;
char **registry = NULL;
char *host = NULL;
char **parts = NULL;
if (is_valid_arguments(server, username, password) != 0) {
ERROR("Invalid arguments");
return -1;
}
parts = util_string_split(server, '/');
if (parts == NULL || util_array_len((const char **)parts) == 0) {
ret = -1;
goto out;
}
host = parts[0];
options.skip_tls_verify = conf_get_skip_insecure_verify_flag();
insecure_registries = conf_get_insecure_registry_list();
for (registry = insecure_registries; (registry != NULL) && (*registry != NULL); registry++) {
if (!strcmp(*registry, host)) {
options.skip_tls_verify = true;
}
}
options.host = host;
options.auth.username = (char *) username;
options.auth.password = (char *) password;
ret = registry_login(&options);
if (ret != 0) {
ERROR("registry login failed");
goto out;
}
out:
util_free_array(parts);
parts = NULL;
util_free_array(insecure_registries);
insecure_registries = NULL;
return ret;
}
/******************************************************************************
* Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved.
* iSulad licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: liuhao
* Create: 2019-07-15
* Description: isula logint operator implement
*******************************************************************************/
#ifndef __IMAGE_OCI_LOGIN_H
#define __IMAGE_OCI_LOGIN_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int oci_do_login(const char *server, const char *username, const char *password);
#ifdef __cplusplus
}
#endif
#endif
/******************************************************************************
* Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved.
* iSulad licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: wangfengtu
* Create: 2020-05-07
* Description: isula logout operator implement
*******************************************************************************/
#include "oci_logout.h"
#include "utils.h"
#include "libisulad.h"
#include "log.h"
#include "registry.h"
static inline int is_valid_arguments(const char *server)
{
if (server == NULL) {
isulad_set_error_message("Logout requires server address");
return -1;
}
return 0;
}
int oci_do_logout(const char *server)
{
int ret = -1;
if (is_valid_arguments(server) != 0) {
ERROR("Invlaid arguments");
return -1;
}
ret = registry_logout((char *)server);
if (ret != 0) {
ERROR("registry logout failed");
goto out;
}
out:
return ret;
}
/******************************************************************************
* Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved.
* iSulad licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: liuhao
* Create: 2019-07-15
* Description: isula logout operator implement
*******************************************************************************/
#ifndef __IMAGE_OCI_LOGOUT_H
#define __IMAGE_OCI_LOGOUT_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int oci_do_logout(const char *server);
#ifdef __cplusplus
}
#endif
#endif
/******************************************************************************
* Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved.
* iSulad licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: wangfengtu
* Create: 2020-05-07
* Description: isula image pull operator implement
*******************************************************************************/
#include "oci_pull.h"
#include "log.h"
#include "utils.h"
#include "oci_common_operators.h"
#include "registry.h"
#include "isulad_config.h"
static int decode_auth(char *auth, char **username, char **password)
{
int nret = 0;
int ret = 0;
unsigned char *decoded = NULL;
size_t decoded_len = 0;
char **auth_parts = NULL;
if (auth == NULL || username == NULL || password == NULL) {
ERROR("invalid NULL pointer");
return -1;
}
decoded_len = util_base64_decode_len(auth, strlen(auth));
if (decoded_len < 0) {
return -1;
}
decoded = util_common_calloc_s(decoded_len + 1);
if (decoded == NULL) {
ERROR("out of memory");
return -1;
}
nret = util_base64_decode(auth, strlen(auth), decoded, decoded_len);
if (nret < 0) {
ERROR("decode auth from base64 failed");
ret = -1;
goto out;
}
auth_parts = util_string_split((char *)decoded, ':');
if (auth_parts == NULL || util_array_len((const char **)auth_parts) != 2) {
ERROR("Invalid auth format");
ret = -1;
goto out;
}
*username = util_strdup_s(auth_parts[0]);
*password = util_strdup_s(auth_parts[1]);
(void)memset(auth_parts[0], 0, strlen(auth_parts[0]));
(void)memset(auth_parts[1], 0, strlen(auth_parts[1]));
out:
free_sensitive_string((char *)decoded);
decoded = NULL;
util_free_array(auth_parts);
auth_parts = NULL;
return ret;
}
static void update_option_skip_tls_verify(registry_pull_options *options, char **insecure_registries, char *host)
{
char **registry = NULL;
if (insecure_registries == NULL || options == NULL || host == NULL) {
return;
}
for (registry = insecure_registries; (registry != NULL) && (*registry != NULL); registry++) {
if (!strcmp(*registry, host)) {
options->skip_tls_verify = true;
}
}
}
static int pull_image(const im_pull_request *request)
{
int ret = -1;
registry_pull_options *options = NULL;
char **insecure_registries = NULL;
char **registry_mirrors = NULL;
char **mirror = NULL;
char *host = NULL;
char *dest_image_name = NULL;
options = (registry_pull_options *)util_common_calloc_s(sizeof(registry_pull_options));
if (options == NULL) {
ERROR("Out of memory");
goto out;
}
options->auth.username = request->username;
options->auth.password = request->password;
if (request->auth != NULL) {
ret = decode_auth(request->auth, &options->auth.username, &options->auth.password);
if (ret != 0) {
ERROR("Decode auth failed");
goto out;
}
}
options->skip_tls_verify = conf_get_skip_insecure_verify_flag();
insecure_registries = conf_get_insecure_registry_list();
host = oci_get_host(request->image);
if (host != NULL) {
options->image_name = oci_default_tag(request->image);
options->dest_image_name = util_strdup_s(options->image_name);
update_option_skip_tls_verify(options, insecure_registries, host);
ret = registry_pull(options);
if (ret != 0) {
ERROR("pull image failed");
goto out;
}
} else {
registry_mirrors = conf_get_registry_list();
for (mirror = registry_mirrors; (mirror != NULL) && (*mirror != NULL); mirror++) {
if (util_has_prefix(*mirror, HTTPS_PREFIX)) {
options->skip_tls_verify = true;
}
host = oci_host_from_mirror(*mirror);
update_option_skip_tls_verify(options, insecure_registries, host);
dest_image_name = oci_default_tag(request->image);
options->image_name = oci_add_host(host, dest_image_name);
free(host);
host = NULL;
options->dest_image_name = dest_image_name;
ret = registry_pull(options);
if (ret != 0) {
continue;
}
break;
}
}
out:
free(host);
host = NULL;
util_free_array(registry_mirrors);
registry_mirrors = NULL;
util_free_array(insecure_registries);
insecure_registries = NULL;
free_registry_pull_options(options);
options = NULL;
return ret;
}
int oci_do_pull_image(const im_pull_request *request, im_pull_response **response)
{
int ret = 0;
imagetool_image *image = NULL;
if (request == NULL || request->image == NULL || response == NULL) {
ERROR("Invalid NULL param");
return -1;
}
ret = pull_image(request);
if (ret != 0) {
ERROR("pull image %s failed", request->image);
goto err_out;
}
image = storage_img_get(request->image);
if (image == NULL) {
ERROR("get image %s failed after pulling", request->image);
goto err_out;
}
*response = (im_pull_response *)util_common_calloc_s(sizeof(im_pull_response));
if (*response == NULL) {
ERROR("Out of memory");
goto err_out;
}
(*response)->image_ref = util_strdup_s(image->id);
goto out;
err_out:
free_im_pull_response(*response);
*response = NULL;
ret = -1;
out:
free_imagetool_image(image);
image = NULL;
return ret;
}
......@@ -12,8 +12,8 @@
* Create: 2019-07-15
* Description: isula image pull operator implement
*******************************************************************************/
#ifndef __IMAGE_ISULA_PULL_H
#define __IMAGE_ISULA_PULL_H
#ifndef __IMAGE_OCI_PULL_H
#define __IMAGE_OCI_PULL_H
#include "image.h"
......@@ -21,7 +21,7 @@
extern "C" {
#endif
int isula_pull_image(const im_pull_request *request, im_pull_response **response);
int oci_do_pull_image(const im_pull_request *request, im_pull_response **response);
#ifdef __cplusplus
}
......
......@@ -594,7 +594,7 @@ static char *build_get_token_url(challenge *c, char *username, char *scope)
url_len += strlen("service=") + strlen(c->service) + strlen("&");
}
if (scope != NULL) {
url_len += strlen("scope") + strlen(scope) + strlen("&");
url_len += strlen("scope=") + strlen(scope) + strlen("&");
}
url = util_common_calloc_s(url_len);
......
......@@ -37,6 +37,7 @@
#include "map.h"
#include "linked_list.h"
#include "pthread.h"
#include "isulad_config.h"
#define MAX_LAYER_NUM 125
#define MANIFEST_BIG_DATA_KEY "manifest"
......@@ -60,12 +61,8 @@ typedef struct {
size_t file_list_len;
} cached_layer;
// Share infomation of downloading layers to avoid downloading the same layer.
typedef struct {
// options
bool use_decrypted_key;
bool skip_tls_verify;
// Share infomation of downloading layers to avoid downloading the same layer.
pthread_mutex_t mutex;
bool mutex_inited;
pthread_cond_t cond;
......@@ -1465,6 +1462,7 @@ out:
static int registry_fetch(pull_descriptor *desc)
{
int ret = 0;
imagetool_image *image = NULL;
if (desc == NULL) {
ERROR("Invalid NULL param");
......@@ -1477,6 +1475,13 @@ static int registry_fetch(pull_descriptor *desc)
goto out;
}
// If the image already exist, do not pull it again.
image = storage_img_get(desc->dest_image_name);
if (image != NULL && desc->config.digest != NULL && !strcmp(image->id, desc->config.digest)) {
DEBUG("image %s with id %s already exist, ignore pulling", desc->dest_image_name, image->id);
goto out;
}
// manifest schema1 cann't pull config, the config is composited by
// the history[0].v1Compatibility in manifest and rootfs's diffID
if (!is_manifest_schemav1(desc->manifest.media_type)) {
......@@ -1505,6 +1510,8 @@ static int registry_fetch(pull_descriptor *desc)
}
out:
free_imagetool_image(image);
image = NULL;
return ret;
}
......@@ -1563,8 +1570,8 @@ static int prepare_pull_desc(pull_descriptor *desc, registry_pull_options *optio
desc->dest_image_name = util_strdup_s(options->dest_image_name);
desc->scope = util_strdup_s(scope);
desc->blobpath = util_strdup_s(blobpath);
desc->use_decrypted_key = g_shared->use_decrypted_key;
desc->skip_tls_verify = g_shared->skip_tls_verify;
desc->use_decrypted_key = conf_get_use_decrypted_key_flag();
desc->skip_tls_verify = options->skip_tls_verify;
if (options->auth.username != NULL && options->auth.password != NULL) {
desc->username = util_strdup_s(options->auth.username);
......@@ -1599,8 +1606,7 @@ int registry_pull(registry_pull_options *options)
desc = util_common_calloc_s(sizeof(pull_descriptor));
if (desc == NULL) {
ERROR("Out of memory");
ret = -1;
goto out;
return -1;
}
ret = prepare_pull_desc(desc, options);
......@@ -1632,7 +1638,6 @@ out:
WARN("failed to remove directory %s", desc->blobpath);
}
}
free_pull_desc(desc);
return ret;
......@@ -1662,15 +1667,10 @@ static void cached_layers_kvfree(void *key, void *value)
return;
}
int registry_init(registry_init_options *options)
int registry_init()
{
int ret = 0;
if (options == NULL) {
ERROR("Invalid NULL param when init registry module");
return -1;
}
g_shared = util_common_calloc_s(sizeof(registry_global));
if (g_shared == NULL) {
ERROR("out of memory");
......@@ -1691,9 +1691,6 @@ int registry_init(registry_init_options *options)
}
g_shared->cond_inited = true;
g_shared->use_decrypted_key = options->use_decrypted_key;
g_shared->skip_tls_verify = options->skip_tls_verify;
g_shared->cached_layers = map_new(MAP_STR_PTR, MAP_DEFAULT_CMP_FUNC, cached_layers_kvfree);
if (g_shared->cached_layers == NULL) {
ERROR("out of memory");
......@@ -1712,6 +1709,8 @@ out:
}
map_free(g_shared->cached_layers);
g_shared->cached_layers = NULL;
free(g_shared);
g_shared = NULL;
}
return ret;
......@@ -1737,8 +1736,8 @@ int registry_login(registry_login_options *options)
}
desc->host = util_strdup_s(options->host);
desc->use_decrypted_key = g_shared->use_decrypted_key;
desc->skip_tls_verify = g_shared->skip_tls_verify;
desc->use_decrypted_key = conf_get_use_decrypted_key_flag();
desc->skip_tls_verify = options->skip_tls_verify;
desc->username = util_strdup_s(options->auth.username);
desc->password = util_strdup_s(options->auth.password);
......@@ -1775,6 +1774,9 @@ static void free_registry_auth(registry_auth *auth)
void free_registry_pull_options(registry_pull_options *options)
{
if (options == NULL) {
return;
}
free_registry_auth(&options->auth);
free(options->image_name);
options->image_name = NULL;
......@@ -1784,6 +1786,9 @@ void free_registry_pull_options(registry_pull_options *options)
void free_registry_login_options(registry_login_options *options)
{
if (options == NULL) {
return;
}
free_registry_auth(&options->auth);
free(options->host);
options->host = NULL;
......
......@@ -19,28 +19,25 @@
extern "C" {
#endif
typedef struct {
bool use_decrypted_key;
bool skip_tls_verify;
} registry_init_options;
typedef struct {
char *username;
char *password;
} registry_auth;
typedef struct {
registry_auth auth;
char *image_name;
char *dest_image_name;
registry_auth auth;
bool skip_tls_verify;
} registry_pull_options;
typedef struct {
registry_auth auth;
char *host;
registry_auth auth;
bool skip_tls_verify;
} registry_login_options;
int registry_init(registry_init_options *options);
int registry_init();
int registry_pull(registry_pull_options *options);
int registry_login(registry_login_options *options);
int registry_logout(char *host);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册