diff --git a/src/cli/cli.c b/src/cli/cli.c index a1d3779..fee509e 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -1,5 +1,5 @@ #include "../gimi_constants.h" -#include "command/ci.h" +#include "command/ci/ci.h" #include "command/config.h" #include "command/init.h" #include "command/provider.h" diff --git a/src/cli/command/ci.c b/src/cli/command/ci.c deleted file mode 100644 index 3448d43..0000000 --- a/src/cli/command/ci.c +++ /dev/null @@ -1,185 +0,0 @@ -#include "../../config.h" -#include -#include -#include -#include -#include -#include - -#define SOURCEHUT \ - "image: alpine/latest\n" \ - "packages:\n" \ - " - git\n" \ - "sources:\n" \ - " - \"https://%s/%s/%s\"\n" \ - "secrets:\n" \ - " - \n" \ - "tasks:\n" \ - " - sync: |\n" \ - " cd %s\n" \ - " set +x" \ - " %s\n\n" \ - " set -x\n" - -struct repository { - char *domain; - char *owner; - char *repo; -}; - -struct repository *translate_ssh_to_repository(char *ssh) { - const char *at = strchr(ssh, '@'); - const char *colon = strchr(ssh, ':'); - const char *slash = strchr(colon, '/'); - - if (!at || !colon || at > colon) { - return NULL; - } - - struct repository *repo = - (struct repository *)malloc(sizeof(struct repository)); - if (!repo) { - return NULL; - } - - int domain_len = colon - at - 1; - repo->domain = (char *)malloc(domain_len + 1); - snprintf(repo->domain, domain_len + 1, "%.*s", domain_len, at + 1); - - int repo_len = slash - colon - 1; - repo->owner = (char *)malloc(repo_len + 1); - snprintf(repo->owner, repo_len + 1, "%.*s", repo_len, colon + 1); - - int slash_len = strlen(slash) - 1; - repo->repo = (char *)malloc(slash_len + 1); - snprintf(repo->repo, slash_len + 1, "%s", slash + 1); - - return repo; -} - -void repository_free(struct repository *repo) { - free(repo->owner); - free(repo->repo); - free(repo); -} - -char *generate_sourcehut(struct gimi_config *cfg, - struct gimi_config_provider *main_provider) { - char remotes[1024] = ""; - - for (int i = 0; i < cfg->providers_size; i++) { - struct gimi_config_provider *provider = cfg->providers[i]; - if (strcmp(provider->name, main_provider->name) == 0) - continue; - - struct repository *repo = translate_ssh_to_repository(provider->ssh); - - char buf[1024]; - snprintf(buf, sizeof(buf), - "\n" - "\n git remote add gimi-%s %s " - "\n ssh-keyscan %s >> ~/.ssh/known_hosts" - "\n echo -e \"Host %s\\nUser git\\nIdentityFile " - "${HOME}/.ssh/\" >> ~/.ssh/config" - "\n git push -f --all gimi-%s" - "\n git push -f --tags gimi-%s", - provider->name, provider->ssh, repo->domain, repo->domain, - provider->name, provider->name); - - strcat(remotes, buf); - - repository_free(repo); - } - - char *buf = (char *)malloc(2048); - struct repository *repo = translate_ssh_to_repository(main_provider->ssh); - - snprintf(buf, 2048, SOURCEHUT, repo->domain, repo->owner, repo->repo, - repo->repo, remotes); - - repository_free(repo); - - return buf; -} - -int generate(int argc, char **argv) { - struct gimi_config *cfg = config_read(); - ASSERT_CONFIG_EXIST(cfg); - - struct gimi_config_provider *provider; - - if (argc == 2) { - provider = config_find_provider(cfg, argv[1]); - - if (provider == NULL) { - printf("error: no such provider '%s'\n", argv[1]); - return 1; - } - } else { - for (int i = 0; i < cfg->providers_size; i++) { - if (cfg->providers[i]->primary) { - provider = cfg->providers[i]; - break; - } - } - - if (provider == NULL) { - printf("error: can't find primary provider\n"); - return 1; - } - }; - - char *template; - if (strcmp(provider->name, "sourcehut") == 0) { - template = generate_sourcehut(cfg, provider); - - int ret = mkdir(".builds", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); - if (ret != 0 && errno != EEXIST) { - printf("error: failed to create directory .builds with errno %d\n", - errno); - return ret; - } - - FILE *file_ptr; - file_ptr = fopen(".builds/gimi.yml", "w"); - if (file_ptr == NULL) { - printf("error: failed to open file .builds/gimi.yml\n"); - return 1; - } - - fprintf(file_ptr, "%s", template); - - fclose(file_ptr); - } else { - printf("error: provider '%s' is not supported, create the ci yourself.\n", - provider->name); - - config_free(cfg); - free(provider); - return 1; - } - - config_free(cfg); - free(provider); - - return 0; -} - -int cli_command_ci(int argc, char **argv) { - if (argc == 1) { - printf("usage: gimi ci generate [provider]\n"); - return 1; - } - - // remove "ci" from args - argc -= 1; - argv += 1; - - char *subcommand = argv[0]; - - if (strcmp(subcommand, "generate") == 0) { - return generate(argc, argv); - } - - return 0; -} diff --git a/src/cli/command/ci/ci.c b/src/cli/command/ci/ci.c new file mode 100644 index 0000000..421e254 --- /dev/null +++ b/src/cli/command/ci/ci.c @@ -0,0 +1,200 @@ +#include "../../../config.h" +#include "github.h" +#include "sourcehut.h" +#include +#include +#include +#include +#include +#include + +#define CREATE_DIR(path) \ + int ret = mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); \ + if (ret != 0 && errno != EEXIST) { \ + printf("error: failed to create directory %s with errno %d\n", path, \ + errno); \ + return ret; \ + } + +#define CREATE_DIRS(...) \ + const char *dirs[] = {__VA_ARGS__}; \ + for (size_t i = 0; i < sizeof(dirs) / sizeof(dirs[0]); ++i) { \ + CREATE_DIR(dirs[i]); \ + } + +#define WRITE_TEMPLATE_TO_FILE(path, template) \ + FILE *file_ptr = fopen(path, "w"); \ + if (file_ptr == NULL) { \ + printf("error: failed to open file %s\n", path); \ + return 1; \ + } \ + fprintf(file_ptr, "%s", template); \ + fclose(file_ptr); + +#define HANDLE_PROVIDER(provider_name, file_path, remote_func, gen_func, ...) \ + if (strcmp(provider->name, provider_name) == 0) { \ + char *template = \ + generate_file_content(cfg, provider, remote_func, gen_func); \ + CREATE_DIRS(__VA_ARGS__); \ + WRITE_TEMPLATE_TO_FILE(file_path, template); \ + } + +struct repository { + char *domain; + char *owner; + char *repo; +}; + +struct repository *translate_ssh_to_repository(char *ssh) { + const char *at = strchr(ssh, '@'); + const char *colon = strchr(ssh, ':'); + const char *slash = strchr(colon, '/'); + + if (!at || !colon || at > colon) { + return NULL; + } + + struct repository *repo = + (struct repository *)malloc(sizeof(struct repository)); + if (!repo) { + return NULL; + } + + int domain_len = colon - at - 1; + repo->domain = (char *)malloc(domain_len + 1); + snprintf(repo->domain, domain_len + 1, "%.*s", domain_len, at + 1); + + int repo_len = slash - colon - 1; + repo->owner = (char *)malloc(repo_len + 1); + snprintf(repo->owner, repo_len + 1, "%.*s", repo_len, colon + 1); + + int slash_len = strlen(slash) - 1; + repo->repo = (char *)malloc(slash_len + 1); + snprintf(repo->repo, slash_len + 1, "%s", slash + 1); + + return repo; +} + +void repository_free(struct repository *repo) { + free(repo->owner); + free(repo->repo); + free(repo); +} + +char *generate_file_content( + struct gimi_config *cfg, struct gimi_config_provider *main_provider, + void (*generate_remote)(char *, size_t, const char *, const char *, + const char *), + void (*generate_base)(char *, size_t, const char *, const char *, + const char *, const char *)) { + char remotes[1024] = ""; + + for (int i = 0; i < cfg->providers_size; i++) { + struct gimi_config_provider *provider = cfg->providers[i]; + if (strcmp(provider->name, main_provider->name) == 0) + continue; + + struct repository *repo = translate_ssh_to_repository(provider->ssh); + + char buf[1024]; + + generate_remote(buf, sizeof(buf), provider->name, provider->ssh, + repo->domain); + + strcat(remotes, buf); + + repository_free(repo); + } + + char *buf = (char *)malloc(2048); + struct repository *repo = translate_ssh_to_repository(main_provider->ssh); + + generate_base(buf, 2048, repo->domain, repo->owner, repo->repo, remotes); + + repository_free(repo); + + return buf; +} + +int create_directory(const char *path) { + int ret = mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret != 0 && errno != EEXIST) { + printf("error: failed to create directory %s with errno %d\n", path, errno); + return ret; + } + return 0; +} + +int write_template_to_file(const char *path, const char *template) { + FILE *file_ptr = fopen(path, "w"); + if (file_ptr == NULL) { + printf("error: failed to open file %s\n", path); + return 1; + } + fprintf(file_ptr, "%s", template); + fclose(file_ptr); + return 0; +} + +int generate(int argc, char **argv) { + struct gimi_config *cfg = config_read(); + ASSERT_CONFIG_EXIST(cfg); + + struct gimi_config_provider *provider = NULL; + + if (argc == 2) { + provider = config_find_provider(cfg, argv[1]); + if (provider == NULL) { + printf("error: no such provider '%s'\n", argv[1]); + return 1; + } + } else { + for (int i = 0; i < cfg->providers_size; i++) { + if (cfg->providers[i]->primary) { + provider = cfg->providers[i]; + break; + } + } + if (provider == NULL) { + printf("error: can't find primary provider\n"); + return 1; + } + } + + HANDLE_PROVIDER("sourcehut", ".builds/gimi.yml", generate_sourcehut_remote, + generate_sourcehut, ".builds"); + HANDLE_PROVIDER("github", ".github/workflows/gimi.yml", + generate_github_remote, generate_github, ".github", + ".github/workflows"); + + if (provider == NULL) { + printf("error: provider '%s' is not supported, create the ci yourself.\n", + provider->name); + config_free(cfg); + free(provider); + return 1; + } + + config_free(cfg); + free(provider); + return 0; +} + +int cli_command_ci(int argc, char **argv) { + if (argc == 1) { + printf("usage: gimi ci generate [provider]\n"); + return 1; + } + + // remove "ci" from args + argc -= 1; + argv += 1; + + char *subcommand = argv[0]; + + if (strcmp(subcommand, "generate") == 0) { + return generate(argc, argv); + } + + return 0; +} diff --git a/src/cli/command/ci.h b/src/cli/command/ci/ci.h similarity index 100% rename from src/cli/command/ci.h rename to src/cli/command/ci/ci.h diff --git a/src/cli/command/ci/github.h b/src/cli/command/ci/github.h new file mode 100644 index 0000000..4bccee9 --- /dev/null +++ b/src/cli/command/ci/github.h @@ -0,0 +1,43 @@ +#include +#include +#include + +void generate_github_remote(char *buffer, size_t size, + const char *provider_name, const char *provider_ssh, + const char *repo_domain) { + snprintf(buffer, size, + "\n" + "\n git remote add gimi-%s %s" + "\n ssh-keyscan %s >> ~/.ssh/known_hosts" + "\n echo -e \"$SECRET\" >> ~/.ssh/gimi-%s" + "\n chmod 600 ~/.ssh/gimi-%s" + "\n echo -e \"Host %s\\nUser git\\nIdentityFile " + "${HOME}/.ssh/gimi-%s\" >> ~/.ssh/config" + "\n git push -f --all gimi-%s" + "\n git push -f --tags gimi-%s", + provider_name, provider_ssh, repo_domain, provider_name, + provider_name, repo_domain, provider_name, provider_name, + provider_name); +} + +void generate_github(char *buffer, size_t size, const char *repo_domain, + const char *repo_owner, const char *repo_name, + const char *remotes) { + snprintf(buffer, size, + "name: gimi\n" + "on: [push, delete]\n\n" + "jobs:\n" + " sync:\n" + " runs-on: ubuntu-24.04\n" + " steps:\n" + " - uses: actions/checkout@v4\n" + " with:\n" + " fetch-depth: 0\n" + " - run: |\n" + " set +x" + " %s\n\n" + " set -x\n" + " env:\n" + " secret: ${{ secrets.SECRET }}", + remotes); +} diff --git a/src/cli/command/ci/sourcehut.h b/src/cli/command/ci/sourcehut.h new file mode 100644 index 0000000..410c715 --- /dev/null +++ b/src/cli/command/ci/sourcehut.h @@ -0,0 +1,39 @@ +#include +#include +#include + +void generate_sourcehut_remote(char *buffer, size_t size, + const char *provider_name, + const char *provider_ssh, + const char *repo_domain) { + snprintf(buffer, size, + "\n" + "\n git remote add gimi-%s %s" + "\n ssh-keyscan %s >> ~/.ssh/known_hosts" + "\n echo -e \"Host %s\\nUser git\\nIdentityFile " + "${HOME}/.ssh/\" >> ~/.ssh/config" + "\n git push -f --all gimi-%s" + "\n git push -f --tags gimi-%s", + provider_name, provider_ssh, repo_domain, repo_domain, provider_name, + provider_name); +} + +void generate_sourcehut(char *buffer, size_t size, const char *repo_domain, + const char *repo_owner, const char *repo_name, + const char *remotes) { + snprintf(buffer, size, + "image: alpine/latest\n" + "packages:\n" + " - git\n" + "sources:\n" + " - \"https://%s/%s/%s\"\n" + "secrets:\n" + " - \n" + "tasks:\n" + " - sync: |\n" + " cd %s\n" + " set +x" + " %s\n\n" + " set -x\n", + repo_domain, repo_owner, repo_name, repo_name, remotes); +}