├── examples ├── php-shared-fpm │ ├── home │ │ └── .gitignore │ ├── apps │ │ ├── app1 │ │ │ └── public │ │ │ │ └── index.php │ │ └── app2 │ │ │ └── public │ │ │ └── index.php │ ├── infra │ │ ├── proxy │ │ │ └── docker-compose.yml │ │ └── fpm │ │ │ ├── nginx │ │ │ ├── app1.conf.template │ │ │ └── app2.conf.template │ │ │ ├── php │ │ │ └── Dockerfile │ │ │ └── docker-compose.yml │ └── workspace.yaml └── php-separated-fpms │ ├── home │ └── .gitignore │ ├── apps │ ├── app1 │ │ └── public │ │ │ └── index.php │ └── app2 │ │ └── public │ │ └── index.php │ ├── infra │ └── proxy │ │ └── docker-compose.yml │ ├── templates │ └── fpm-8.1 │ │ ├── nginx │ │ └── default.conf.template │ │ ├── php │ │ └── Dockerfile │ │ └── docker-compose.yml │ └── workspace.yaml ├── .gitignore ├── gen.sh ├── main.go ├── version.sh ├── go.mod ├── Makefile ├── .github └── workflows │ └── test.yml ├── actions ├── general_actions.go ├── workspace_actions_test.go ├── workspace_actions.go ├── component_actions.go └── component_actions_test.go ├── core ├── context.go ├── bootstrap.go ├── core.go ├── workspace_config.go ├── component_config.go ├── git.go ├── pc.go ├── home-config.go ├── workspace.go ├── mock_pc.go └── component.go ├── LICENSE.md ├── get.sh ├── README.md ├── doc └── commands.md └── cmd └── elc.go /examples/php-shared-fpm/home/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /examples/php-separated-fpms/home/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /examples/php-shared-fpm/apps/app1/public/index.php: -------------------------------------------------------------------------------- 1 | -1 { 25 | return append(ctx[:index], ctx[index+1:]...) 26 | } 27 | 28 | return ctx 29 | } 30 | 31 | func (ctx *Context) add(name string, value string) Context { 32 | tmp := ctx.remove(name) 33 | return append(tmp, []string{name, value}) 34 | } 35 | 36 | func (ctx *Context) RenderString(str string) (string, error) { 37 | return substVars(str, ctx) 38 | } 39 | 40 | func (ctx *Context) renderMapToEnv() []string { 41 | var result []string 42 | for _, pair := range *ctx { 43 | result = append(result, fmt.Sprintf("%s=%s", pair[0], pair[1])) 44 | } 45 | 46 | return result 47 | } 48 | -------------------------------------------------------------------------------- /examples/php-separated-fpms/templates/fpm-8.1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: $WORKSPACE_NAME/$APP_IMAGE 4 | build: 5 | context: $TPL_PATH/php 6 | args: 7 | - BASE_IMAGE=$BASE_IMAGE 8 | - GROUP_ID=$GROUP_ID 9 | - USER_ID=$USER_ID 10 | hostname: "$APP_NAME.$BASE_DOMAIN" 11 | extra_hosts: 12 | - "host.docker.internal:host-gateway" 13 | environment: 14 | VIRTUAL_HOST: "$APP_NAME.$BASE_DOMAIN" 15 | VIRTUAL_PORT: "80" 16 | HOME: /tmp/home 17 | COMPOSER_HOME: /tmp/home/composer 18 | COMPOSER_CACHE_DIR: /tmp/home/composer_cache 19 | working_dir: /var/www 20 | volumes: 21 | - "$SVC_PATH:/var/www" 22 | - "$HOME_PATH:/tmp/home" 23 | networks: 24 | - dev 25 | nginx: 26 | image: $NGINX_IMAGE 27 | volumes: 28 | - "$SVC_PATH:/var/www" 29 | - "$TPL_PATH/nginx/default.conf.template:/etc/nginx/templates/default.conf" 30 | network_mode: "service:app" 31 | depends_on: 32 | - app 33 | 34 | networks: 35 | dev: 36 | external: true 37 | name: $NETWORK -------------------------------------------------------------------------------- /core/bootstrap.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "path" 5 | ) 6 | 7 | func CheckAndLoadHC() (*HomeConfig, error) { 8 | homeDir, err := Pc.HomeDir() 9 | if err != nil { 10 | return nil, err 11 | } 12 | homeConfigPath := path.Join(homeDir, ".elc.yaml") 13 | err = CheckHomeConfigIsEmpty(homeConfigPath) 14 | if err != nil { 15 | return nil, err 16 | } 17 | hc, err := LoadHomeConfig(homeConfigPath) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return hc, nil 23 | } 24 | 25 | func GetWorkspaceConfig(wsName string) (*Workspace, error) { 26 | hc, err := CheckAndLoadHC() 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | wsPath, err := hc.GetCurrentWsPath(wsName) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | cwd, err := Pc.Getwd() 37 | if err != nil { 38 | return nil, err 39 | } 40 | ws := NewWorkspace(wsPath, cwd) 41 | 42 | err = ws.LoadConfig() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | err = ws.checkVersion() 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | err = ws.init() 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return ws, nil 58 | } 59 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/php-shared-fpm/workspace.yaml: -------------------------------------------------------------------------------- 1 | name: elc-example-2 2 | variables: 3 | DEFAULT_APPS_ROOT: ${WORKSPACE_PATH}/apps 4 | APPS_ROOT: ${APPS_ROOT:-$DEFAULT_APPS_ROOT} 5 | NETWORK: ${NETWORK:-example} 6 | BASE_DOMAIN: ${BASE_DOMAIN:-example.127.0.0.1.nip.io} 7 | GROUP_ID: ${GROUP_ID:-1000} 8 | USER_ID: ${USER_ID:-1000} 9 | HOME_PATH: ${WORKSPACE_PATH}/home 10 | 11 | APP1_HOST: app1.${BASE_DOMAIN} 12 | APP1_PATH: ${APPS_ROOT}/app1 13 | APP1_MOUNT_PATH: /var/www/app1 14 | 15 | APP2_HOST: app2.${BASE_DOMAIN} 16 | APP2_PATH: ${APPS_ROOT}/app2 17 | APP2_MOUNT_PATH: /var/www/app2 18 | 19 | 20 | services: 21 | proxy: 22 | path: ${WORKSPACE_PATH}/infra/proxy 23 | variables: 24 | APP_IMAGE: jwilder/nginx-proxy:latest 25 | fpm: 26 | path: ${WORKSPACE_PATH}/infra/fpm 27 | variables: 28 | APP_IMAGE: fpm-8.1:latest 29 | BASE_IMAGE: php:8.1-fpm-alpine 30 | NGINX_IMAGE: nginx:1.19-alpine 31 | dependencies: 32 | proxy: [default] 33 | 34 | modules: 35 | app1: 36 | path: ${APP1_PATH} 37 | hosted_in: fpm 38 | exec_path: ${APP1_MOUNT_PATH} 39 | repository: git@gitlab.com:user/project.git 40 | app2: 41 | path: ${APP2_PATH} 42 | hosted_in: fpm 43 | exec_path: ${APP2_MOUNT_PATH} -------------------------------------------------------------------------------- /examples/php-shared-fpm/infra/fpm/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: $WORKSPACE_NAME/$APP_IMAGE 4 | build: 5 | context: $SVC_PATH/php 6 | args: 7 | - BASE_IMAGE=$BASE_IMAGE 8 | - GROUP_ID=$GROUP_ID 9 | - USER_ID=$USER_ID 10 | hostname: "$APP_NAME.$BASE_DOMAIN" 11 | networks: 12 | dev: 13 | aliases: 14 | - "${APP1_HOST}" 15 | - "${APP2_HOST}" 16 | extra_hosts: 17 | - "host.docker.internal:host-gateway" 18 | - "${APP1_HOST}:127.0.0.1" 19 | - "${APP2_HOST}:127.0.0.1" 20 | environment: 21 | VIRTUAL_HOST: "${APP1_HOST},${APP2_HOST}" 22 | VIRTUAL_PORT: "80" 23 | HOME: /tmp/home 24 | COMPOSER_HOME: /tmp/home/composer 25 | COMPOSER_CACHE_DIR: /tmp/home/composer_cache 26 | working_dir: /var/www 27 | volumes: 28 | - "$HOME_PATH:/tmp/home" 29 | - "${APP1_PATH}:${APP1_MOUNT_PATH}" 30 | - "${APP2_PATH}:${APP2_MOUNT_PATH}" 31 | nginx: 32 | image: $NGINX_IMAGE 33 | environment: 34 | APP1_HOST: ${APP1_HOST} 35 | APP1_MOUNT_PATH: ${APP1_MOUNT_PATH} 36 | APP2_HOST: ${APP2_HOST} 37 | APP2_MOUNT_PATH: ${APP2_MOUNT_PATH} 38 | volumes: 39 | - "$SVC_PATH/nginx:/etc/nginx/templates" 40 | - "${APP1_PATH}:${APP1_MOUNT_PATH}" 41 | - "${APP2_PATH}:${APP2_MOUNT_PATH}" 42 | network_mode: "service:app" 43 | depends_on: 44 | - app 45 | 46 | networks: 47 | dev: 48 | external: true 49 | name: $NETWORK -------------------------------------------------------------------------------- /actions/workspace_actions_test.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "github.com/ensi-platform/elc/core" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestWorkspaceShow(t *testing.T) { 10 | mockPc := setupMockPc(t) 11 | expectReadHomeConfig(mockPc) 12 | 13 | mockPc.EXPECT().Println("project1") 14 | 15 | _ = ShowCurrentWorkspaceAction(&core.GlobalOptions{}) 16 | } 17 | 18 | func TestWorkspaceList(t *testing.T) { 19 | mockPc := setupMockPc(t) 20 | expectReadHomeConfig(mockPc) 21 | 22 | mockPc.EXPECT().Printf("%-10s %s\n", "project1", "/tmp/workspaces/project1") 23 | mockPc.EXPECT().Printf("%-10s %s\n", "project2", "/tmp/workspaces/project2") 24 | 25 | _ = ListWorkspacesAction() 26 | } 27 | 28 | func TestWorkspaceAdd(t *testing.T) { 29 | mockPc := setupMockPc(t) 30 | expectReadHomeConfig(mockPc) 31 | 32 | const homeConfigForAdd = `current_workspace: project1 33 | update_command: update 34 | workspaces: 35 | - name: project1 36 | path: /tmp/workspaces/project1 37 | root_path: "" 38 | - name: project2 39 | path: /tmp/workspaces/project2 40 | root_path: "" 41 | - name: project3 42 | path: /tmp/workspaces/project3 43 | root_path: "" 44 | ` 45 | 46 | mockPc.EXPECT().WriteFile(fakeHomeConfigPath, []byte(homeConfigForAdd), os.FileMode(0644)) 47 | mockPc.EXPECT().Printf("workspace '%s' is added\n", "project3") 48 | 49 | _ = AddWorkspaceAction("project3", "/tmp/workspaces/project3") 50 | } 51 | 52 | func TestWorkspaceSelect(t *testing.T) { 53 | mockPc := setupMockPc(t) 54 | expectReadHomeConfig(mockPc) 55 | 56 | const homeConfigForSelect = `current_workspace: project2 57 | update_command: update 58 | workspaces: 59 | - name: project1 60 | path: /tmp/workspaces/project1 61 | root_path: "" 62 | - name: project2 63 | path: /tmp/workspaces/project2 64 | root_path: "" 65 | ` 66 | 67 | mockPc.EXPECT().WriteFile(fakeHomeConfigPath, []byte(homeConfigForSelect), os.FileMode(0644)) 68 | mockPc.EXPECT().Printf("active workspace changed to '%s'\n", "project2") 69 | 70 | _ = SelectWorkspaceAction("project2") 71 | } 72 | -------------------------------------------------------------------------------- /get.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROGRAM_NAME="elc" 4 | OWNER="ensi-platform" 5 | REPO="elc" 6 | BIN_LOCATION="/opt/elc" 7 | LINK_LOCATION="/usr/local/bin" 8 | 9 | if [ "$VERSION" == "" ]; then 10 | version=$(curl -sI https://github.com/$OWNER/$REPO/releases/latest | grep -i "location:" | awk -F"/" '{ printf "%s", $NF }' | tr -d '\r') 11 | else 12 | version="$VERSION" 13 | fi 14 | 15 | if [ ! $version ]; then 16 | echo "Failed while attempting to install $REPO. Please manually install:" 17 | echo "" 18 | echo "1. Open your web browser and go to https://github.com/$OWNER/$REPO/releases" 19 | echo "2. Download the latest release for your platform. Call it '$PROGRAM_NAME-'." 20 | echo "3. chmod +x ./$PROGRAM_NAME-" 21 | echo "4. mv ./$PROGRAM_NAME- $BIN_LOCATION" 22 | echo "5. ln -sf $BIN_LOCATION/$PROGRAM_NAME- $LINK_LOCATION/$PROGRAM_NAME" 23 | 24 | exit 1 25 | fi 26 | 27 | if [ -e "$BIN_LOCATION/$PROGRAM_NAME-$version" ]; then 28 | echo "Already downloaded" 29 | else 30 | targetFile="/tmp/elc_linux_amd64" 31 | if [ -e "$targetFile" ]; then 32 | rm "$targetFile" 33 | fi 34 | 35 | if ! [ -e "$BIN_LOCATION" ]; then 36 | mkdir -p "$BIN_LOCATION" 37 | fi 38 | 39 | echo "Downloading package $url as $targetFile" 40 | url="https://github.com/$OWNER/$REPO/releases/download/$version/elc_linux_amd64" 41 | curl -sSL $url --output "$targetFile" 42 | if [ "$?" = "0" ]; then 43 | echo "Download complete." 44 | chmod +x "$targetFile" 45 | $targetFile --version $> /dev/null 46 | if [ "$?" != "0" ]; then 47 | echo "ERROR: Downloaded file is not executable" 48 | exit 1 49 | fi 50 | mv "$targetFile" "$BIN_LOCATION/$PROGRAM_NAME-$version" 51 | if [ "$?" != "0" ]; then 52 | echo "ERROR: Unable to move $targetFile into $BIN_LOCATION/$PROGRAM_NAME-$version" 53 | exit 1 54 | fi 55 | else 56 | echo "ERROR: Unable to download $url into $targetFile" 57 | exit 1 58 | fi 59 | fi 60 | 61 | if [ -e "$BIN_LOCATION/$PROGRAM_NAME-$version" ]; then 62 | ln -s -f "$BIN_LOCATION/$PROGRAM_NAME-$version" "$LINK_LOCATION/$PROGRAM_NAME" 63 | fi 64 | 65 | echo "Successfully installed" -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | var Version string 11 | 12 | type GlobalOptions struct { 13 | WorkspaceName string 14 | ComponentName string 15 | Debug bool 16 | Cmd []string 17 | Force bool 18 | Mode string 19 | WorkingDir string 20 | UID int 21 | Tag string 22 | DryRun bool 23 | NoTty bool 24 | } 25 | 26 | func contains(list []string, item string) bool { 27 | for _, value := range list { 28 | if value == item { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | type reResult map[string]string 36 | 37 | func reFindMaps(pattern string, subject string) ([]reResult, error) { 38 | re, err := regexp.Compile(pattern) 39 | if err != nil { 40 | return nil, err 41 | } 42 | matches := re.FindAllStringSubmatch(subject, -1) 43 | names := re.SubexpNames() 44 | var result []reResult 45 | for _, match := range matches { 46 | foundFields := reResult{} 47 | for i, field := range match { 48 | if names[i] == "" { 49 | continue 50 | } 51 | foundFields[names[i]] = field 52 | } 53 | result = append(result, foundFields) 54 | } 55 | 56 | return result, nil 57 | } 58 | 59 | func substVars(expr string, ctx *Context) (string, error) { 60 | foundVars, err := reFindMaps(`\$\{(?P[^:}]+)(:-(?P[^}]+))?\}`, expr) 61 | if err != nil { 62 | return "", err 63 | } 64 | 65 | for _, foundVar := range foundVars { 66 | varName := foundVar["name"] 67 | value, found := ctx.find(varName) 68 | if !found { 69 | value, found = foundVar["value"] 70 | if !found { 71 | return "", errors.New(fmt.Sprintf("variable %s is not set", varName)) 72 | } 73 | 74 | if strings.HasPrefix(value, "$") { 75 | varRef := strings.TrimLeft(value, "$") 76 | value, found = ctx.find(varRef) 77 | if !found { 78 | return "", errors.New(fmt.Sprintf("variable %s is not set", varRef)) 79 | } 80 | } 81 | } 82 | re, err := regexp.Compile(fmt.Sprintf(`\$\{%s(?::-[^}]+)?\}`, varName)) 83 | if err != nil { 84 | return "", err 85 | } 86 | expr = re.ReplaceAllString(expr, value) 87 | } 88 | 89 | return expr, nil 90 | } 91 | -------------------------------------------------------------------------------- /core/workspace_config.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "gopkg.in/yaml.v2" 5 | ) 6 | 7 | type WorkspaceConfig struct { 8 | Name string `yaml:"name"` 9 | ElcMinVersion string `yaml:"elc_min_version"` 10 | Components map[string]ComponentConfig `yaml:"components"` 11 | Variables yaml.MapSlice `yaml:"variables"` 12 | 13 | // deprecated 14 | Aliases map[string]string `yaml:"aliases"` 15 | // deprecated 16 | Templates map[string]ComponentConfig `yaml:"templates"` 17 | // deprecated 18 | Services map[string]ComponentConfig `yaml:"services"` 19 | // deprecated 20 | Modules map[string]ComponentConfig `yaml:"modules"` 21 | } 22 | 23 | func NewWorkspaceConfig() *WorkspaceConfig { 24 | return &WorkspaceConfig{ 25 | Aliases: make(map[string]string, 0), 26 | Components: make(map[string]ComponentConfig, 0), 27 | Templates: make(map[string]ComponentConfig, 0), 28 | Services: make(map[string]ComponentConfig, 0), 29 | Modules: make(map[string]ComponentConfig, 0), 30 | } 31 | } 32 | 33 | func (wsc *WorkspaceConfig) normalize() { 34 | for k, v := range wsc.Templates { 35 | v.IsTemplate = true 36 | wsc.Components[k] = v 37 | } 38 | wsc.Templates = nil 39 | 40 | for k, v := range wsc.Services { 41 | wsc.Components[k] = v 42 | } 43 | wsc.Services = nil 44 | 45 | for k, v := range wsc.Modules { 46 | wsc.Components[k] = v 47 | } 48 | wsc.Modules = nil 49 | } 50 | 51 | func (wsc WorkspaceConfig) merge(wsc2 WorkspaceConfig) WorkspaceConfig { 52 | for name, cc := range wsc2.Components { 53 | if _, exists := wsc.Components[name]; !exists { 54 | wsc.Components[name] = cc 55 | } else { 56 | wsc.Components[name] = wsc.Components[name].merge(cc) 57 | } 58 | } 59 | 60 | for alias, ccName := range wsc2.Aliases { 61 | wsc.Aliases[alias] = ccName 62 | } 63 | 64 | for name, comp := range wsc.Components { 65 | if comp.Alias != "" { 66 | wsc.Aliases[comp.Alias] = name 67 | } 68 | } 69 | 70 | wsc.Variables = append(wsc2.Variables, wsc.Variables...) 71 | 72 | return wsc 73 | } 74 | 75 | func (wsc *WorkspaceConfig) loadFromFile(wscPath string) error { 76 | yamlFile, err := Pc.ReadFile(wscPath) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | err = yaml.Unmarshal(yamlFile, wsc) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | wsc.normalize() 87 | 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /core/component_config.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "gopkg.in/yaml.v2" 4 | 5 | type ModeList []string 6 | 7 | func (s ModeList) contains(v string) bool { 8 | for _, item := range s { 9 | if item == v { 10 | return true 11 | } 12 | } 13 | return false 14 | } 15 | 16 | type ComponentConfig struct { 17 | Alias string `yaml:"alias"` 18 | ComposeFile string `yaml:"compose_file"` 19 | Dependencies map[string]ModeList `yaml:"dependencies"` 20 | ExecPath string `yaml:"exec_path"` 21 | Extends string `yaml:"extends"` 22 | HostedIn string `yaml:"hosted_in"` 23 | Hostname string `yaml:"hostname"` 24 | IsTemplate bool `yaml:"is_template"` 25 | Path string `yaml:"path"` 26 | Replace bool `yaml:"replace"` 27 | Variables yaml.MapSlice `yaml:"variables"` 28 | Repository string `yaml:"repository"` 29 | Tags []string `yaml:"tags"` 30 | AfterCloneHook string `yaml:"after_clone_hook"` 31 | } 32 | 33 | func (cc ComponentConfig) merge(cc2 ComponentConfig) ComponentConfig { 34 | if cc2.Replace { 35 | return cc2 36 | } 37 | 38 | if cc2.Path != "" { 39 | cc.Path = cc2.Path 40 | } 41 | if cc2.ComposeFile != "" { 42 | cc.ComposeFile = cc2.ComposeFile 43 | } 44 | if cc2.Extends != "" { 45 | cc.Extends = cc2.Extends 46 | } 47 | if cc2.HostedIn != "" { 48 | cc.HostedIn = cc2.HostedIn 49 | } 50 | if cc2.ExecPath != "" { 51 | cc.ExecPath = cc2.ExecPath 52 | } 53 | if cc2.Alias != "" { 54 | cc.Alias = cc2.Alias 55 | } 56 | if cc2.Repository != "" { 57 | cc.Repository = cc2.Repository 58 | } 59 | if cc2.AfterCloneHook != "" { 60 | cc.AfterCloneHook = cc2.AfterCloneHook 61 | } 62 | 63 | cc.Variables = append(cc.Variables, cc2.Variables...) 64 | cc.Tags = append(cc.Tags, cc2.Tags...) 65 | 66 | for depSvc, modes := range cc2.Dependencies { 67 | if cc.Dependencies[depSvc] == nil { 68 | cc.Dependencies[depSvc] = make([]string, 1) 69 | } 70 | for _, mode := range modes { 71 | if !cc.Dependencies[depSvc].contains(mode) { 72 | cc.Dependencies[depSvc] = append(cc.Dependencies[depSvc], mode) 73 | } 74 | } 75 | } 76 | 77 | return cc 78 | } 79 | 80 | func (cc *ComponentConfig) GetDeps(mode string) []string { 81 | var result []string 82 | for key, modes := range cc.Dependencies { 83 | if modes.contains(mode) { 84 | result = append(result, key) 85 | } 86 | } 87 | 88 | return result 89 | } 90 | -------------------------------------------------------------------------------- /actions/workspace_actions.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/ensi-platform/elc/core" 7 | ) 8 | 9 | func ListWorkspacesAction() error { 10 | hc, err := core.CheckAndLoadHC() 11 | if err != nil { 12 | return err 13 | } 14 | for _, workspace := range hc.Workspaces { 15 | _, _ = core.Pc.Printf("%-10s %s\n", workspace.Name, workspace.Path) 16 | } 17 | return nil 18 | } 19 | 20 | func AddWorkspaceAction(name string, wsPath string) error { 21 | hc, err := core.CheckAndLoadHC() 22 | if err != nil { 23 | return err 24 | } 25 | 26 | ws := hc.FindWorkspace(name) 27 | if ws != nil { 28 | return errors.New(fmt.Sprintf("workspace with name '%s' already exists", name)) 29 | } 30 | 31 | err = hc.AddWorkspace(name, wsPath) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | _, _ = core.Pc.Printf("workspace '%s' is added\n", name) 37 | 38 | if hc.CurrentWorkspace == "" { 39 | hc.CurrentWorkspace = name 40 | err = core.SaveHomeConfig(hc) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | _, _ = core.Pc.Printf("active workspace changed to '%s'\n", name) 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func RemoveWorkspaceAction(name string) error { 52 | hc, err := core.CheckAndLoadHC() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | _, _ = core.Pc.Printf("workspace '%s' is removed\n", name) 58 | 59 | return hc.RemoveWorkspace(name) 60 | } 61 | 62 | func ShowCurrentWorkspaceAction(options *core.GlobalOptions) error { 63 | hc, err := core.CheckAndLoadHC() 64 | if err != nil { 65 | return err 66 | } 67 | hci, err := hc.GetCurrentWorkspace(options.WorkspaceName) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | _, _ = core.Pc.Println(hci.Name) 73 | return nil 74 | } 75 | 76 | func SelectWorkspaceAction(name string) error { 77 | hc, err := core.CheckAndLoadHC() 78 | if err != nil { 79 | return err 80 | } 81 | 82 | if name != "auto" { 83 | ws := hc.FindWorkspace(name) 84 | if ws == nil { 85 | return errors.New(fmt.Sprintf("workspace with name '%s' is not defined", name)) 86 | } 87 | } 88 | 89 | hc.CurrentWorkspace = name 90 | err = core.SaveHomeConfig(hc) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | _, _ = core.Pc.Printf("active workspace changed to '%s'\n", name) 96 | 97 | return nil 98 | } 99 | 100 | func SetRootPathAction(name string, rootPath string) error { 101 | hc, err := core.CheckAndLoadHC() 102 | if err != nil { 103 | return err 104 | } 105 | 106 | ws := hc.FindWorkspace(name) 107 | if ws == nil { 108 | return errors.New(fmt.Sprintf("workspace with name '%s' is not defined", name)) 109 | } 110 | 111 | ws.RootPath = rootPath 112 | err = core.SaveHomeConfig(hc) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | _, _ = core.Pc.Printf("path saved\n") 118 | 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /core/git.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | var hookNames = []string{ 10 | "applypatch-msg", 11 | "pre-applypatch", 12 | "post-applypatch", 13 | "pre-commit", 14 | "pre-merge-commit", 15 | "prepare-commit-msg", 16 | "commit-msg", 17 | "post-commit", 18 | "pre-rebase", 19 | "post-checkout", 20 | "post-merge", 21 | "pre-push", 22 | "pre-receive", 23 | "update", 24 | "proc-receive", 25 | "post-receive", 26 | "post-update", 27 | "reference-transaction", 28 | "push-to-checkout", 29 | "pre-auto-gc", 30 | "post-rewrite", 31 | "sendemail-validate", 32 | "fsmonitor-watchman", 33 | "p4-changelist", 34 | "p4-prepare-changelist", 35 | "p4-post-changelist", 36 | "p4-pre-submit", 37 | "post-index-change", 38 | } 39 | 40 | var hookScript = `#!/bin/bash 41 | set -e 42 | 43 | ELC_BINARY="%s" 44 | HOOKS_FOLDER="%s" 45 | HOOK_NAME="%s" 46 | 47 | if command -v $ELC_BINARY &> /dev/null; then 48 | $ELC_BINARY --mode=hook --no-tty $0 49 | else 50 | for script in ./$HOOKS_FOLDER/$HOOK_NAME/* ; do 51 | if [ -f $script ]; then 52 | $script 53 | fi 54 | done 55 | fi 56 | ` 57 | 58 | func GenerateHookScripts(options *GlobalOptions, svcPath string, elcBinary string, scriptsFolder string) error { 59 | gitPath := fmt.Sprintf("%s/.git", svcPath) 60 | if Pc.FileExists(gitPath) == false { 61 | _, _ = Pc.Println(fmt.Sprintf("\033[0;33mRepository %s is not exists, skip hooks installation.\033[0m", gitPath)) 62 | return nil 63 | } 64 | 65 | hooksPath := fmt.Sprintf("%s/hooks", gitPath) 66 | if Pc.FileExists(hooksPath) == false { 67 | if options.Debug { 68 | _, _ = Pc.Printf("mkdir %s\n", hooksPath) 69 | } 70 | 71 | if !options.DryRun { 72 | err := Pc.CreateDir(hooksPath) 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | } 78 | 79 | scriptsFolder = strings.ReplaceAll(scriptsFolder, "./", "") 80 | scriptsFolder = strings.Trim(scriptsFolder, "/") 81 | 82 | scriptsFolderPath := fmt.Sprintf("%s/%s", svcPath, scriptsFolder) 83 | if Pc.FileExists(scriptsFolderPath) == false { 84 | _, _ = Pc.Println(fmt.Sprintf("\033[0;33mFolder %s is not exists, skip hooks installation.\033[0m", scriptsFolderPath)) 85 | return nil 86 | } 87 | 88 | for _, hookName := range hookNames { 89 | scriptPath := fmt.Sprintf("%s/%s", hooksPath, hookName) 90 | scriptContent := fmt.Sprintf(hookScript, elcBinary, scriptsFolder, hookName) 91 | 92 | var filePermissions os.FileMode = 0775 93 | 94 | if Pc.FileExists(scriptPath) == false { 95 | if options.Debug { 96 | _, _ = Pc.Printf("touch %s\n", scriptPath) 97 | _, _ = Pc.Printf("chmod %s %s\n", filePermissions, scriptPath) 98 | } 99 | 100 | if !options.DryRun { 101 | err := Pc.CreateFile(scriptPath) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | err = Pc.Chmod(scriptPath, filePermissions) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | } 112 | 113 | if options.Debug { 114 | _, _ = Pc.Printf("echo \"