From ceae1e01db1dc6d93777e6c7c46e5148104c9c4b Mon Sep 17 00:00:00 2001 From: matang Date: Mon, 25 Nov 2019 00:19:08 +0200 Subject: [PATCH 1/5] Refactor to the internal components --- Makefile | 16 --- README.md | 138 ------------------ cmd/packman/controllers/configure.go | 43 ------ cmd/packman/controllers/init.go | 28 ---- cmd/packman/controllers/pack.go | 33 ----- cmd/packman/controllers/singleton.go | 7 - cmd/packman/controllers/unpack.go | 86 ----------- cmd/packman/lib/configuration.go | 5 - cmd/packman/lib/module.go | 45 ------ cmd/packman/main.go | 45 ------ docs/pack_github.png | Bin 91975 -> 0 bytes go.mod | 9 +- internal/business/config_service.go | 30 ++++ internal/business/git_project_init.go | 105 -------------- internal/business/git_project_init_test.go | 29 ---- internal/business/manifest.go | 25 +--- internal/business/packer.go | 15 -- internal/business/template_service.go | 83 +++++++++++ internal/business/template_service_test.go | 95 +++++++++++++ internal/business/unpacker.go | 133 ----------------- internal/business/unpacker_test.go | 69 --------- internal/data/file_local_storage.go | 57 ++++++++ internal/data/file_local_storage_test.go | 80 +++++++++++ internal/data/generic_script_engine.go | 76 ++++++++++ internal/data/generic_script_engine_test.go | 68 +++++++++ internal/data/git_remote_storage.go | 84 +++++++++++ internal/data/git_remote_storage_test.go | 105 ++++++++++++++ internal/data/github_backend.go | 142 ------------------- internal/data/go_script_engine.go | 34 ----- internal/data/go_script_engine_test.go | 59 -------- internal/data/go_template_engine.go | 30 ---- internal/data/go_template_engine_test.go | 36 ----- internal/data/golang_template_engine.go | 41 ++++++ internal/data/golang_template_engine_test.go | 25 ++++ internal/data/local_config_store.go | 40 ------ internal/data/local_config_store_test.go | 54 ------- internal/data/manifest.go | 32 +++-- internal/etc/console.go | 23 +++ internal/etc/files.go | 35 +++++ pkg/flags.go | 22 --- pkg/script_response.go | 41 ------ 41 files changed, 828 insertions(+), 1295 deletions(-) delete mode 100644 Makefile delete mode 100644 README.md delete mode 100644 cmd/packman/controllers/configure.go delete mode 100644 cmd/packman/controllers/init.go delete mode 100644 cmd/packman/controllers/pack.go delete mode 100644 cmd/packman/controllers/singleton.go delete mode 100644 cmd/packman/controllers/unpack.go delete mode 100644 cmd/packman/lib/configuration.go delete mode 100644 cmd/packman/lib/module.go delete mode 100644 cmd/packman/main.go delete mode 100644 docs/pack_github.png create mode 100644 internal/business/config_service.go delete mode 100644 internal/business/git_project_init.go delete mode 100644 internal/business/git_project_init_test.go delete mode 100644 internal/business/packer.go create mode 100644 internal/business/template_service.go create mode 100644 internal/business/template_service_test.go delete mode 100644 internal/business/unpacker.go delete mode 100644 internal/business/unpacker_test.go create mode 100644 internal/data/file_local_storage.go create mode 100644 internal/data/file_local_storage_test.go create mode 100644 internal/data/generic_script_engine.go create mode 100644 internal/data/generic_script_engine_test.go create mode 100644 internal/data/git_remote_storage.go create mode 100644 internal/data/git_remote_storage_test.go delete mode 100644 internal/data/github_backend.go delete mode 100644 internal/data/go_script_engine.go delete mode 100644 internal/data/go_script_engine_test.go delete mode 100644 internal/data/go_template_engine.go delete mode 100644 internal/data/go_template_engine_test.go create mode 100644 internal/data/golang_template_engine.go create mode 100644 internal/data/golang_template_engine_test.go delete mode 100644 internal/data/local_config_store.go delete mode 100644 internal/data/local_config_store_test.go create mode 100644 internal/etc/console.go create mode 100644 internal/etc/files.go delete mode 100644 pkg/flags.go delete mode 100644 pkg/script_response.go diff --git a/Makefile b/Makefile deleted file mode 100644 index b33b213..0000000 --- a/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -# Go parameters -GOCMD=go -GORUN=$(GOCMD) run -GOBUILD=$(GOCMD) build -GOTEST=$(GOCMD) test -GOINSTALL=$(GOCMD) install - -build: test - $(GOBUILD) -o packman -v cmd/packman/main.go -run: - $(GORUN) . -test: - $(GOTEST) -v ./... - -install: build - $(GOINSTALL) -v . diff --git a/README.md b/README.md deleted file mode 100644 index fd783f1..0000000 --- a/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# Packman -Scaffolding was never that easy... - -## Motivation -At SecureNative, we manage lots of microservices and the job of creating a new -project, wiring it up, importing our common libs, etc.. is a tedious job and should be automated :) - -packman was created to tackle this issue, as there are other good scaffolding tools (such as Yeoman), we've just wanted a simple tool that works simply enough for anyone to use without scraffing the developer freedom. - -## Prerequisites -- Go 1.11 or above with [Go Modules enabled](https://github.com/golang/go/wiki/Modules#how-to-use-modules) -- Basic knowledge of [Go's templating engine](https://curtisvermeeren.github.io/2017/09/14/Golang-Templates-Cheatsheet) -- Git installed and configured properly. -- A Github account token (only if you want to publish new packages). - -## Quick Example -First, lets install packman, assuming you've installed go correctly just download the binary from our release page. -In order to start a new template you may use packman's init, which generates the template seed: -```bash -$> packman init my-app-template -``` -You'll see that a new folder is created for your template (named `my-app-template` obviously), -inside that folder you'll find another folder called `packman` which contains our scaffolding script (more about that later) - -Now, lets create a simple file in the root folder: -```bash -$> echo "Hello {{{ .PackageName }}}" > README.md -``` -Lets check how the rendered version of our newly created template will look like by running: -```bash -$> packman render my-app-template my-app-template my-app-template-rendered -$> cat my-app-template-rendered/README.md -Hello my-app-template -``` - -Wow, the `{{{ .PackageName }}}` placeholder was replaced with our package name, miracles do exists :) - -Lets assume that we are happy with our template and we want to publish it so other users can use it as well, Packman uses Github as the package registry so lets configure our github account: -```bash -$> packman config github --username matang28 --token --private -``` - -Now we are ready to push our template to Github by just doing: -```bash -$> packman pack securenative/pm-template my-app-template -``` -![](docs/pack_github.png) - -And Voila! we just created our first template and pushed it to Github, Now anyone can pull it and use our template for its own use by just doing: -```bash -$> packman unpack securenative/pm-template my-app -$> cat my-app/README.md -Hello securenative/pm-template -``` - -That's it! now you can use `packman` whenever you want to automate your boilerplate. - -## How it works -If you read and followed the `Quick Example` you may have many questions about packman, we'll try to answer them now. -Understanding how packman works is crucial if you want to use it, but first lets define following: -- **Project Template** - this is the un-rendered version of your project, will contain the template files and the activation script. -- **Activation Script** - this script will be invoked by packman when calling `render`/`unpack`, the flags you give to these commands will be forwarded to the script file. -The responsibility of this script is to create the data model that can be queried by Go's templating directives. (`{{{ .PackageName }}}` for example) - -Packman uses a simple approach to render your project, at first packman will run your **Activation Script**, your **Activation Script** is responsible for creating a data model that represents your project. -*Then*, packman will go through your project tree rendering each file using Go's template engine and the data model provided by your **Activation Script**, and ... That's it! - -Let's examine the simplest form of an **Activation Script** -```go -package main - -import ( - "os" - pm "github.com/securenative/packman/pkg" -) - -type MyData struct { - PackageName string - ProjectPath string - Flags map[string]string -} - -func main() { - // Parse the flags being forwarded from packman commands: - // This is how we can use custom flags - flags := pm.ParseFlags(os.Args[2:]) - - /** - YOUR CUSTOM LOGIC - **/ - - // The next step is to build our data model, the data model will be used by - // the template directives. - model := MyData{ - PackageName: flags[pm.PackageNameFlag], // Here we can see that {{{ .PackageName }}} refers to this field - ProjectPath: flags[pm.PackagePathFlag], - Flags: flags, - } - - // You must reply your data model back to the packman's driver - pm.Reply(model) -} -``` - -These concepts makes `packman` simple enough to understand but powerful enough to generate any kind of project. -## API - -### New Project -You can use packman to create a basic seed project which contains the basic structure of a packman template. -```bash -packman init -``` - -### Render -As the **Template Project** grows you'll need a way to quickly check that your project is rendered correctly, -The render will take the path of your **Template Project** and the path to the rendered output and any custom flags you wish to forward to your **Activation Script**. -```bash -packman render -customflag1 value1 -customflag2 value2 ... -``` - -## Unpack -Unpack is the "wet" version of render, the only difference is that unpack will pull a template from the remote storage instead of you local file system. -```bash -packman unpack -customflag1 value1 -customflag2 value2 ... -``` - -## Pack -Pack will take a **Template Project** and will push it to the remote storage so others can use it. -```bash -packman pack -``` - -## Configuration -Packman supports local persistent kv-storage so you can store any kind of configurations with it, but currently only github configuration is supported. -```bash -packman config github --username --token [--private] -``` -The `--private` flag will indicate that we will pack the template as a private github repository instead of a public one. diff --git a/cmd/packman/controllers/configure.go b/cmd/packman/controllers/configure.go deleted file mode 100644 index a58bccb..0000000 --- a/cmd/packman/controllers/configure.go +++ /dev/null @@ -1,43 +0,0 @@ -package controllers - -import ( - "github.com/securenative/packman/internal/data" - "gopkg.in/urfave/cli.v2" -) - -var ConfigureCommand = cli.Command{ - Name: "config", - Aliases: []string{"c"}, - Usage: "configure [--key value]", - UsageText: "Will set the configuration of the given scope using key-value flag pairs", - Subcommands: []*cli.Command{ - { - Name: "github", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "username", - Hidden: false, - }, - &cli.StringFlag{ - Name: "token", - Hidden: false, - }, - &cli.BoolFlag{ - Name: "private", - Hidden: false, - }, - }, - Action: func(context *cli.Context) error { - return configureGithub(context) - }}, - }, -} - -func configureGithub(context *cli.Context) error { - cfg := data.GithubConfig{ - Username: context.String("username"), - Token: context.String("token"), - PrivatePush: context.Bool("private"), - } - return PackmanModule.ConfigStore.Put(PackmanModule.Backend.ConfigKey(), cfg) -} diff --git a/cmd/packman/controllers/init.go b/cmd/packman/controllers/init.go deleted file mode 100644 index 3213302..0000000 --- a/cmd/packman/controllers/init.go +++ /dev/null @@ -1,28 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "gopkg.in/urfave/cli.v2" -) - -var InitCommand = cli.Command{ - Name: "init", - Aliases: []string{"i"}, - Usage: "init ", - UsageText: "Will create the minimal folder structure which is required by packman", - Action: func(context *cli.Context) error { - - if context.NArg() != 1 { - fmt.Println("init expects exactly 1 argument") - } - - path := context.Args().Get(0) - - if path == "" { - return errors.New("you must provide a path to the project") - } - - return PackmanModule.ProjectInit.Init(path) - }, -} diff --git a/cmd/packman/controllers/pack.go b/cmd/packman/controllers/pack.go deleted file mode 100644 index 45f842e..0000000 --- a/cmd/packman/controllers/pack.go +++ /dev/null @@ -1,33 +0,0 @@ -package controllers - -import ( - "errors" - "fmt" - "gopkg.in/urfave/cli.v2" -) - -var PackCommand = cli.Command{ - Name: "pack", - Aliases: []string{"p"}, - Usage: "pack ", - UsageText: "Will pack the given folder and push it to the backend so it can be later located using the package-name", - Action: func(context *cli.Context) error { - - if context.NArg() != 2 { - fmt.Println("pack expects exactly 2 arguments") - } - - packageName := context.Args().Get(0) - path := context.Args().Get(1) - - if packageName == "" { - return errors.New("you must provide a package name") - } - - if path == "" { - return errors.New("you must provide a path to the project") - } - - return PackmanModule.Packer.Pack(packageName, path) - }, -} diff --git a/cmd/packman/controllers/singleton.go b/cmd/packman/controllers/singleton.go deleted file mode 100644 index 7a756a3..0000000 --- a/cmd/packman/controllers/singleton.go +++ /dev/null @@ -1,7 +0,0 @@ -package controllers - -import ( - "github.com/securenative/packman/cmd/packman/lib" -) - -var PackmanModule *lib.PackmanModule diff --git a/cmd/packman/controllers/unpack.go b/cmd/packman/controllers/unpack.go deleted file mode 100644 index 31a0579..0000000 --- a/cmd/packman/controllers/unpack.go +++ /dev/null @@ -1,86 +0,0 @@ -package controllers - -import ( - "errors" - "github.com/securenative/packman/pkg" - "gopkg.in/urfave/cli.v2" - "strings" -) - -var UnpackCommand = cli.Command{ - Name: "unpack", - Aliases: []string{"u"}, - Usage: "unpack ", - UsageText: "Will unpack the given package to the given destination path", - Action: unpack, -} - -var DryUnpackCommand = cli.Command{ - Name: "render", - Aliases: []string{"r"}, - Usage: "render ", - UsageText: "will render the source project into the dest path WARNING: THIS WILL REMOVE THE DEST PATH", - Action: render, -} - -func render(context *cli.Context) error { - - packageName := context.Args().Get(0) - sourcePath := context.Args().Get(1) - destPath := context.Args().Get(2) - - if packageName == "" { - return errors.New("you must provide a package name") - } - - if sourcePath == "" { - return errors.New("you must provide a source path") - } - - if destPath == "" { - return errors.New("you must provide a destination path") - } - - flags := extractFlags(context, packageName, destPath) - - return PackmanModule.Unpacker.DryUnpack(sourcePath, destPath, flags) -} - -func unpack(context *cli.Context) error { - - packageName := context.Args().Get(0) - path := context.Args().Get(1) - - if packageName == "" { - return errors.New("you must provide a package name") - } - - if path == "" { - return errors.New("you must provide a path to the project") - } - - flags := extractFlags(context, packageName, path) - - return PackmanModule.Unpacker.Unpack(packageName, path, flags) -} - -func extractFlags(context *cli.Context, packageName string, path string) []string { - flags := flagsArray(context) - flags = append(flags, pkg.PackageNameFlag, packageName) - flags = append(flags, pkg.PackagePathFlag, path) - return flags -} - -func flagsArray(ctx *cli.Context) []string { - out := make([]string, 0) - - for idx, arg := range ctx.Args().Slice() { - if strings.HasPrefix(arg, "-") { - out = append(out, arg[1:], ctx.Args().Get(idx+1)) - } else if strings.HasPrefix(arg, "--") { - out = append(out, arg[2:], ctx.Args().Get(idx+1)) - } - } - - return out -} diff --git a/cmd/packman/lib/configuration.go b/cmd/packman/lib/configuration.go deleted file mode 100644 index fcb49a3..0000000 --- a/cmd/packman/lib/configuration.go +++ /dev/null @@ -1,5 +0,0 @@ -package lib - -type PackmanConfig struct { - ConfigPath string -} diff --git a/cmd/packman/lib/module.go b/cmd/packman/lib/module.go deleted file mode 100644 index 1a00655..0000000 --- a/cmd/packman/lib/module.go +++ /dev/null @@ -1,45 +0,0 @@ -package lib - -import ( - "github.com/securenative/packman/internal/business" - "github.com/securenative/packman/internal/data" - "gopkg.in/urfave/cli.v2" -) - -type PackmanModule struct { - Config PackmanConfig - ConfigStore data.ConfigStore - Backend data.Backend - templateEngine data.TemplateEngine - scriptEngine data.ScriptEngine - - ProjectInit business.ProjectInit - Packer business.Packer - Unpacker business.Unpacker - - Commands []*cli.Command -} - -func NewPackmanModule(config PackmanConfig, commands []*cli.Command) *PackmanModule { - - configStore := data.NewLocalConfigStore(config.ConfigPath) - backend := data.NewGithubBackend(configStore) - template := data.NewGoTemplateEngine() - script := data.NewGoScriptEngine() - - init := business.NewGitProjectInit() - packer := business.NewPackmanPacker(backend) - unpacker := business.NewPackmanUnpacker(backend, template, script) - - return &PackmanModule{ - Config: config, - ConfigStore: configStore, - Backend: backend, - templateEngine: template, - scriptEngine: script, - ProjectInit: init, - Packer: packer, - Unpacker: unpacker, - Commands: commands, - } -} diff --git a/cmd/packman/main.go b/cmd/packman/main.go deleted file mode 100644 index 2c13eb9..0000000 --- a/cmd/packman/main.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "github.com/securenative/packman/cmd/packman/controllers" - "github.com/securenative/packman/cmd/packman/lib" - "gopkg.in/urfave/cli.v2" - "os" - "path/filepath" -) - -func main() { - - cfg := parseConfig() - module := lib.NewPackmanModule(cfg, []*cli.Command{ - &controllers.InitCommand, - &controllers.PackCommand, - &controllers.UnpackCommand, - &controllers.DryUnpackCommand, - &controllers.ConfigureCommand, - }) - controllers.PackmanModule = module - - app := cli.App{ - Name: "packman", - Version: "0.1", - Commands: module.Commands, - } - - err := app.Run(os.Args) - if err != nil { - panic(err.Error()) - } -} - -func parseConfig() lib.PackmanConfig { - home, err := os.UserHomeDir() - if err != nil { - panic(err.Error()) - } - configPath := filepath.Join(home, ".packman") - cfg := lib.PackmanConfig{ - ConfigPath: configPath, - } - return cfg -} diff --git a/docs/pack_github.png b/docs/pack_github.png deleted file mode 100644 index e45a96efb2498ca2d4f30323422b720614c50cda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91975 zcmdSB1y@|lwl++VU_pWgC%9|ShK2-pcMsCR-7OFT3GVK}-5nZ2aQEQaIE~XdFWLLt zd(XZ5d_UmpQKQG|RjXvqs+y~6&gbdx%8F7LXvAo6aBvth(&DOca0q*FaPR<>m(O>i z+ZK-D;9mGxiis)9h>1}uJK0-U+L*(^F~pb}8#BqUF!Y<47#sJGFf*b#xvPHr7OrXx z?(Q7v?56x>+(Vg}tgpZJ279ghGp|Om?nCke-99SXB2Hf?q~Z(()oF+TcT^JQ*|!p zbF0kDW`TSTfM`xnTq+^)D<@>^Ar23YCJqLUWbQW{+FZL_{E2y*@t^SocL>!9%nLyA z4E*q7yc%5sB8}N~H1*O_UEJ(CN>W|@o#SwMn0O%UA0o&B3Lrc{6!wr&f2VQraA)Vz z7i_uI-p(ptEQBw=7=w}rg?dy6^q)%_!Z%2GQi4pOd3%LN@<^{1i# z{`}ofa}Ue^Y01v{Ut&EA$o?mXos*4&{lDveE-LiruAs7|hq;ZmxTUSRo%3@WKu!)0 zPNBaF{3Gjsn*6t-I{#CYhwp!t{I{Hcl@wzCBf-B(^tZbHy8EmzAes>Sf9oEI##%Bg z2L~q#CnNq|-Q&e!22!@V1YsZ6^0KshlFOGOhTrAimi^pbFkys#jrbbM&_KyRz(gjS zB;DxZ?z`g;9j&pSZ)`mlUh%#oe?qXgA6`mn2B)-pkEL^S6Je48y*@C|h{FFf6)RGj zmLz>fqI`k#&%^<*J~@vq{TcE9CQ3;ZWtpg$P-D*joAc*cS;0j()aZYtUnnn__Bd5v zv%LHdnaVOCl7Du9GT_ZyQCeY1rof*6kojDl4Z=S~qQRy{EXwPXmZ*C3A2LO0Gi?7U z&}YPtL2wBdZ;DX}zWhVl&(GD>V*FE}Xwh)&eo|`4|JK4JTmUY;V*h9czt z-U>KxU2NRNJ1fm;(k@(BGpzoetF=2o+RY)v-KSNzdvaAias0XAc~?+o?UR?k-|{kJ zTzPrlAI#woiFr@JqD|>)HFsqy6PfXF2SZ=zT<;FamyXF*F<)PhKjp3(VbQMQm_I;F za5!0eKi}l|V@7Tww5_zZutL}y>2zqT|Md)yrSN2~1fv?oT72?x=w{mevLGA% z|LXLlP{0wuO?DubmKMZYFZo`eH!HNU6s=C_00>nB=LB~XLwnBfjyFpj#FQHOf6pB# z>@eFJpb9%ck(xF&k39exvx<8B`j z$6!KzT?-nsyR5OE8!b|sZ*wh;XHfazl>QdYYuZ>*?bk0KE&bX5pizFW!*6DzsS*i? z;RSE9+TcR#3O@i0NctM=t(pk!PIHVXLJXGPxuOeW(yB$}RBcjeFDjYwLjhlEJL=Sn zL!8F$l^_)AZ-WH|fHf8)Ons4LVS;!D>p~-7r0$%D47_>JiGqF`tIHf;R-TAx*EMq=1{Vt&|J@e6z?A|Dc zMZc!th&`y@iH(#tOtzEX>*c(9kOf>guewSP{fsHd&kk*}fR`{3YkTKv?MYd3#Py_n zAHcYc=<4>eF(tVai-2Jpl}1};wH6e`asxBcIUB=iZ7!K9k)2`Epi#;c;`FcG%lRzk zx5_q^c$7p~!OSLcM$>eQ6R}owRTpu+4=6S&Oq?#!5uVsP`PqbM_-I)=Y~-zMm6@PG z-dZKS)QJaNXmL&38qHYLf1}2KKm%$NGhkPFoXnS_6=0?N+m2ypeqN|LqId^+RTXdT zqE?pUSW1A&vpD@}s#jQo%#kG*n$97js0LA(51z$D7sRt~YB23%+;;nfYzS1oU}4Sc zdsg;w2!d!ewPD!A&x`=qMAa3HYPCg{fj!D-N_Ut+tJ}bdl1Rcp9NnU*$E)Bz;ON_M zr(2XYce}I6hqJL>F-IKVt270!j|+-7o{ua1J8^f$r?LK!J5E!`H>QJX7AgFtO7yFR z)kNWH15Jl}iJ{y1>5K*ihB^NyfEi4{_U19jkeCZ7_z?}SpMr~JRw;LhLFDUS_J`6O z<@X!stOUK0of7Yx_)kVxI2y80-4btUJXG z!12#!1=0Y-l#ldEnN%S&_zgO>7L!FPVndDvemV~1{)C1e?>A(-N)_K$S3pFQpiVEr`@#|DZowklrr`^H9Dn+lp?(bo3|rrd`a6Qsa~}`X{wP2(QK0^hx;oX zbh%HsX<&mwzFnmuP-SuE+G2lp z%JZsWl+V;rTSYz9{ZtuTAechJo2xC9wRmUb|3JI!CB*vl0HX-JKkG>{LVXOyV`D+M zg>jokGwzmbh28p)_aQnbHYgH;Lf@&*;PQvmy~et`uC1~38RE8kK*x)KE5muO(S~ z95tLF{#rUZ7L(a4hDzcdt!0SQex_l5iz63=7>}S0orXCCdVxR!}{fmtFIC^U^QDr}A zh^?KSnGR#n)B3^SZkt#q=Vo|UgYkrm=;90L>-}LSm#SDF0(BP2iXn4jJtM;qA4MX? z4m8GfF{(fEEf0;}_A8uXA?U65C?ic(U{rduUZSla4SuLaLI{AM;m#RJyY=YXtBfkK zUe*O`%~PD4+I}QgCpmUj_W=ErygB!@t+2h7lYrBw!#FP`b{<}8C@VFVe}l^7_<^5q zOjJ*Zz~WT{Y;Is!19|KGZcv)1zOG`$OFQ%SOeCd4pB{P9UEV5T z&|=PDm8Iq<*maRtr_t|A`eKJdy3kTr5bfR8>1&dslQiVP!E@7w-|b{VP9OH;N7)`K znF>rRTvG@VWS*dLQkUeE_RBtr7f*h*_st$z@odQwb~Z7qz2^QLejz^mCauFd&dRcc zgH%z5%G!2wUf*eorc`R(?tWYnHP0^)RfvEac08*j=iSvXxit@yFaVLV`5$#g^m2C0 zC1^pK_7{B-$7KoruE|~X3g?SKyxnw1(e(=i3z}oJV+IELXJ%tGN`BQUenxdlm}J6i z<)z!PTCMT%s$N*pod+sU$M@9++k5Tt{aHF}_Q^__hx+1NUdITJH7}v}g-6J2Ct%hk zW;K_7vC%T3x{eyTsIV~EG^=LBb%XkF^f|J+Ax+CWp#8Y)U1p~@9L|WZ?k@&lMf0hf zFY+XNh3rb3XP&~)w$sGd9l6K*oOZn6i0cmv^>&O&E{f@(k*(sX{30NTNvi}~V(-nr ztYbcEr!MBvVnfS8xoM-O;?|iK>5aeu#?Go=(>k81Uu3+L}4`kNAXfKC;x$kn*Xf-8i zHCIAw-+pP)cGGQamy#pOkmq;p-oAAiuVvRN5MhGW_O%&>3Z0NGcGZuBe$L1}18-Il zDGz`3!TxuZ;pnkPj?n3~>16#-#J8gFbgcfH#Yo+(O?Ozs6z+-GL?R}!Li6PzHWT?x zevgiI>LtlS;P&s9+ySmvP;V;<*nqc{ivMLoS1(XMqw5T~e|Ky?l3LHZqc}vsnzyK? zB-8Z)3DO@U#PY-+3Q5X)*4Vi|-@yQs^nrN64mWB-1Ec^o6JI ziQKk1jxF&3ex+f%7nk3?ha9=#%wKPSxe5bj|EGr}z=b`)g`cm>In|`-Qc2_~@7#1I zmPwFaSGIqfRc{OG{Y3A)Jz9H6oVhHz;Aa{dHxBgvb?Q`Q4I?oY<|<_}C5Ea>Li!~j@;CpX$kV*FpRK`4iLw2nx4bMtf9{~Zq}=&v&OyFM$8Z4y6yT!IM%1<-h?G6e!;&TT zXz1AqCCotmLBU3U<78GNv#$9G^a5I*Y^cZ3sHLIYuMF*$Qj(@_C%B`qrkBW$jl37h zYLG*N4A3NKpc<5zCh1-2y#>H1;@Skb7H)``F^h6rM)5kN>Tutt52v(t1|H0nlW{G$ zjbNei%zjhB%hcN=wPesGQS=qL0CTle+FgP3I#<%#Or zNpbS(O?EWr+wQjbcPO?GV;~EeT$VpX3&Z0$z5LFhJY1)Hp>Bf);GH!$oqQ;)Z=5?n zkn`-k?iIsSh!`=iRHP+JO-PBVYdNk6zSwMnd+@) zvtu*)ecD3*Y-(b&;No>Yaa?UKryyZ$>U|4VG5KxE{>PRId2ui9W(lHrS?#&J6e|*^;&{bR(SisT$9wkSaa; zl?o1)N}OGo;Y}{i)zTw5%D)?Ta*sSU?A(wDgS5j|lbpwdr4lYae8V|@@P3gPoWH?IU0>$pUd=)Jgf;H57+|J%;wP-opw#R6D<!M?B__Lx5j=|Z}&)X2>5>7h7RkfrGgA5 zhW&^K4Y^J7A8hn)G=M5Qxcs|5>@6PX2$US<+dlqTXDCx%Fy=A5`k-+m2Ef&{7IM+e z{$f&Uh&6OaX!RoGU3t8kpI=*B+eCdH8M5EBCNmGqOjUBXDs<~^z7+|#s0-)S*P2!) zu=049CuC@b#mMEQXcTdFqRkeO;@UpuocV>16pn{fn~MQz%J`4KCI5;?kK$}o_>~u3 zu^Ir%mG(=P)<#kwGcVo+CXe$_qg6}@($YY`P7FQWp|26Kfs|%y@*&0p}bJ|1KC?}rDJ4f0X`o%bP_;t5& zwQs1@zJt2`4lDM+zGFz$)A1yY%eHs5#;1p=`6;H*rY%RV1GKv1Lb~B{K=!z{sRV%{ zlgEXSAHSsrKTYJeJzja=QtHzf`2u9UF3-vR?qC$rUG{C%2KHbjsPBm*5v&g}iL~)= z?@IA|INb`Rtn*%?gSU~FSZFPbvIILpHovP>GWS=yS$0u8uX92UVP=!l#ZJqveEqI` zOW9y_H5#o^ec~(1TpzxCOUAxSW8{BcU+ik+bDB?Pzfz;4S+2_(C43rG zXEmi+(XlN^bT#16PW9zp5X$Fi-H+Y+7$ZfIgcXcAQ%i3)df4p79+CPkp%vPq*ZTk* zUA6q&U-fI*<-j)45bTFwcHH5sNq+bFEOc@cZ0kTTJ;T=`wDveb8tRw)SKB9lc)MFFgF6ma8?{`H*n{GW+{2Ok665l8*k%ASc`V&X>d+OLmHdTc z<87EasN&>xgOSf=M9=QjIg#aRMY+(~8bXaXjQr+i$B0@#g{w=z8B!Ncz;1XrHh$E~ z;V(LlDY(m|RdM*_EcPjh-KYWvs|_dS8DzNxxO)lWYT|X@NQ6y$OMt!O=#RV?E&GHXa0_?&oGr>$ldB0uuXZw*}AZ&EHvO$1CkJ^1}BJ6~?`(0)<+2 z^66llJ^R~>6Irjf8Xa$|^gpBoi!ugtt?1yNPLvpLczgV40Rxg;84j^Bb{N$*r;*;k zAJA^Ns`EKAlwdRDDNnDcARW%mA|2{~5LrvaFrQ_Nm`Ln{s8u*ZWm{#Cz|v>^DIsEXO5 zVlt9<0r-nLS@shRj&h~&fv9t99nkolEW$&_qy>V+y|<0=@#wCx9fYn$Uvag!Dbyj4 zXThGf{ht9~lbzR%2djR01kN7e*|xB`JIAxB`zFukbBBY~b329k2StTVcz1t}uXBiq z8I+&2n%F@CW98%|{1t^OG~b(*lHBX`gsRZC#wtbhiChvw5V$M{T^Nlj%7mtlROCL+ zL6@CQvN10ij5O!;Me^F`!Vd3ypLhpEp4e(>@BPSNg)I6PQ9?dX%TC?(T7yZek?|P& z+aAj^Npj|{^#|h}a154d2Lm(MqeN(oU)g^3>i?Pv%bK! zP!G&^3vBTgv)J{xp_Yn#_ECPFdAMV|IXDJGX7%i2;E~V=vk8sVm1+`Hb3IUtb?ZZW z?3apSSl_q#&e=7?9`DR&_Gg@CG_(fEHHtEQN?Mzv(&~*&{dqqIVGx!p=LAN!KdJUc zknAve3=ZvW*q5Fe71{~kl^QfwOjqa`J?+aJ30`iMH->n?Dmk))q&Do4!Z&{SXJtB# z8MrSTd*V&R;nAw5AS@0m@VO=USuu{K&!1zzO5)tOIGEFFS@|ttJ!75piQB<8M5sLU zGR?k?b~jq|LEm>qBUXj}gWW_;{?{ZGYoP_QGyfVr_fpuRU9$w5RXg~zmr?y7p6eSL zsVH(*X3dKDFhO@55RxQ*uBu`Y9YwMTP95(-DfbXdC|2E%SX#Mj)=`Z8x!O_(AVHpM z=`6fC>7e5p#pqj>^v+ErFQmsRES*ZR58I<@%tYK4@ipc{3sxp-7l2UyldYvj|11`r znqS-E%!Y?s-tQ_5%H;{3YPDrEHB>Ra(>m#6Uf9e|MUr=!#?=rrt(K-}~me^6;ARZz39y*m_x znNPp>?ej($Gxxfw!4gPf{WxOOIt(9my6wNKZ3!}$y2&Xz7`L=s_V-N3isc)Eh3^5psE&|szwR^oMd_Ih z!)8aIHb)3Fxmh9tktGVv)jHce89HprK8R zzm`BtcpoT6CgvYbIRahp$G*owYQh9>s1sGRYAg8kH$~h4${T5XmNfn&>8uze)3vND zr#CK`VUar-rvA@v=0*r<&em8)V`u!yp+Jo_y(o~;u&jud$9a~txk*i`STjHNK_OL0 zVS6}@opnYy;=^B|q-a6J`$UBYlb`IgkC6v*uNtt6%_7GY4uWFje^}Eb6kxSaP<&|)v^uybGRk;c^fNh zrCw>bxix`0ah_5LIb*Jfw$*E#pZrBKgPyJYcC2%{!d8#y;$HNq>^F1yVx30V8ea46 zM%cv)hkbj3KI@75Jb*I(QnYyo+KT8;Zc=sr0pXZ}aSiL$!|S$BCL)|G0<^RpTt5!6 z=PbtOohKwR>Y-@;)K&^24wQ8;6HM19KOOu%f)>Hg%Z*^x^yr0p>pgwAs8YGR*8Oeh zgGPzE%&+8uuvw?l9&w-|!xUsm! zDd9#W7O@>Hp$Gt~=44!M_K&6HqF0*WE)n~L>hr5XrSq%b6un+bM>;Bfy54BGbQfsq z4i2m=+f^uH7t1TZ$h@T{{CiF(snCWs2QjK`xpbyl zn%_xY(04{7W1{rAKtK|;^~-jUHmTz8ba<%G+LfBCJ6MDM!5Lo5%>$)#gnV;`TIM5^ zTQwD>kN?z0r*;9MPOa~J>t`g{eG>jTU(9><_6l$Yy}V3ckQ4TWJSN3y zLbh+$GOBRNNE$C@)8t|g);XFhz~{igl~LU-Gd((gNK;ux{M@xQw-L;lUZ8Px4eZ>@9+T%egdk23X0I% za2dpxy)mvOF0jOh@y%BAzDf7-2rK&)q z+bbxm(ysVNZWnF(C36xUyS$2#zM08~R-cR0K8s>!#ugzTuVfu9mCT5=$65v+7b<4y zoS$DGdRai0rH1XPd(q985OM?edE6X3uNz zm+Q(qBCg3kJz#C@PqX(tqPdR5p4F$XmBkn6gkihmw5y4GRyCFAk=L35(Q0=O0!IR zg3tc57YXPxT>B1d7jZzq=~ft5zi;BjLx&*kGToEFkymF$&&V&3-7HENO;8zda~IOY=m zC4{6o&4vAAZU>6<_HZ2Wtr#*AEAe~Zrj+T|s~Ae6!p1M3UD%0uj`zQ}W$DUTiel0sKtMaoZ?8u=Z~+F#8@`fZmnxCSyqg z4Zr53*eb?)xu8BcBl>1v2k9Uc<`m_G7*D%PThh`owlxqZ1)MwGiM~2olzHx>!rMMa z-1<5(Mla~iPQK+-!P%{K<)CJL_^MZ${cuD6j%#u^?yDOBt;u|ixoWvCJ{i=FoGJULAugySLn!ph@kasTI@eDi5d=b+KD@=EHMYJB6;+id$Fo4ujmU za71I72o@J3>c@V-5RVM)UJH`BJM$58^Nt6B41)VYAEXAXp_Roa9M@em^ zkVksF6WN?G81)+DgP7HL`Nyrun`EuVd*8xAvp zZ;ZS=cssu!X~L)k!+UfZH^Z`ipPS1j#mc8}>12F!sW;RvM>6BOJ`(jd zRyvfHOis$+dXh;Gh{mldW;|l6HsTEW%8Z{4n+co^C)?)eS z=J>;ea6|C z8T`0_{BdUpumztk0veiS`uRgG0?ZYK z>G-Vrza~jIH=JE_p0rYVt*yvS9Mqn+(~h@NK;DCKO2A_Bj|}%RoRCqwen7~yVu@$w zt=&%D+$LLnc6TVb&r@Qq-&z|w`oNP}17|H^^>CAbQN}#m^{&Jl*db!=H{?;K4(V>v z+PFE&4BQskPOr4D;MRsHktb62V}Fk6UfxFgxzPoi1{(nzorY6fy8fV3TqFYLE%)DA z$Mof}BHBF6@8HqEbE$(yK^R*%NZn~fL#v4PAzM)uR;DO=9nw~op{xDauz9m1;p!_Z z@l$+5S^YHR;TM|VX{Tkc5%+X*?NWzY3b%P8RmZW8&)(0U7=#}BcS&w-9vQ5sJ&O09 zE|0FCRuz+c?=*l9ht3)DCu%ohi&IEsV~tmF*YVUwDY=O*+Q>P|;d&mw6D7N^ZMBiB zSNO`ngnL^i$-}QSUCNMFigvit>d*&;S$Z6n{S-kn!Nwz2@@&E8)xQdmGu9NhE?<0jOp`RW@#=Fyv-jy5Q&hd@0jRUNj7maDSxzkxi6pXz z58bnisVmAjj<4iu$NH`2I?G80c`PvOXY8Bx3l6ON8r6yMZkdT8_4RX(j!D~&%j_?A zw?6GyBKMhQ*{er=^%aBy!q|4|#{`)w)Y%csa4BRhtdHN(EXZtPyC+3vL9>U_k%nrM zdXEIqF+~je9CYk+&PlV$jMIU16JL;D&=q-~wrn^-4y@%lr9!Y+mKI%Kf&IYc4+cBu zKz;-BE<}3@tHRfh@cG4xC4{R^PDgd;sHPKhd)E)KSS$^^>xmBN@(6cHdu5l}CD@P< z`jb5ZD}5|~2O@{?6H!!PaO~qUOhh{ApyEQRn|R;8<2=Bn{m8IbJ*RCt zh&h`b@1P2Nxe{_F4l_L44?~!)f7JK9sc+o!7gQ7KWvc%)t35g^1Naw$u2{U2{LaIw z^fs5By+Cg-hcSbATT4+{NVsx&XHo~G&t8yN2U53OD2j9Ny#* z`=rcKB3H?7q8BkcFEQhRg7Z2u^%W1t{!ejE&pUWfdc&%O0!iGViBVj2n~f=?+cSO! z5W(h=TpV~zAyE~H(!{>|jQVN+^_rkP+mvR$$A4;~F~wI99Y`nNJsy(gy5vDi_ zx}RD%}JzNqt8&uwiKg+SGb<7PKoj&AB<@2Vbu2i7S9>sxcocc=m+iOYrrIfEo zZyUy{kF7%qJD9)+-FbRpwLpOcAZ*(EisUB_DShhZ^SLDLlGN~ZaynL`tB=$l_*NWN zh#WOo@uU6^NsJ61rvTUO>FMT|v0`2@uUBF>7JX+_p|n8*b2G)X|x zozg~qB2c%}J9QV2hMsI9TtS>Yw_CoI4BHfO7!vpE>m-mbr~sQi?!2!l4eL%KyCJL% zE_$120*JGhiB>8-!&}-PSd^caX48T5QiCW*0O{=dODM2KXlbrQG`Abxw1-oVe`%ak z(OOPWYZI;(kZ#vJ**UGo?fw6~pZLL|dd+G23 z<;#7uHsGa;c0if~mfbX2ytfgVDs~q7J$hwZ4ptw}+4kCdL#k7sTph+Ivf#9IhrpJqEWoGyo|FpLkQ<~?KerT5(av)6qr(T0Mm-Q*+2*HBZMRWQ@O8qB zCLK#5?^U(a?8==#xJNLaLmVOVMZa}9)6v>1h~Gswghhlk76aeWkp6^N=QOsDE%mNW z93H4y(v$6Trc2@u{KFAn|=_7-t9rDz4`_4Lf^wGeibsp`UjbsE`#_uiz;kA z^Ye`j!$e8nZ)7~_L(%L`4{tX~5r^;xvHf23p}F|tDYKHu_abEn8m5PiVHP8K-xV(s z)sjA*(|^10o%8avZ1DzkHQlWi$mZ|`^E}?U-%v(4+_c7iZSiNWH1}hA`YLGZ zcTR^9<5e>xL^fwst{YKwCL^Dw)8-KqKI1snc;N8G=jKdDSil0{eiVxZycT6mCDTw* zgA8i~N}`wryXW%=HY&&%JPzrdF|%=N8>=Ked+=&*>r(nIN$D^VKbju3cK@24r!wfu z)`_eAv9U#E;`&8lp?Cce)BPYw`oq3?@30OzILIv9vG1NkQn%j5TuUMSor<9QetIX~ zhNP6Ru`3dvwcpbCly}{yi4T_Zbt-|l%SENwISP2}P@-_$SkVvrzw${EVk2EAm$_`9 zKd&1+I(cmuXgb#TcXEP6i-UVOF!ffdG!8!;J`BB&eoNgkP@|%Y&?jhUJkxe49N5- zlngGExy@!#|61hzcT3%#Wpr-e;s!uBO8-EX{@c>H6qNCZZ zZJESOzQQNB_55H!vQH|=kQ=k6^YceDT?c>Fnjo{CkE6q5mVeT1qM_VU&C6$e#H8cd zx|8%H$Fvp$5}AP1(7H=JSLziJ6NSfHapr1=tck>g)4vgXle{mACLCx(%s9X{>8CpJ zmrIUQ*_Uq|T~;rg85X{oCs}^GGNumRnJcGvTq$($*76FA>lz_5**yuCJ`xO!`7CTEX%P&jL`XTer8)|Cu?BajIp)U|2Hq_4-PVov@bUad=^y*n2+n70jGY z??K?-r8BVeX5YFbnIxV498$}kgLp3xcl`g1K@Tz zZEn*Bxto-2R#A*6ffW7?Bc~C0QTy4NH`7*~$8;ugxnNig1sPZDnFhl1q zqI}=jauV)E$4o(hZ0IG`Pcm?YVzT5ZO4Go}ByrTwujs`!tzvFap=Na>-R2ORmUxmu zrGK3x{NR=AI6O%#Y+>T~?8$9!TTIaa`c*j@okA6-!-KY|{8aB%z9b9Up?SPa=e8Cs z+hiK=a~+HKH3BO4+dA|L#dlKeeT6DEX2vgYXQTFAR|V_*#aN7R5O6 z^4daI>HB>O@?ZFWQDQ1%yP*QfJJ!uiRWf77uPx~; z|WL$j19VwR8GmSopyW+o0iS6H^ zl-?+2k384}GJt(tqn(*re+{juAp>X@dMDwWBQx|-6ve7s9j~$<%vB~m`@_XE&==*W zi*U$J*SfxFvfrFZh%_pz_$2Y!#XsY~X#|Z7--rbKfv8)ll*d7-oaO$F_u*iH`<)ATgb zEeL9P#@HbR{Ja;oqVja6Jr;UDU%N9dV}UNc(somaX^??9+f#h^iF>hb#AcyJoa{6*3f*?=M!%YjJ53YqKS?M7 z>U5&+F!c&J3#EUP;=h)ubYlcr2{tsjm{^z{^!-n`T3~~ah zvWyZ0&G_oy=g_~d*Pcl)|KGi&6;5FOJRkROWB>P3kN!+8O1m7hmiQldC8{aUq?7-j z3l}|q6ttMh@1~9^|M=nR;i`jFM*yI&9^4~FDfjhd>_Pe>jZ~O5jaWfZRbL#A(mxfH zrv?8PLy2l~qJJ~;{~D+@d1~y0*1L{j&#`xG z18(3ut{u6Cx)hFojJD_k9d^R%WzHrQK5#Xz;rN}TEcoMP@EbW2`hQd!@J{CWg_2sv zOOX1H=|L%s^9-INw@!eG|D&VPbbsb)+)VF>|DaE)KV2~`Kg;}=uKtbmGfDGInzF1} z(Ejw#u3Sh{I(xM-uHd5PX}~jURm49ZsS0N{YL__0;cQX;QSs|U8oGj1P4DXz*I=vu zNzbm*!plYDo4-j#lc;}cZll7Y0J4zq+C)D`D26hHy$|iqwg#v@&$knvL&~zVWja&W zWWnr@6F;ONC+lKPTm32me)mo3{Ek1_o;~?r%!ZRyYRm@8Ue(BK4_D7jyB{skmTFZd z{^*O8ZLWdj8FvL1D67a|8Lbw-PSsp|v!MDh9+QC?)DoMic_75oK`|VMsQRU|+O*GX za!ck4u$t?MzE~hBkWBw)V$cu<{GQauaY*tIWjE^hP-8Yi_3X+_e0F8huI;P+fG*VQ zb&y=P>w67I#EKYos8Rn~OL>lx4d(WQ(LBFI3bqoMwMJ!8&B;RVQtPerBrl0on;h2P zu=q}f)%iaG1w2la`-8H}enhNcIo5#2=mea%(TMnX6i3rUl=kOJ?RD7o>y$$Y821~! zKFACf+EzWctePhsW5&GFBtj>hABvx6lcHMVa4J&cehjpn+&wfKVrQjGPh3pijJVoD zl=aRtCFE;vypD_{7S`OKDP@HW#w$V(a}`C(=RAGL@#bqmN&7q{Ln5~ZA`WYvj6#Y@ z$VmIHTe}qb@7r5mXR$tWCK66@3;QK90J04(D>*)BCy43N422iBLaQCVt(_<_v~mPp zkJUeJNh+TjY!@xS3CLLF)2r3(D(&Ys3vCjss7}(EOU<~-xDyfzpL`{fm`tA+L%JoR zs=mzXGiOKWVt4aw>2rtbepzietmX0mQVbpIUrRC^53$ava&>s7d3l~fy3S7Nvr3RI zt9n?~i?W3t`@5YkHMD}-dG*%{6w>mPz%M3qJ!{|PHNc>@!ff&1G6PPk4!(Nr1 zJwpx59`A2dOEt@*2Ul(}NFX_V-qPUA<$S9I+4YlO`fmiO#tCxq8TS7KwkfVZRmzNd zcnqM>SJnN1GHF!gWhk2~e!snJTpc1@4Zp~e@`4N{#<0y9(aw}=o@QY+a&HXTLqg>M zi(waad;UZFJO`5zu|U^bBUN!gA(Mvu{dhp#O7G7Z-w)l2Qr_sJ&W(N{Qa+dEhZ9Nr zPIL4IGDgi-5jMY@RCM22`4|ri#*8b>@>1WQt=JRs-+73Vp6bPboP_~UGgUNxu&tt1X(-ICe@%^e=mT&Hmrv(X zm@Qv5K33o<(TEu(%wz}6Rgz#cKQ1eerK~xlmRDXoo1&mbcc#cwN+ewP-dzopyRVcF zLOytI6qBFL(^C+6A1b8*yMIdVHtRQUGS|$57AopE@R6G|=aBv7joeQLoci z8~wpcPE!$k9l)IQ$J#9lMvanq^$PuYe{cXhj>BbzYJOO zg>Gbw%GsZfS_y4jeu~N%wOZIp(>?@g8Uo2C$6@n@WyE|o_ey?G&bb$4*SSJ-FrD@S z^>lBMDa?*XySjHmt%kYJwtT!-5tKnMaD(7p;uuRdmg-Rj8>z1l(Xg zz;+ia+#b%bpQv_~z5N5VdZzSUrsx^?Tb~Vt6wRdzY|730cWgEv<>$MVY>kpV*>{LI z5ifNq9qwJHC}1ke_(~SIxURKhA9MHB*ODckiw3BC>~mAH48h3)kRM!EE_4M&cD34r z=lv+Iju+XDN+*H4kU7ZLfcAWO#xV!HWVaK)q8<5@=B=X2HK&v0XUft8t~BQ}%90DV z^pwrGTa}IFRRPXoyQ(~Ps{Q(Q6!{q+S@NN{ny@%+#hn_Q6!37DtS5-msl7r^?k&Dn z0W`q(EzDy#Xp#3SHTQYprG35~7rB$g90k13_7A^GZ$8g+UwwQArpmgaR?->ReOsLJ zUF&>Lbh32L1%-(~`X{I-Yb2caE{CV~gVX1XpwkP_?6e58;b;{m&9XPuDBo&5&n>!m zVm2t0<%XWajQ!zYi@y{IXXm~T5Tb|8FD zDrf7t@}!(@U>rWPRthuB^Jr1~my{E-k&dyadC(^4F%c(o2Hw`Fo*{j_uV4IUokfmSTf|>fo8udPyuwO@v zR@l$2n-}!OMM#sXZ7hMu2Z94JLad;41Q9{<&}-+ z(~ot{Ib&JT5&==SW5&d^<&k96>LDz*A=K#$$2NO16gYr+2N$*GZJ*VP) zH2z>LR_!KjtC^w#dBT2$j((3>(B#hR4bjXQulD`3u#p;Q*v9G_(b;(=c<2e*+`YZ! zv3UeLYNWo_IRaB=9Db&bAMUbqdF&K z;~E2_j~!Dw@36OX zpU0kC=|j9pyO-%t5}ZN7S2z`7j7>g6iIsJ$fo`_CsrY1MU)@*z;>uasfB)Bi0lc%G zRCc-%Ou#G-+2(kNd=j>edJiVDk`;EG*tA{bqMQHB zE3WxCW5t-elGV7p)bq4RS`6&2k682G;(Ugc+`f&blB?>Pyb*6Czokt3$Og^Acvqmnfla zETxeQR=vg|C(R8!LYT7o&nTjeja2Eg9<9}Z%FW&(T0#R#t%D(ULz)w6EL6ha(}*-z z8rNWSE_igVEw{_u z&qKs#qM1`qXDl;ar(*f9N$Kk&5%4dA5?{z7B9igbM`m$IY#V=$jY<-mo>DQ|5v|+n zRv-|g?wD;D7hl#H6D}-gn)e^lJI0(z%8PY+c*eM@WHePOJrSA}>XN_pS4?Kf3cpre z#yKDA$q*d;>7SU4WSTa%5HrntJ_eS9jJN|i2%o&6Z9RhUD{sy^+SQjGM(`InlccOl zs3ssSrHA?9wk!(^kw8OZ?{1B>nY+!YF)+a^kw9pl?*GT$TSiqC zwSB)*(%s!4jevB6bP3X3(%s#lNT+m{ba$6@gEX7&?mWvoeI7mM^LfX3zpcR@n?2WD z*P8Q+-}RsLlVkhU9f-;cC4=lsMWoRS7~IYZ{qwaOr3Y84(jc;+&D#1E%$lOv@1Ap| zN>jnrnWLch0Rt4e?pLyYDTmti%SLe2+$pEoMD$tdu&%*R?;PsXL&m0%zQd0m=#61F zz=q&W5z>aQc`BacNV%=)>aF=$`lf!4*B?%&;nIo|#Ju}%B_ z;U?Buu0*?8r>yDFz{6Sp2Y^c-<6;h*ogRpM{E}KaXiRRlRN?1&-^;|6gGDvF{pi9~TJt1QnA4+Ixy7;kDIK(I#NG>FkUY+fBr+n)gI zhUAX(&$G*U&E0vd<96JuT3QXeXHclt{rW0bL1--Hh~Y^)iPtH(#%xAw^k96JJLh%# zSp+5KJ{S7FmaP|P{a}vwq`)LJw?N+{ufykAMvu*^YR>uYqSkY z2y;~!#*&?y0F;_Rd&K;UWAn`+_00E=3F1Tz{Z(f9gAar^e3pZEdDyyd41JzuO-h-_ zy*!+&PvNIyx9Xbt%HeoWLF9@l>Ny)b`tklwWaYG*3WF*|SJ&U#96`O9WvO{m@iksX zz3f#P3k^2?OS3>zU$%NA|S@iRyGDgM!JigfWFm>v>Q!v z6z+;_FEOj>EZI}*rVl*d$HCx=l&h03aZe2h>=gt-wU8EXjX>GG+gtuz(lPg8VX+_J z2xQV()FTM^*T-P#<{;rm8Fbs0O7^~$L#GQ^E!Kpim+GV~*dHk;4!Eqon5N-XL~z(F zNR)lZfo!1wVX`almj~K`_W48?w3wBd{-Tjgt5avo3j{2+x>F zmmWj}rUxojiifVGs$X$K5rV8VYR~ERhEz)`c&TK*6*5s?pjd7kRcIHLNfnjm<`4{! z?s9oQv?6bH_BX*V#RA|cE#_c+ZAn+qd)lEUC;Z{3LtuG37hi7lNVHtt4kwkCSa3aq z!BT5EwLj^hIp8Fg63#=V)hWs7#u>M0UeWIjoO#9MJE!Xv)GC-m{o*JMkruQFyN`jT zq{fnFr2IAT0B8Pm)yF)D(=021&eH0LSFIf+fX#Qvu9#w$3pZz5E4l=DBH#xwpJIZC z3_Qo6>MAQ5a^Mi=Rr~jt5aB~#x`3EvqJB2{>ih~)x{%F(BX`p-Q;{gu zrlDf3!O3Dyu{;Uc@{q4Di^FF2XSOeOoMS^ncS>itJaQXRzlCGN)PYTVK|77?W{bl@ za44G6juy3;Fz0&*h?&}e?#3&_-ss8wqBY*pUq*H6#Gj*+DtIR5?x)zMg$tlh7dYnN zlx+N8*k5kTQ+*x19HV|rmTT=1)go=_*r=`KC#dZLVa>lDJxRI`rP08YGl|fQ;_WO-z zy`CFkzXrb>Y~4ov`L3UefLVeE+imSSwLQj+e!2=}8lF_T26$fWR6PdUH@1EonGiSK zTL+Uc^&`d7Rge+O&d1f98R<}IDlRJvGuCe{RBW7G-i+E&WNM&YWTSt>;sztRO(&d@ z15<+lKRD}(*a5U@VESe5JEdtG6P0|DB~4kO9PPhz=(_26KOZ4`%;P=wq2RvDFOf^W zk&$?xW;HC&H^DPe!!aio1-k$PdAuiwlFlXlDJ_z;Ok5_HJ?H>L8BOWX0tn(^>VNC-6)N8y_f_|{X%Xh0 zL*0Jsx5iP{f=37U`lU|8^pe=miCFDqVj3_T*-J>ASRh)=f<(LSiyfc#Iy*zbW2AC^H`1aNLRc%{aC%T;|UEB_PsBuzls3L=BaxeV_X%Ym`F&_*}kqs7ekq7AF^x(kIv zj>4;c+3BswR5%_SxQY*nctG=-R;u0JaWCDeF@k{UOOY~qY>8+LARbf*EezWzvW$9` zgIs(cDe&}3%DsX4aZSGpv!NbqVjz_SCubKDon1f7wE%wf>gLce-$y1G95%5|y4&gu zf`E4xA%U@>#UPc=8>SMOhDZ<0Va^c0Py#Yt-r_-F81x-ms;ZC-k`i}Atg;2Qr?DhB zG$YF4);e2`)p*pXZ1`9stTd1;;xu~!iMeQEMpbLw-#A^)ax>VBqIM+nNwBQtknEwm z$4HIQ{46HVpVEm0hx#PYYB(ANzrgeS&3&uw5yMAXO3yKZ273cbR!A&mnRDF7<*?~| zTZXos3h%6qaoE0FGAkLQ*IeHX`O-40o$56@nX`-z5=J1cw)N9w&X5Vcl|b7KOt)F- zbl;YccFuVBatRfpd=eSzBi6`*;2SL;7u7t37J6+Up6WtIfK@daB5v% zgcKF;K!p9U!N#DwfV;O#kO{jAT}yN*EjD_4YaK|Z9Xny~CnFVhwA31`*sgG`7GV;R zyMd(lB#|G+heN3=HW7iBb?s#@Mj zvQ++Wx8boFVm_?8A5W`lITy9O+thB{X@iDD?`D|6E{+tyW!%YF>?Ep5)V8l|xx41U z^1jh_`B`$V>vfUVeod=fk~!qSHqI&Hc%CDpsH{D(f_EM)8^gmh;wXFAb_Rly$P}v;8!(q zNpIH68clAO;*pjDWKJ||$CGM(6y|nsHdzQo!eEDkTVA5sJ|r3s+u|`Et+9}2=ux(M zEvn?BAWe*>Y|G*;4pC^Cr?7ML+}iVq;T18cZD2|1CjZ91iqRGJq z0qR2GkJHpFFbain};7DtdL^nOrAO{Z&-JRYmJ=&3fxhwu5wCw?u+aW{n4pN2&_t2 zhf7^%8*d~5-)vkp7JuSiM-6!`O$pzhA0iQX#Vfgm;L|91rdd8! zo@&9il}|ZoiT6)MOxv~CHU|kqfI)cHGhf{Hri~(}D8C-iOAC(VbIjnqb0o%(eCQ#6 zf%h0m!ooQ^A>oc>CO}N1DxNEgU)t|w{;=p-v7<=gE$JpECdI?mH31tb2_j+U?@;h- z*ApTLyFj`k42p(YHFiV`0O=g{bnhddUUo0RL0#eoabdSjm|%H}fcQS|U~lturgA6E z#8TgK1;~}GIdmGsxe&;Di7m2sZFMvM^2)?X-q&I_^dNe(Dppp`Jo4#CUV zp>~z{8u1@80?RkgVYGU)$$u8-=z+JxBIQMRalb&vbQg~>#2L>& z{^did`MV=6H9f2osr&3eme(5Mr{r~(^DGDYWy-}WKO=$=+B|~0crQ0VlSlJy*AP(B z1Bl!X928nOeO=lERyMl z?FBgLA2MxBk9d}-bG_YQ$8eM=h}hYKpQh5xw^|}bx9lQ(qc8n@)j|-n@QHcFE6EwM zbI~xP%n9B_w8ZOxzHXP^UADi&fatICCu)wF&F77& z)yDx|+g0uSk3QK$6`efLv!~R=UgHZn0x>q!iYZ;vY<;%tdG|n%9u#{0ayB|I$PJ>X z+<~fN>wbn~bpkhzi52;)X0wh>w{yiu5}l5T*GzC(3c=qUa^0F4TVEqb-|*ipv#J--ceROF4xtlYU7q-()}LPW3^65PxB z0oZ-wAz_1AujwxjZDii=7)$ZZ95>det{DwSrrth;go(2OH_K*e+3-%n0wqRg9jz`u zE2Vj+F9`0S6G&-*&|6;0`mH7M7|IfU$(BOjSvo`~g&h<;#>euOqwQ;CI*&J$ghPP_ zKM+>Xmqd$q!HHS`{u7~x?=z+Lrc{6idBWD<4$k!Y5>C-85$hx2A{T2-7o zB%d)6UWukR^ahG`x$yre3*)(vD1BbksMnAE_~xLIE*M5GUa4XjXer6B5>&N0eX#JVpfGEat34ZvOdNk9rNB zi`)3_4p-15QdqsuOPdmgQ((@GXY;e|#(U;zX}EPkuHhus=W|jysUJO(=*mTfHtHkk zteOBFhY9p`g6zn|IXm!5D(V`r17If5F~SmaTRm>(5e~8#1Bc%7Qm@T317#;Gs*S{q zlY_|QX?FBu_rT73wA*EY*ae)p%Il^Ul}yFp4T<%#uNX|EAlu#`o;Td%shQPB+dL=L zS5rzHU&)V<(0#mz}VY#x`w`lfk_VY9d(RUxe{Jg{~=7* zusn=2j}NUHPVHT&INtdu)Z)$Z2ver%A9fMMp`qzpQEJ8>h`fcZkL}YKoN%n#el33~ z{d-~cR*UQ&!A5g=&!^9Y1~eSe9-M23fu6zResSuvi6}_>dc#vUs+$TH4{n+ z^O*>z!8=)=4!%y|nFHNG3ZFhNG5CQ{2Vv zU{u_NLYux{Zy-&x@|||Ac|0YwG2u}38k22RjOhj3M|1yjSnSv7z!!425&~f5u={^K z8e~d*^w>t&XDuA}rm@L(Abvxt%*@R31mr2BF`Ght7wgk}BPBp!AIOiz6y?aMP^iGU zMb?!PY;nJZ>>t`mQq_(GHWTT0TG8JAj3;+ZAzJv>Twsdf0F+(pY_Z=WJK#j3?!qp`kbsgJo4?HP^M)&pthK6U(3xHn`A`5-)F zInz276I{4gcF)1Aaa1W8^Fo>s{F~!d;L^l5?()Y)9o6wp0AB-~KPy4$rTlXvd!i{{`Ej{RP+j zAO7&7xR#eu|Mu*b(bTG)(=1opM1XoXBQ?wCT_1V5)NzQ_=;P#FQs<)*8|aC{z+&DSa*+#ZK_&nT;@1Rb{m&pBXmZ=EtwJtOiF*SX9 zz0F`#pSZ^NGK+Co#F^6P8|lxF$ON1uK+Aj#!Mp>7+J}PX<_vn5KcAXGQU`l&W+J`1 zqWz~KYdF@pPcZBi0*hI+n3`5_a#H7SK@cszm*kSeR!kDxPrO!{4 ze*T9=g^r*S4fHE50gNiOx*j)%M;w1 z;VxS)om-?Y?CISAE&-SQ$F^JdTzZWbiUG4;Vep2?Tzf!WGY8@25r4kc!i9cGaX1bn zvxYWu1J|0Byj}uj1rmq!MU+0fB{*6}HzZP{oxOZz@ySh8kc^7qH(3F1yi88n z3MUm2*}iy`E+2B@SSDnlFQOK>JEXhwdfd#Va?mJyF}=k;e-_Ue>S{*%Y)|L%v;X)1H{*u&vIW)j&xT zVASHe5SCPBu(fYAK>AI($|z>=i`Yf029V)?I4;#{Wgo~3&ohyVu?hZdo$SCB?dbc~ zJs9pW%aA&q1z)^kxxP9_h~1B2CF)yOrqSRKbVIPo_il;JKCl@BRld9v_cP5!8^g8s z0_sJ5an1`M{n}=EN86kPFk(F)*tMO9R7zs(jisBrr|S3|cPmP?+l6RV>3tR^Wu$~7 zd2Q<*eIA7d(zU7SJ}c&r-fE`%+#v!?aBhr_X(_xr1L4aIM z%athMQ;M-?YWa6e$)6J}VYMG3|Ul~LYMa4yRcz#zcr4PrSl~<@Mo<2`}XQ4u)p55@mUBO#m zMaNZa1pCscwhFt6qBeW9PxGqSrT5(U$kFo*yd1VRLqL~*&()e|Ac3J6Dr6nYRombK zR)&1x$=0n@qk1$3r~=B=3k@Tp3G1YGQE$*$7<@$qY@1LhiN!c3*Be>S>r7RGP9lD*HxhurVVBr`ST_^e(f z+V*6fOE9@L<$Sb6?sb114+W3)fZ=I_z*{Dv@cc}mg-dS3Djs3a(pR-bmuH^`=N~lh z^LXddxIQ@=!&RLKS#lU9g*mv0#ql)MaK<9b@B`kd#NO}mE^ou_XlrtmP*1l z$NU#Q>GQ1-I$6ATQA13P3idmL#&*}oi_{_^;Zb*Rgcd*F@iwPBa<>O86O)BKIAnW1 zE#ou_4u#px?)tjkHLlnTYA=C~Uotg1ii@hAS3(m{miOs6soUm2`ec?ioqDMhXn5X%GKZEvQP(p|%Y6b0i#j>-fq0Km zRcBUxq0ZcT;^odIspWp#!ya_E^W!~sy2;L3-BZ_EJ;3m&N>7QI3@zP#-E=Yb=l!q) z=+!7}t4bu$mp!X4I04E6u_g~lF_g)l(s>(8!+o=2ZLj>rjyP5`k0>Lx>m247_pT3LnN3wC4J+PwxQ1$ZOH%LI zA7R?X9IZI`&hW^!U34O`k~So^+IOU6dk{j%4^eh$vty{3vf+N8=Jd zz(xD9fyG`my3eHaj$XeNn;RI}cy z{$xj&)nq84bTL;Y+}wVrlw+SYt56`rG@a)8$I^e7buhHgX5fIjo7GIiQK=!)LH)=~ zndak#)*6U!zQQh6>3HY4?X z@2y6X-x*BTQXCSGz-3%WF0^=i|Q%XJ^11XbM?svwUyOsIV zB;au%ab?_8X0z|prP@l#92WEdL`L@njhFz?mYR3~h{WCtzo9ae$Uwg1WJ(9r*5i6# z=xZ-}y^K4Z6l(ZqS(Fv%>|42OW&HMMUZC)+)43d|%vNf$1Ut(BO{W^``Hy&JA+L3ttZX$d z*8JoGO^qKS2&7q<4$Ub z0M%4@pU1o*)t}EH8vXqrK~{75LCY^uHv)B|7?5J$u{R?b@|t@~+ATh9&3z7sag>tw2$>GQBt>@f=sKh)%OTU%JNtZ6ZSB-OK&?6DVooJJAOj zAuYJy*KjKJg*W{aiBFSzWzJ%{kTH_?G3uqXC*+$0uM9+R*z}NX+xOk>+AzJJP@7dxipFx=2B&ij9hH)l%Jx zL5UmJRvHU#YLgBIoz6}>!RKGphq8vd(GdLdPV;$)qN2GQe`1=4BVfsNv7ZLb$xPt# z?!Ewr(D-61WA~3SE-x>WQPM2uO~SEf?~4D-{uu=KMlI7HE;QSK>fa8*BXljTw3R}^U*W2qAXzUJ5b%8sz)xY15zlZJ zsy&_h*sQB^poM@P_F7mol6$^>?jnZ2)ezUeGiud4!vG5nMd2k70HK4 zZ~m#WV8hr=wrKs6a)n5#2=7g?MuA?aXry?v`qPDZ`Jq=4xMJmsc$B`1kFL)hF8U?vsR`kmZW5%xt8-)NkW0C%bK3h%Kot zqzWz;Bni%AM|9+4*?DvN5RZJTV>ZeH=1lQQ7s3085>&z@NQlCB1IpqT6|- zDLlWZT{j35rX#&rr?l$GeSy{VajilScev{;#BB(LOrH!%HB*p(5$R=H+z%J%P&HB3 z)aZVVKtdp&%-vlxp6(mbF}@xC{0xsiPUtnJyLA2_yQ_2rU zxYOzf2GcCZnV5!T>xu-<#WuQP;Ck__Wh4wMv&*M?*L0pA-5WY<`-7)8Id*ljE)QE2 zu2_;+i^5#`Y8|_(w$g;XS(DR=;fmr2NeyrGgmz=|{pYbhc`s-$gO@29tlA z%if5?igNfC#4Hiu$P4@|vtB?aL8OT14U|7Qq?bvX9jWLIY(ei+Ly-LogFUZ+#heW@ z7%KR?n$`4+SzO3CkZ0>7irtQT~jBA~nCdf=1k8_|%CDJp!$i4&=yX5cPbmdfQhMxFi= zozu6#2KTLi$M~~b*{3~0$-y|9O^|ik2(p0dj$S5ondsY{JI#h&CTZ-k;u}W6C%^7B z^}-X3uN}m{|e$wt?y^DGwE z^z+3kKVV)a!~?FD2v|)73RpHiqmqY#4R4L5hQ|Qn9S2>j{kfh&=P`iOUkffvKHg0kZGR~MuwQ7s#)X8d^3wUBSJJ$zG<4w%%D$stVrXXSL67O{N zk#AW}eY%+!Y@vC}cXR14z_YQcZ-CpfUD+S)|fCyK*Kx?a`-NY04if{^>*%*{( zlA!<(h2U)Yw%SvTqQ-Qkhn}1n8Q(g&Y&Wu9Fi~ryMc*7*s8%Z}*l&fK#6e3c;mxO= zjb6)wK@xO$~R^~+9t zVL9{t^iBs?vyKVpE-<~WsDp>tp9aCSJg+!NN1xsFSLuBIX%*_*Ln(_gE78O<7nOD0 zd@jqOA}QYMFIuH@J8()e;EjfZFp$r}P!|WR)pO~%yd2;1(RNW7r(LA@YzplS44hf4o#Hj{UKC-`G&I93fqdKf#0m zdYmYO({3;y(e+}tavUF$h#m=g2*L)8>r|{U1o>&r-(!PPz+(k(ape1gu1`oO^fzq} z2>R2n2W^6H77HQ3ul8pzDN(~T3jsNOi73J`7SakL_Z!4y4Fui}|I7x9cc>J&t3@5| zV37*xU{HtxTCb*Ll@V*0#3lXbgqr^PF* z{{`W#Z?F@Q-g*QLCC57(ORDoz_|5wFy54Gwq~oPCy(Id^ufUe7ll|&i?E;$ogpO{P z{fL3B1Qsue`u7}j3>6s6#&^odh=}QPF2yQ_^-tNXM#`VZBQA{Y0A1bz!$|(^K-Efa zk2?DAf(xYM6&w}|(~wp$mzaZz4uOt^Ygu3FacB>D_T71c8N5V|Oz+4`p19tMJom;^ z?w*=4IF>;KsW{f?>u`JaL?#69AS;>D|JWL_3#MkPQ;Tj9jvD27Bwf=NHOeZ>K58WV ze$@wVtX7G)U1j^zAkXd_A+V-1$^6V3>FrWn-S$ub*ogT^Q3W)C?~)2<&TnCk2t2==-#I zhQ+!EdXyGhlrUZ$9sX}NM_d)R8rZYotM=SBL{o^iqeF}EAuN7h>c7{!@V7C%7Ir?F zH+1Obsj)B?V%>+kMJe>beP}N2I>}3vKB0u{<#NEjje+zy4IYQA=ZArgb2^P;Dt-LM zkumNy57+teT^^-Y>{&wtrQ?fNP?!mINHIlX5=#8{=VdwHUifpCXl(QR2dkzd>W>0HHo zKsVEO(D;s0cEOQWWb0$~mCSc$jT32u)?yO&EaHJKGwyU2OndUcyNdIhNjpXOJq`-l zCJtgg1-*f6ypE>Y??Z8Wtg;ek{2qbwf%={G1-hQg+Y3be_M+aCT!m_o@OW){WuLCT zQ$-=)8-25!y<0$yM!j~UALi&SsN_}EeOD^+psXE)eZ{&PA;Useg4p+iCn* zlLW&VE>Adpjd777!g+pOLI}84+GIR!Vu5CeO(12ECq~3hkyR%E<3B zUtwPET{5+au&RgVPV!|nNheouzW}ug3&DRm@DJ$ln^}|Ie+7o+_;pDC6g&ZbT*{qp z#840{;4KQ$SrYGUR@#hKRpIXLu%+w4*|(k z*jcv@>PO}RVyD-m8~#-<%;(s5jz3k|Zz0g1?9izeO(1z(n!0OV&SqXZEMRMi`g_SS z5`Lg>AKMnr6{b92>^)`B1$O$|>3z8oN7tVB>h5c|(&xX$WKzMw_dwF0jf7gSVBsHr z!M1pM5P^oMtRU2Sa}t+6b~72j;qs#&;z5(J?(^gCNehW8ND>d8^ZH{`A3|`}K-0y0 z$6a^fG-~2^BZX@s!qu3nAU7YKj!=-jeLI*@uSC25*hp|wlLcp z)N&IuM^X!^Hm+UMggFRb9i1FtNKkgo56CQ+i5uqXxm`;{FJB4ku)pI`%gkThb$w|y zF7~>?#dCuRUGbb(TzD(saa>Z0fi%J@({-k8MV|@C zC&3U*;jk@|IuI@<&IsLYCRnuK*?y|oEjus^^||EZ@9Vuc!#vG=-xK;!Uu)yh>F=UL zU`hJ(C2uvvN3fk55eyG0e_1Ji1jCcDJOZi36$$UkpowG+jZpBFji)M6j>7Y*O@BeV zxk@?J$R)b*c!h|9zuxAualY(}&Yjn9n1?}pyO+99;EA$K*Zx z!tEIk7NB*j)ATZp{IUb5|5nZK3e7egN2a?&8vUJF|IO%Gnk#}O=a`*(8g`9#K@ELX z)0afn{IwdJxyeTnw}Rnh*45W)8Y|;HSkj`;j=vXrBol%9)wj_i@M_5=T3(TA#DaV4 zAHxy$%fPXed!!=4yTW!J#>ar)x2hG7KP7pzP-~@K4#Yi++4gEE&2}4oRpv9ad#>{0 zUlfZ+0c|^-{plhG<1z-i`6`omOgfdp-HDvMUpkUJE<|AnJt+c%wSa2$EN!>l8##f+ zZ2>V9skrfOL3}vS*eU{N*3Zuni0`)dvdM`Ot80*nqlc|S_|!Oojg$G6-`j89ExMjB z+_prX6%~FvBtHy|FU$EK#@D;!uo;0?V!&O%+3MA@F}>VyV7}uoraFI4?z`=JcmORP zGOPPEkI_9BFwOG=hMm+JSOx}qRNY0U7Zkqf74XzrT5oy*u~b-0%1`EfBt6gIo(3zC zI@Yx*O%+N9`<4e}w4~4V5W_)cAR+3!7JjJ^Y@tY?11TptnIjo;2!Y z+8+E@=)SOg@Gv2Js4EzVFD&}RHzZ{)a1Gt7nwb2|jK8p$|CAA9u>9uD^|k8gXPFei z)P1b>IRhOs)DB<{-qe~^UiG7|@a;{ytROl({(O7nAdFi|+5#EfhCeqTJvFo8(AH*X zdRMO2CTy;wi`xr3A7pe)WN!`zMoJPwo=MKVE(nnjRb2ytwFDOi;m5*{-Qe!%sSoj# z86K=Mj()m>X7N29ZjSu=S14Cv{G-S(0k4WbpK!eGpLSl9j8 z&;Zr^g56D$Rdt_8nf0XS)G@fnp}H*Ht;vRj=gA3k4N9QQGNHp51_YjrL+V~lH&f5gGx5rqvew;GU7y+E_u!F2{#DmoQ8Sp~ zQJ3*$FP^LdnesMyrM`3#0E>JsF@C2T(`|WZyIT8DJ|J?=lV49 zzozpq8zT9FKAr?p5f;xtdfz}K5_Jur_L?Zv1i6;tCr7FEw|K9*>Wh)>N6qKXmxCs0A+*8nTX86t?tP`~lf_4?yj6ND8<&Bjkx z4SVt3;OTIj(G~~sr9Q6D%kFaTHkfT(0!%q&MXEoe4wC*XE>CgZy(1qxB?!7ab$!eB zTO#zI^iIbX#VaL;5#nT9o0SIgk0P&B2Noc0VAfEooFL2=g`0!KP{c#)tDo}^s6xma z`b3GMt}EZ6yFCbH>u=h8A;}s~@BvChF0aQ8IjI5smDh^>L zuG1Xp#@sNMt_UFQFl)vZQyt{TuOV#^VT6_*Q`uw~>u;lm-VxdFnpoc)&vwqhCXu3%IT49xgSC z00hA&p{_g7{xN9WK&|aC9&3BDKVIUhC%`uECRGy`HxO$iHxfZ7(oc2} zpRV&NTm+E>TBw=qIh!uGChcw?z=6`s2sL9DwB;=shl%lC2)L@H&$^(Vx`OJYN%`31 z5Z?dhLH}EI>5H82_VJg9Z@B-;-u;GMMG2ZhzR3;g`;ztFsrb*DMlu1bAR+JAGyI>w z{B|KsF8KfTgTn6FIjH^Mz!@ z^ot6-WHrg5RPWE5^!ix`fBxjLiYqy>$Fz5KaSZ<$Gx^2l6LH-9LBz`jnHT$pFYonC zgik6G`rigC2u=#SH-UH1^6bg?5S7a5z^{TQQa~J4y_3DA#U@Ot4GnU?-}hn6ZFkbt zQJ*dNIN7R@5cbEg)24dud)w=*+i-_353}wEF{#tgL9=WA$I&SXkp>GtjWUu;ks-R992nzW&QP@(rQCwcP2e&A6dlRERb6cp zK2LYSo*KnrGn8Xy67SM~;vP6>^=~bJSV_8wq}5~c59fzBJb53IE+=OGv0r%`EZTN$A6lRmFMv`aI)|A=9Ryw!3@ze2Cn-NyK&LprPSC3b86A)d@ z^4;Xawbn}I$4UiFNksRn4h#~?Y4nFAu*zPy*R zq0-!V9-3Aq+VD_nlH3Yte`Lv*q~c=pGn{5p3d-fl{@qjj077O#CqZSXwq{3D{Y)Sy0bejCqFDRrG2s~gyT9|G0o zRrLdgrW~A!V7Si$L*C@!wn(w(OQe_SyuQ8ee$fo)C70uO`UPj;xwkrM6{t=s;04bHiE=eVtnB;rAi#Tk2mH}fz&~~#N)DE8*~kK zAP8BOw|eB3SH$ybEokRAIOac#?;~s=WiI*p(^d6ATEKGxLQ!ndX{w6UmR+FYtx$*e z`oYoU!S>Q*?FVP{=^?)6*E(lS(cBzfoBrJ_Z6>JbsUG1A42D=d8C(Ir{615#x}Gp= z>BS}B9t&~NRW1s+i|#uc3+@(+Le1JFnsDlEN4X@34aS=CeR!{KEiA&s2=E@r%$qVK zNA{iw13kZOdmOKwIj|=Z;W?RZa;kHL4 zPezM!ugv+VjfZj7sK|@`w$Pn2G?a$&;O19S9xYG!_tI&nv1E5@X4k!Fs+T0@TrIZJUB5fzDY4QSyF5}zv zhIvk=XQWZkO>*2W-tox|hET2z)Yd=rFBaeSw_QY)PHJJdcl-M8B*)1z$w&Mo9a&v4 zoqKe>INT2E43Kbv%=+}jaZ(+cRu4}1xv@4z3w~O4XQ|-BkvFm|F5e zsK~+cR&{CBMF2}34nY?PPS5*EJMgL$*p|!3Hm$Fd)yinrg~|wpI1X&40zg~w=M<7y#De7 zFceX5k}o@Cp7-9W_-5qcT|M2q)%BSNBURt`IC?nA@oX;}e&GJw&~EEtv`~fb+o1yP z{^DQ({;{C)*;ejY%kRPBzh3x`$X7v*0ys;5Vxc5{Lz2G2_y6~M|J%Fz|Jl3<+P6P( zx{PHAF!e`d#C3~D4i-(BtRsjc|HZ^^%h4s$X!DE)ql6|4q{*YBL3@27Xp{9PD|8=B zvMFqiQ~if`|FaeRe&LVqYwyut#%I568D}&W!{wUEIT}ncDgOSwWP0j6O=dq>biv=u zi~QA=vtLUiAj@UAQWRAW_@q0dX^CuRiXo|QApXKzclaa;hGGmrRXH{=Qjqd3iQ=aJ z;gbR}0Es8yr+1t5;rx$P@t@&F)Egk6poUYSlKP9c0s{PWB7WAs|NT$_FW*m;6bL9j zRIq&b%gn)KyaK|{q_?mC;u=!G0RhDq|M)L|xgWvXU(wB2*xTPH^Iz8_AAo>D9-~Ai z?k~64MGXwb^HbQLlB0s)Uxa~xB3sWm`!9Eo!tb{U64wv&ky-=hoW*BCQ61=nd$vk& z*cp`s2}9+of&3NAXRl)38V36$Lu~*hPc1R^aA+i=m`e2gA3$n$Yw7L+i_&G3V6sq0wy{mBS0P3Z2bema=#I80XaI@zj>RmV zlT5OZ`Y}>!9d$Tg_577mPlY1N74lb>IcpP{fSZ24%1G)B&eH}dv-vP&W4@7{dX;6~ zrN@h*db?e<(NMB70L{Lc+yA-+a0ur+U3o0}=Z*uhJOxj^v8e=n?jL7Mw5h4((xbv~ zStN`$IfAZ^t>fg*hiG@o&cmT_HFwUBV{P&jEgjj{$k3d z%|Y*vN!5I##LJUjC`y8N*DkoX1I%u^JSQl_53ScNjt8EJ5#w_)_qD}CtuA$mZRNZ< z-tk6j{oR%nFaDxS=w2&_)bZbE;@CM{qQqov;y!WI#cU0!_F(c#W)NQ;*s!cWM7Obw zCSMekXtqzd3=^U?puvhtgbAj<6>-n-ZPTAx1qW+8X(ooPIqF>M6bad!=+B7DrYw&) ze|G>0Z?F^W^N>TY+sG~VG6ago7;@y|WFDi7?YJgv&h<)3*?bQ1kOIyx-E1-ce`tHl zsJ5c6TewJz6>AH$c%hUQFJ3H2u~OU}in}`@Xeq_r3GVJ5io08Jceh|kzV!L-d&e`z z+rRf;Mv`%I)?PdN?7ik(b53!X4StApgUHX8E`BiY6LY}8Bahsh18MOU6ZUDeY z8LiiT$K-*eH%)mo7at}|DxP{GtopL90!Wy&XjZ)uHF)2SDji`{_0aB+*o(ze<*nQE zmxu)y_ME`K)r6!8I-Dx|bM~o}Rit8&bo552qDxxKL%r){u87T@^)1x#YW^JOB7@iB zmp9*)Q;||rOzYICEYjqW-()ji))AC=GMm*%E&2j*esL*3=nHQH<~^PiOt9Pi{=WLj z4LSSX`I@!5A@K8{i&O_3z#3gqXXn~h=!AlWm$GsbY)9>41V4whv35W?|Em#&JJM*o z`7Rwm1r&Lmfn1fO$Wax|#-7RYnheIhFRYkB*_wEZd4La?6+`Mg(z`9qis-rDSk$=g zLvOq;rp9vo^T%S5%fR|eUWbd^G(I4o+86U_2g*@>gN9AsXT9rlhu15PLuGFL<;yJ& z?wYzEU~6QlTnQC`2dQ5~%-+(2*xMazZ}t22>fl*2eCRB3uVUr?ln=&~YSgJ2+^^(| z*YH+U08@#pg`eK9&ZJ%M%<}YN^VmMRBl~B3uRPikU$N*&E!5h6eCY!=W<`=rp)#Z7 zpT7t7p-3zUDRQ)5lkrF+tN3|p*Ts|j<=P`E#PHYx*xQ`Gu*7Ar8zj@UDHqtHrgVAF za2AuL?`@o&@&vAPFHBl*xIf3)&{Nrpvt24lZ+sb51wwV zJfsyI$+ce!lx8>DA@?}WD;SAG1cO2GO(2%(rU$dU??H}}=Mi}7;R7+hx5rc^Go6bz zkO&`VoD$uy`*PL*%NfILJd!uvkHB84n%plT8(~F@?L8{nf%QDod#_Q(yy>)pqUt@n< z=ce$l&U*ReWM>~G2Za1J;MkhgUby|m=5o(;cQ@yNwA0ob$h&m0L+Xh^+l4ccc$?Iz z&*>2MjCrm+Sni~$CSZEn4UYm#=Og+LT+avpY*7VJ=_1e+3r(c04zK>HlMF)36x9>S z`rf+MtI$CDhgyEUBre;Il@A^qu`$Y^V-?D1so@j0|5m3FN{pa`u6h-4j=Q>Cs1(?& z)YySvP3}V5y-voV()O_rNPWGb0xP>>f1;yQA&2Wnu-0@t7_Fegifb#qru0%OnI^C7VFPPhit4ZfsYWDX z0Ch8GmCq2MvzW}i0oXhkfyh{ODI4{FmE5^UW{l~qoq{&NS4C~EZ)^_mX+c#&cFB`- z{j|&cGC!{N18xG`kgRhN?Unj%+>4c9@StzQYSeU*lIHowA^!7Q^PieeU8Zm5oyH&w zl9qFgdfPv&FS4}mJKH8(Ag?C4s`Y$Mr1|BfgfMSbs((HHW7f zhGXZ-jT^kchITeg8j9=lBS zaU%h*MYoff)R`|NxYv?kZx)SR6ud(ng9yruMAA9+I=P34^p@>5e4zyLg%57~-VJ!K zz6`dY0d8DO&srl>*)-v^cmO^^ac63R_by3`7(8ApsK>9pzk9eeKXA+JE~5mPKn^3> z+<9`x?;6~SRS9+wz-)iCQp`Om>Y|szZOfnzi%9?YnU~XZ=3daD#*p9s{V7}#cQ$3D zGe9_B0Osyy+1&ryG=jA5uQ)P7(El{x_j*_k$w3 zv>(QQf4TmXxI$1{;I`sf6Zb*yix3sO>>H9F>5ueA4G5cZg&?^-DLz&_n0~{;o==Rp8RJb&2Arqcwk#k>tRk6)((`!9MqXFR6 z(DqTnAV;!d7$)VqrXhMIFaAFUCjZ8Khu0x14l1sVJRLggO ziAzr0?zgsd+7_AnfZ(&}gZy%7N2pw01@YaVhMxtKQ6>xf7OCc>bPN{=`+3(#UXt-3 zY<2{~59X@a@?_!_Y`TJ`o)9ENpvUm}4JWf>O0?7*h^FPayd4mBD~l-XGGwv-z+yGa zbRoB@&isLN_MutOHo#JKIml^k;@VG6NS251woTtd?rZZx;~)`ZKhjPuS3gxF8!cdl ziNYMA?^7O(wSjvu8^?cg%yPNI8k3sBlMtQL_w}u=H?oIAuewr}Y-Y;O&#>C_*mIR5 z6x#8ZE(}Koolv0fE=$s+!#cNDBO{3_o&1FO*TYpPq2+=PB=>7PySbvQf_mB90~PQ+ z1xecnXij-c*_xA=}=Rp`!$| zX{Kf%k*+pHcZFVEHEbQ`*K}(SIBfpp5T8?}wcbs_b4Kd%4K*=ESWtIr|FQyfMi*+t zEAI=19*&pir9+?IEPe#aeuU&Sy0MCbNdsoxO2iB2c0|pC> Q=T4Uc;%hj4NdIg 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 B{O|w( diff --git a/go.mod b/go.mod index 131bfee..f47af67 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,7 @@ module github.com/securenative/packman go 1.12 require ( - github.com/google/go-github/v25 v25.0.2 - github.com/logrusorgru/aurora v0.0.0-20190428105938-cea283e61946 // indirect - github.com/mingrammer/cfmt v1.1.0 - github.com/otiai10/copy v1.0.1 - github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 // indirect + github.com/otiai10/copy v1.0.2 github.com/stretchr/testify v1.3.0 - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be - gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 + gopkg.in/src-d/go-git.v4 v4.13.1 ) diff --git a/internal/business/config_service.go b/internal/business/config_service.go new file mode 100644 index 0000000..a37735e --- /dev/null +++ b/internal/business/config_service.go @@ -0,0 +1,30 @@ +package business + +import "github.com/securenative/packman/internal/data" + +type configService struct { + localStorage data.LocalStorage +} + +func NewConfigService(localStorage data.LocalStorage) *configService { + return &configService{localStorage: localStorage} +} + +func (this *configService) SetAuth(username string, password string) error { + if uErr := this.localStorage.Put(string(data.GitUsername), username); uErr != nil { + return uErr + } + + if pErr := this.localStorage.Put(string(data.GitPassword), password); pErr != nil { + return pErr + } + + return nil +} + +func (this *configService) SetDefaultEngine(command string) error { + if err := this.localStorage.Put(string(data.DefaultScript), command); err != nil { + return err + } + return nil +} diff --git a/internal/business/git_project_init.go b/internal/business/git_project_init.go deleted file mode 100644 index d39a624..0000000 --- a/internal/business/git_project_init.go +++ /dev/null @@ -1,105 +0,0 @@ -package business - -import ( - "github.com/mingrammer/cfmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" -) - -type GitProjectInit struct{} - -func NewGitProjectInit() *GitProjectInit { - return &GitProjectInit{} -} - -func (this *GitProjectInit) Init(destPath string) error { - path := packmanPath(destPath) - - cfmt.Info("Creating path ", path, "\n") - if err := os.MkdirAll(path, os.ModePerm); err != nil { - cfmt.Error("Cannot create the following path: ", path, ", ", err.Error(), "\n") - return err - } - - cfmt.Info("Writing the main.go script file", "\n") - scriptPath := filepath.Join(path, "main.go") - if err := this.write(scriptPath, replyScript); err != nil { - cfmt.Error("Cannot create ", scriptPath, ", ", err.Error(), "\n") - return err - } - - cfmt.Info("Writing the go.mod file", "\n") - modPath := filepath.Join(path, "go.mod") - if err := this.write(modPath, modeFile); err != nil { - cfmt.Error("Cannot create ", scriptPath, ", ", err.Error(), "\n") - return err - } - - cfmt.Info("Initialing the git repository", "\n") - gitInit := exec.Command("git", "init") - gitInit.Dir = destPath - if err := gitInit.Run(); err != nil { - cfmt.Error("Cannot init git repository, ", err.Error(), "\n") - return err - } - - gitAdd := exec.Command("git", "add", ".") - gitAdd.Dir = destPath - if err := gitAdd.Run(); err != nil { - cfmt.Error("Failed to add untracked files to the git repository, ", err.Error(), "\n") - return err - } - - cfmt.Info("Creating the first commit", "\n") - gitCommit := exec.Command("git", "commit", "-m", `"First Commit"`) - gitCommit.Dir = destPath - if err := gitCommit.Run(); err != nil { - cfmt.Error("Failed to commit changes, ", err.Error(), "\n") - return err - } - - cfmt.Success("Packman package created successfully!") - return nil -} - -func (this *GitProjectInit) write(filePath string, content string) error { - return ioutil.WriteFile(filePath, []byte(content), os.ModePerm) -} - -const replyScript = `package main - -import ( - "os" - pm "github.com/securenative/packman/pkg" -) - -type MyData struct { - PackageName string - ProjectPath string - Flags map[string]string -} - -func main() { - // flags sent by packman's driver will be forwarded to here: - flags := pm.ParseFlags(os.Args[3:]) - - // Build your own model to represent the templating you need - model := MyData{ - PackageName: flags[pm.PackageNameFlag], - ProjectPath: flags[pm.PackagePathFlag], - Flags: flags, - } - - // Reply to packman's driver: - pm.Reply(model) -} -` - -const modeFile = `module packmanScript - -require ( - github.com/securenative/packman latest -) -` diff --git a/internal/business/git_project_init_test.go b/internal/business/git_project_init_test.go deleted file mode 100644 index 1a614de..0000000 --- a/internal/business/git_project_init_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package business - -import ( - "github.com/stretchr/testify/assert" - "io/ioutil" - "os" - "path/filepath" - "testing" -) - -func TestPackmanProjectInit_Init(t *testing.T) { - - init := NewGitProjectInit() - - tempDir := filepath.Join(os.TempDir(), "packtest") - if err := os.MkdirAll(tempDir, os.ModePerm); err != nil { - t.Fail() - } - - err := init.Init(filepath.Join(tempDir, "my_pkg")) - assert.Nil(t, err) - - bytes, err := ioutil.ReadFile(filepath.Join(tempDir, "my_pkg", "packman", "main.go")) - assert.Nil(t, err) - - assert.Equal(t, replyScript, string(bytes)) - _ = os.RemoveAll(tempDir) - -} diff --git a/internal/business/manifest.go b/internal/business/manifest.go index 3ea237a..f7db3a3 100644 --- a/internal/business/manifest.go +++ b/internal/business/manifest.go @@ -1,23 +1,12 @@ package business -import "path/filepath" - -// Encapsulates all logic required for packing a project -type Packer interface { - Pack(name string, sourcePath string) error -} - -// Encapsulates all logic required for unpacking a project -type Unpacker interface { - DryUnpack(sourcePath string, destPath string, args []string) error - Unpack(name string, destPath string, args []string) error -} - -// Encapsulates all logic required for initializing new package -type ProjectInit interface { - Init(destPath string) error +type TemplatingService interface { + Pack(remoteUrl string, packagePath string) error + Unpack(remoteUtl string, packagePath string, flags map[string]string) error + Render(templatePath string, packagePath string, flags map[string]string) error } -func packmanPath(destPath string) string { - return filepath.Join(destPath, "packman") +type ConfigService interface { + SetAuth(username string, password string) error + SetDefaultEngine(command string) error } diff --git a/internal/business/packer.go b/internal/business/packer.go deleted file mode 100644 index 5ebf60f..0000000 --- a/internal/business/packer.go +++ /dev/null @@ -1,15 +0,0 @@ -package business - -import "github.com/securenative/packman/internal/data" - -type PackmanPacker struct { - backend data.Backend -} - -func NewPackmanPacker(backend data.Backend) *PackmanPacker { - return &PackmanPacker{backend: backend} -} - -func (this *PackmanPacker) Pack(name string, sourcePath string) error { - return this.backend.Push(name, sourcePath) -} diff --git a/internal/business/template_service.go b/internal/business/template_service.go new file mode 100644 index 0000000..d990ba7 --- /dev/null +++ b/internal/business/template_service.go @@ -0,0 +1,83 @@ +package business + +import ( + "fmt" + copy2 "github.com/otiai10/copy" + "github.com/securenative/packman/internal/data" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +type templateService struct { + remoteStorage data.RemoteStorage + scriptEngine data.ScriptEngine + templateEngine data.TemplateEngine +} + +func NewTemplateService(remoteStorage data.RemoteStorage, scriptEngine data.ScriptEngine, templateEngine data.TemplateEngine) *templateService { + return &templateService{remoteStorage: remoteStorage, scriptEngine: scriptEngine, templateEngine: templateEngine} +} + +func (this *templateService) Render(templatePath string, packagePath string, flags map[string]string) error { + if templatePath != packagePath { + _ = os.RemoveAll(packagePath) + if err := copy2.Copy(templatePath, packagePath); err != nil { + return err + } + } + + scriptPath, err := toScriptPath(packagePath) + if err != nil { + return err + } + + scriptData, err := this.scriptEngine.Run(scriptPath, flags) + if err != nil { + return err + } + + err = filepath.Walk(packagePath, func(path string, info os.FileInfo, err error) error { + ierr := this.templateEngine.Run(path, scriptData) + if ierr != nil { + return ierr + } + return nil + }) + + return err +} + +func (this *templateService) Pack(remoteUrl string, packagePath string) error { + return this.remoteStorage.Push(packagePath, remoteUrl) +} + +func (this *templateService) Unpack(remoteUtl string, packagePath string, flags map[string]string) error { + err := this.remoteStorage.Pull(remoteUtl, packagePath) + if err != nil { + return err + } + + err = os.RemoveAll(filepath.Join(packagePath, ".git")) + if err != nil { + return err + } + + return this.Render(packagePath, packagePath, flags) +} + +func toScriptPath(prefix string) (string, error) { + packmanPath := filepath.Join(prefix, "packman") + packmanDir, err := ioutil.ReadDir(packmanPath) + if err != nil { + return "", err + } + + for _, file := range packmanDir { + if !file.IsDir() && strings.HasPrefix(file.Name(), "main") { + return filepath.Join(packmanPath, file.Name()), nil + } + } + return "", fmt.Errorf("in order for packman to work you must have a 'main.*' file within your packman folder") +} diff --git a/internal/business/template_service_test.go b/internal/business/template_service_test.go new file mode 100644 index 0000000..a32ff33 --- /dev/null +++ b/internal/business/template_service_test.go @@ -0,0 +1,95 @@ +package business + +import ( + "errors" + copy2 "github.com/otiai10/copy" + "github.com/securenative/packman/internal/data" + "github.com/securenative/packman/internal/etc" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +var underTest = NewTemplateService( + data.NewGitRemoteStorage(&envLocalStorage{}), + data.NewGenericScriptEngine("go run"), + data.NewGolangTemplateEngine(), +) + +func TestTemplateService_Unpack(t *testing.T) { + skipOnMissingEnv(t) + gitPath := filepath.Join(os.TempDir(), "packman_service_test") + defer os.RemoveAll(gitPath) + + err := underTest.Unpack("https://github.com/matang28/packman_git_test.git", gitPath, + map[string]string{"Key": "My Key", "Value": "My Value"}, + ) + assert.Nil(t, err) + + content, err := etc.ReadFile(filepath.Join(gitPath, "template.txt")) + assert.Nil(t, err) + assert.EqualValues(t, "My Key My Value\n", content) +} + +func TestTemplateService_Render(t *testing.T) { + skipOnMissingEnv(t) + gitPath := filepath.Join(os.TempDir(), "packman_service_test") + defer os.RemoveAll(gitPath) + + git := data.NewGitRemoteStorage(&envLocalStorage{}) + err := git.Pull("https://github.com/matang28/packman_git_test.git", gitPath) + assert.Nil(t, err) + + defer os.RemoveAll(gitPath + "-rendered") + err = underTest.Render(gitPath, gitPath+"-rendered", + map[string]string{"Key": "My Key", "Value": "My Value"}, + ) + assert.Nil(t, err) + + content, err := etc.ReadFile(filepath.Join(gitPath+"-rendered", "template.txt")) + assert.Nil(t, err) + assert.EqualValues(t, "My Key My Value\n", content) +} + +func TestTemplateService_Pack(t *testing.T) { + skipOnMissingEnv(t) + gitPath := filepath.Join(os.TempDir(), "packman_service_test") + git := data.NewGitRemoteStorage(&envLocalStorage{}) + defer os.RemoveAll(gitPath) + + err := git.Pull("https://github.com/matang28/packman_git_test.git", gitPath) + assert.Nil(t, err) + + err = copy2.Copy(gitPath, gitPath+"-temp") + assert.Nil(t, err) + + err = underTest.Pack("https://github.com/matang28/packman_git_test.git", gitPath+"-temp") + assert.Nil(t, err) +} + +type envLocalStorage struct{} + +func (this *envLocalStorage) Put(key, value string) error { + return nil +} + +func (this *envLocalStorage) Get(key string) (string, error) { + switch key { + case string(data.GitUsername): + return os.Getenv("USERNAME"), nil + case string(data.GitPassword): + return os.Getenv("PASSWORD"), nil + } + return "", errors.New("") +} + +func skipOnMissingEnv(t *testing.T) { + if os.Getenv("USERNAME") == "" { + t.Skip("Skipped because no env variable called 'USERNAME' exists") + } + + if os.Getenv("PASSWORD") == "" { + t.Skip("Skipped because no env variable called 'PASSWORD' exists") + } +} diff --git a/internal/business/unpacker.go b/internal/business/unpacker.go deleted file mode 100644 index fcded4b..0000000 --- a/internal/business/unpacker.go +++ /dev/null @@ -1,133 +0,0 @@ -package business - -import ( - "github.com/mingrammer/cfmt" - . "github.com/otiai10/copy" - "github.com/securenative/packman/internal/data" - "github.com/securenative/packman/pkg" - "io/ioutil" - "os" - "path/filepath" - "strings" -) - -type PackmanUnpacker struct { - backend data.Backend - templateEngine data.TemplateEngine - scriptEngine data.ScriptEngine -} - -func NewPackmanUnpacker(backend data.Backend, templateEngine data.TemplateEngine, scriptEngine data.ScriptEngine) *PackmanUnpacker { - return &PackmanUnpacker{backend: backend, templateEngine: templateEngine, scriptEngine: scriptEngine} -} - -func (this *PackmanUnpacker) DryUnpack(sourcePath string, destPath string, args []string) error { - if err := os.RemoveAll(destPath); err != nil { - return err - } - - if err := Copy(sourcePath, destPath); err != nil { - return err - } - - if err := this.render(destPath, args); err != nil { - return err - } - - if err := this.clean(destPath); err != nil { - return err - } - - return nil -} - -func (this *PackmanUnpacker) Unpack(name string, destPath string, args []string) error { - if err := os.MkdirAll(destPath, os.ModePerm); err != nil { - return err - } - - if err := this.backend.Pull(name, destPath); err != nil { - return err - } - - if err := this.render(destPath, args); err != nil { - return err - } - - if err := this.clean(destPath); err != nil { - return err - } - - return nil -} - -func (this *PackmanUnpacker) clean(destPath string) error { - if err := os.RemoveAll(filepath.Join(destPath, ".git")); err != nil { - return err - } - - if err := os.RemoveAll(filepath.Join(destPath, "packman")); err != nil { - return err - } - - return nil -} - -func (this *PackmanUnpacker) render(destPath string, args []string) error { - _ = os.Setenv("PACKMAN_PROJECT", packmanPath(destPath)) - - scriptFile := filepath.Join(packmanPath(destPath), "main.go") - if err := this.scriptEngine.Run(scriptFile, args); err != nil { - return err - } - - dataModel, err := pkg.ReadReply(packmanPath(destPath)) - if err != nil { - return err - } - - err = filepath.Walk(destPath, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && shouldRender(path) { - cfmt.Infof("Rendering %s\n", path) - content, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - rendered, err := this.templateEngine.Render(string(content), dataModel) - if err != nil { - return err - } - - if err = ioutil.WriteFile(path, []byte(rendered), os.ModePerm); err != nil { - return err - } - } - return nil - }) - if err != nil { - return err - } - - return nil -} - -func shouldRender(path string) bool { - if strings.Contains(path, ".git") { - return false - } - - if strings.Contains(path, ".idea") { - return false - } - - if strings.Contains(path, ".vscode") { - return false - } - - if strings.Contains(path, "packman") { - return false - } - - return true -} diff --git a/internal/business/unpacker_test.go b/internal/business/unpacker_test.go deleted file mode 100644 index 3ab6bd9..0000000 --- a/internal/business/unpacker_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package business - -import ( - "github.com/securenative/packman/internal/data" - "github.com/securenative/packman/pkg" - "github.com/stretchr/testify/assert" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" -) - -const expected = ` -package my-pkg -func main() { -fmt.Println("Hello") -fmt.Println("World") -fmt.Println("my-pkg") -}` - -func TestPackmanUnpacker_Unpack(t *testing.T) { - replacer := strings.NewReplacer("\n", "", "\t", "") - unpacker := NewPackmanUnpacker(&mockBackend{}, data.NewGoTemplateEngine(), data.NewGoScriptEngine()) - - path := filepath.Join(os.TempDir(), "packtest", "unpack") - err := unpacker.Unpack("my-pkg", path, []string{"--", pkg.PackageNameFlag, "my-pkg", "-a", "Hello", "-b", "World"}) - assert.Nil(t, err) - - bytes, err := ioutil.ReadFile(filepath.Join(path, "testfile.go")) - assert.Nil(t, err) - assert.Equal(t, replacer.Replace(expected), replacer.Replace(string(bytes))) - - _ = os.RemoveAll(path) -} - -type mockBackend struct { -} - -func (mockBackend) Push(name string, source string) error { - return nil -} - -func (mockBackend) Pull(name string, destination string) error { - init := NewGitProjectInit() - - if err := init.Init(destination); err != nil { - return err - } - - content := ` -package {{{ .PackageName }}} - -func main() { - {{{ range .Flags }}} - fmt.Println("{{{ . }}}") - {{{ end }}} -} -` - if err := ioutil.WriteFile(filepath.Join(destination, "testfile.go"), []byte(content), os.ModePerm); err != nil { - return err - } - - return nil -} - -func (mockBackend) ConfigKey() string { - return "" -} diff --git a/internal/data/file_local_storage.go b/internal/data/file_local_storage.go new file mode 100644 index 0000000..36b5bd7 --- /dev/null +++ b/internal/data/file_local_storage.go @@ -0,0 +1,57 @@ +package data + +import ( + "encoding/json" + "fmt" + "github.com/securenative/packman/internal/etc" +) + +type fileLocalStorage struct { + storage map[string]string + filePath string +} + +func NewFileLocalStorage(filePath string) (LocalStorage, error) { + m, err := file2Map(filePath) + if err != nil { + return nil, err + } + return &fileLocalStorage{filePath: filePath, storage: m}, nil +} + +func (this *fileLocalStorage) Put(key, value string) error { + this.storage[key] = value + return this.flush() +} + +func (this *fileLocalStorage) Get(key string) (string, error) { + value, ok := this.storage[key] + if !ok { + return "", fmt.Errorf("failed to find key: %s in local storage", key) + } + return value, nil +} + +func (this *fileLocalStorage) flush() error { + err := etc.WriteFile(this.filePath, this.storage, etc.JsonEncoder) + return err +} + +func file2Map(filePath string) (map[string]string, error) { + if !etc.FileExists(filePath) { + return make(map[string]string), nil + } + + content, err := etc.ReadFile(filePath) + if err != nil { + return nil, err + } + + var out map[string]string + err = json.Unmarshal([]byte(content), &out) + if err != nil { + return nil, err + } + + return out, nil +} diff --git a/internal/data/file_local_storage_test.go b/internal/data/file_local_storage_test.go new file mode 100644 index 0000000..5fe6da2 --- /dev/null +++ b/internal/data/file_local_storage_test.go @@ -0,0 +1,80 @@ +package data + +import ( + "github.com/securenative/packman/internal/etc" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestFileLocalStorage_Init_NewFile(t *testing.T) { + filePath := filepath.Join(os.TempDir(), "packman_local_storage_test.json") + key := "somekey" + value := "somevalue" + + defer os.Remove(filePath) + s, err := NewFileLocalStorage(filePath) + assert.NotNil(t, s) + assert.Nil(t, err) + assert.False(t, etc.FileExists(filePath)) + + _, err = s.Get(key) + assert.NotNil(t, err) + + err = s.Put(key, value) + assert.Nil(t, err) + assert.FileExists(t, filePath) + + storedValue, err := s.Get(key) + assert.Nil(t, err) + assert.EqualValues(t, value, storedValue) + + _, err = s.Get(key + "123") + assert.NotNil(t, err) +} + +func TestFileLocalStorage_Init_ExistingValidFile(t *testing.T) { + filePath := filepath.Join(os.TempDir(), "packman_local_storage_test_valid.json") + key := "somekey" + value := "somevalue" + + defer os.Remove(filePath) + err := etc.WriteFile(filePath, map[string]interface{}{key: value}, etc.JsonEncoder) + if err != nil { + assert.FailNow(t, err.Error()) + } + + s, err := NewFileLocalStorage(filePath) + assert.NotNil(t, s) + assert.Nil(t, err) + assert.True(t, etc.FileExists(filePath)) + + storedValue, err := s.Get(key) + assert.Nil(t, err) + assert.EqualValues(t, value, storedValue) + + newKey := "new key" + newValue := "new value" + err = s.Put(newKey, newValue) + assert.Nil(t, err) + + storedValue, err = s.Get(newKey) + assert.Nil(t, err) + assert.EqualValues(t, newValue, storedValue) +} + +func TestFileLocalStorage_Init_ExistingInvalidFile(t *testing.T) { + filePath := filepath.Join(os.TempDir(), "packman_local_storage_test_invalid.json") + + defer os.Remove(filePath) + err := etc.WriteFile(filePath, "not json", etc.StringEncoder) + if err != nil { + assert.FailNow(t, err.Error()) + } + + s, err := NewFileLocalStorage(filePath) + assert.Nil(t, s) + assert.NotNil(t, err) + assert.True(t, etc.FileExists(filePath)) +} diff --git a/internal/data/generic_script_engine.go b/internal/data/generic_script_engine.go new file mode 100644 index 0000000..ba242a8 --- /dev/null +++ b/internal/data/generic_script_engine.go @@ -0,0 +1,76 @@ +package data + +import ( + "encoding/json" + "fmt" + "github.com/securenative/packman/internal/etc" + "os" + "os/exec" + "path/filepath" + "strings" +) + +type genericScriptEngine struct { + command string +} + +func NewGenericScriptEngine(command string) *genericScriptEngine { + return &genericScriptEngine{command: command} +} + +func (this *genericScriptEngine) Run(scriptPath string, flags map[string]string) (map[string]interface{}, error) { + flagsFile := pwdPath(scriptPath, "~flags.json") + replyFile := pwdPath(scriptPath, "~reply.json") + err := etc.WriteFile(flagsFile, flags, etc.JsonEncoder) + + mainCommand, args, err := splitCommand(this.command) + if err != nil { + return nil, err + } + + var cmdArgs []string + cmdArgs = append(cmdArgs, args...) + cmdArgs = append(cmdArgs, scriptPath) + cmdArgs = append(cmdArgs, flagsFile) + cmdArgs = append(cmdArgs, replyFile) + + cmd := exec.Command(mainCommand, cmdArgs...) + etc.PrintInfo(fmt.Sprintf("Running %s script file with: '%s'\n", scriptPath, cmd.String())) + result, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + etc.PrintResponse(string(result) + "\n") + etc.PrintSuccess("Script was run successfully.\n") + etc.PrintInfo("Trying to read reply file: %s...\n", replyFile) + content, err := etc.ReadFile(replyFile) + if err != nil { + return nil, err + } + + var out map[string]interface{} + err = json.Unmarshal([]byte(content), &out) + if err != nil { + return nil, err + } + + os.Remove(flagsFile) + os.Remove(replyFile) + + return out, nil +} + +func pwdPath(scriptPath string, newName string) string { + fileName := filepath.Base(scriptPath) + scriptFolder := strings.ReplaceAll(scriptPath, fileName, "") + return filepath.Join(scriptFolder, newName) +} + +func splitCommand(command string) (string, []string, error) { + parts := strings.Split(command, " ") + if parts != nil && len(parts) > 0 { + return parts[0], parts[1:], nil + } + return "", nil, fmt.Errorf("cannot parse command %s, the command syntax should be as follows: 'commnad arg1 arg2 arg3 ...'", command) +} diff --git a/internal/data/generic_script_engine_test.go b/internal/data/generic_script_engine_test.go new file mode 100644 index 0000000..f0de4d2 --- /dev/null +++ b/internal/data/generic_script_engine_test.go @@ -0,0 +1,68 @@ +package data + +import ( + "github.com/securenative/packman/internal/etc" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestGenericScriptEngine_Run(t *testing.T) { + filePath := filepath.Join(os.TempDir(), "packman_script_engine.go") + defer os.Remove(filePath) + err := etc.WriteFile(filePath, script, etc.StringEncoder) + if err != nil { + assert.FailNow(t, err.Error()) + } + + flags := map[string]string{ + "flag1": "flag1", + "flag2": "flag2", + "flag3": "flag3", + } + + s := NewGenericScriptEngine("go run") + reply, err := s.Run(filePath, flags) + assert.Nil(t, err) + for k, v := range flags { + assert.EqualValues(t, v, reply[k]) + } +} + +const script = ` +package main + +import ( + "encoding/json" + "io/ioutil" + "os" +) + +func main() { + if len(os.Args) != 3 { + panic("the script requires exactly 3 arguments") + } + + flagsPath := os.Args[1] + replyPath := os.Args[2] + + bytes, err := ioutil.ReadFile(flagsPath) + if err != nil { + panic(err) + } + + var m map[string]interface{} + err = json.Unmarshal(bytes, &m) + if err != nil { + panic(err) + } + + err = ioutil.WriteFile(replyPath, bytes, os.ModePerm) + if err != nil { + panic(err) + } + + os.Exit(0) +} +` diff --git a/internal/data/git_remote_storage.go b/internal/data/git_remote_storage.go new file mode 100644 index 0000000..32f40dd --- /dev/null +++ b/internal/data/git_remote_storage.go @@ -0,0 +1,84 @@ +package data + +import ( + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + "os" + "time" +) + +type gitRemoteStorage struct { + localStorage LocalStorage +} + +func NewGitRemoteStorage(localStorage LocalStorage) *gitRemoteStorage { + return &gitRemoteStorage{localStorage: localStorage} +} + +func (this *gitRemoteStorage) getAuth() transport.AuthMethod { + username, _ := this.localStorage.Get(string(GitUsername)) + password, _ := this.localStorage.Get(string(GitPassword)) + + if username == "" || password == "" { + return nil + } + + return &http.BasicAuth{ + Username: username, + Password: password, + } +} + +func (this *gitRemoteStorage) Pull(remotePath string, localPath string) error { + _, err := git.PlainClone(localPath, false, &git.CloneOptions{ + URL: remotePath, + Auth: this.getAuth(), + Progress: os.Stdout, + }) + if err != nil { + return err + } + + return nil +} + +func (this *gitRemoteStorage) Push(localPath string, remotePath string) error { + repo, err := git.PlainOpen(localPath) + if err != nil { + return err + } + + w, err := repo.Worktree() + if err != nil { + return err + } + + if err = w.AddGlob("."); err != nil { + return err + } + + _, err = w.Commit("Pushed by packman", &git.CommitOptions{ + All: true, + Author: &object.Signature{ + Name: "packman", + Email: "", + When: time.Now(), + }, + }) + if err != nil { + return err + } + + err = repo.Push(&git.PushOptions{ + RemoteName: "origin", + Auth: this.getAuth(), + Progress: os.Stdout, + }) + if err != nil { + return err + } + + return nil +} diff --git a/internal/data/git_remote_storage_test.go b/internal/data/git_remote_storage_test.go new file mode 100644 index 0000000..5dae7b2 --- /dev/null +++ b/internal/data/git_remote_storage_test.go @@ -0,0 +1,105 @@ +package data + +import ( + "errors" + "fmt" + "github.com/securenative/packman/internal/etc" + "github.com/stretchr/testify/assert" + "math/rand" + "os" + "path/filepath" + "testing" +) + +func TestGitRemoteStorage_PullWithoutAuth(t *testing.T) { + gitPath := filepath.Join(os.TempDir(), "packman_git_test") + git := NewGitRemoteStorage(&emptyLocalStorage{}) + + err := git.Pull("https://github.com/matang28/packman_git_test.git", gitPath) + assert.NotNil(t, err) +} + +func TestGitRemoteStorage_PullWithAuth(t *testing.T) { + skipOnMissingEnv(t) + gitPath := filepath.Join(os.TempDir(), "packman_git_test") + git := NewGitRemoteStorage(&envLocalStorage{}) + + defer os.RemoveAll(gitPath) + err := git.Pull("https://github.com/matang28/packman_git_test.git", gitPath) + assert.Nil(t, err) + + content, err := etc.ReadFile(filepath.Join(gitPath, "README.md")) + assert.Nil(t, err) + assert.EqualValues(t, "This is a test\n", content) +} + +func TestGitRemoteStorage_Push(t *testing.T) { + skipOnMissingEnv(t) + gitPath := filepath.Join(os.TempDir(), "packman_git_test") + customFilePath := filepath.Join(gitPath, fmt.Sprintf("file-%d", rand.Int())) + git := NewGitRemoteStorage(&envLocalStorage{}) + + defer os.RemoveAll(gitPath) + + clone(git, gitPath, t) + err := etc.WriteFile(customFilePath, "dummy data", etc.StringEncoder) + assert.Nil(t, err) + + err = git.Push(gitPath, "https://github.com/matang28/packman_git_test.git") + assert.Nil(t, err) + + os.RemoveAll(gitPath) + clone(git, gitPath, t) + assert.FileExists(t, customFilePath) + + err = os.Remove(customFilePath) + if err != nil { + assert.FailNow(t, err.Error()) + } + + err = git.Push(gitPath, "https://github.com/matang28/packman_git_test.git") + assert.Nil(t, err) +} + +func clone(git *gitRemoteStorage, gitPath string, t *testing.T) { + err := git.Pull("https://github.com/matang28/packman_git_test.git", gitPath) + if err != nil { + assert.FailNow(t, err.Error()) + } +} + +func skipOnMissingEnv(t *testing.T) { + if os.Getenv("USERNAME") == "" { + t.Skip("Skipped because no env variable called 'USERNAME' exists") + } + + if os.Getenv("PASSWORD") == "" { + t.Skip("Skipped because no env variable called 'PASSWORD' exists") + } +} + +type envLocalStorage struct{} + +func (this *envLocalStorage) Put(key, value string) error { + return nil +} + +func (this *envLocalStorage) Get(key string) (string, error) { + switch key { + case string(GitUsername): + return os.Getenv("USERNAME"), nil + case string(GitPassword): + return os.Getenv("PASSWORD"), nil + } + return "", errors.New("") +} + +type emptyLocalStorage struct{} + +func (this *emptyLocalStorage) Put(key, value string) error { + return nil +} + +func (this *emptyLocalStorage) Get(key string) (string, error) { + return "", errors.New("") +} diff --git a/internal/data/github_backend.go b/internal/data/github_backend.go deleted file mode 100644 index afba437..0000000 --- a/internal/data/github_backend.go +++ /dev/null @@ -1,142 +0,0 @@ -package data - -import ( - "context" - "errors" - "fmt" - "github.com/google/go-github/v25/github" - "golang.org/x/oauth2" - "os/exec" - "strings" -) - -type GithubConfig struct { - Username string - Token string - PrivatePush bool -} - -type GithubBackend struct { - cfg *GithubConfig - configLoader ConfigStore - client *github.Client -} - -func NewGithubBackend(configLoader ConfigStore) *GithubBackend { - return &GithubBackend{configLoader: configLoader} -} - -func (this *GithubBackend) Push(name string, source string) error { - gh, cfg, err := this.loadClient() - if err != nil { - return err - } - - splitName := strings.Split(name, "/") - if len(splitName) != 2 { - return errors.New("github repository name should be formatted as /") - } - - err = this.getOrCreateRepository(gh, splitName, cfg) - if err != nil { - return err - } - - gitAdd := exec.Command("git", "add", ".") - gitAdd.Dir = source - if err := gitAdd.Run(); err != nil { - return err - } - - gitListFiles := exec.Command("git", "ls-files") - gitListFiles.Dir = source - changedFiles, err := gitListFiles.Output() - if err != nil { - return err - } - - gitCommit := exec.Command("git", "commit", "-m", string(changedFiles)) - gitCommit.Dir = source - if err := gitCommit.Run(); err != nil { - return err - } - - clearRemote := exec.Command("git", "remote", "rm", "origin") - clearRemote.Dir = source - _ = clearRemote.Run() - - addRemote := exec.Command("git", "remote", "add", "origin", githubUrl(name)) - addRemote.Dir = source - err = addRemote.Run() - if err != nil { - return err - } - - push := exec.Command("git", "push", "-u", "origin", "master") - push.Dir = source - err = push.Run() - return err -} - -func (this *GithubBackend) Pull(name string, destination string) error { - url := githubUrl(name) - cmd := exec.Command("git", "clone", url, destination) - return cmd.Run() -} - -func (this *GithubBackend) ConfigKey() string { - return "github" -} - -func (this *GithubBackend) getOrCreateRepository(gh *github.Client, splitName []string, cfg *GithubConfig) error { - - _, _, err := gh.Repositories.Get(context.Background(), splitName[0], splitName[1]) - if err != nil { - err = this.createRepository(splitName, cfg, gh) - } - - return err -} - -func (this *GithubBackend) createRepository(splitName []string, cfg *GithubConfig, gh *github.Client) error { - repo := &github.Repository{ - Name: &splitName[1], - Private: &cfg.PrivatePush, - } - _, _, err := gh.Repositories.Create(context.Background(), splitName[0], repo) - return err -} - -func githubUrl(name string) string { - url := fmt.Sprintf("https://github.com/%s.git", name) - return url -} - -func (this *GithubBackend) loadClient() (*github.Client, *GithubConfig, error) { - if this.client == nil { - cfg, err := this.loadConfig() - if err != nil { - return nil, nil, err - } - - ctx := context.Background() - ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: cfg.Token}) - tc := oauth2.NewClient(ctx, ts) - this.client = github.NewClient(tc) - } - - return this.client, this.cfg, nil -} - -func (this *GithubBackend) loadConfig() (*GithubConfig, error) { - if this.cfg == nil { - var temp GithubConfig - found := this.configLoader.Get(this.ConfigKey(), &temp) - if !found { - return nil, errors.New("cannot find github configuration") - } - this.cfg = &temp - } - - return this.cfg, nil -} diff --git a/internal/data/go_script_engine.go b/internal/data/go_script_engine.go deleted file mode 100644 index 8dfcae5..0000000 --- a/internal/data/go_script_engine.go +++ /dev/null @@ -1,34 +0,0 @@ -package data - -import ( - "fmt" - "os/exec" -) - -type GoScriptEngine struct { -} - -func NewGoScriptEngine() *GoScriptEngine { - return &GoScriptEngine{} -} - -func (this *GoScriptEngine) Run(scriptFile string, args []string) error { - var cmdArgs []string - cmdArgs = append(cmdArgs, "run") - cmdArgs = append(cmdArgs, scriptFile) - cmdArgs = append(cmdArgs, "--") - cmdArgs = append(cmdArgs, args...) - - fmt.Println(fmt.Sprintf("Running main.go with %v", cmdArgs)) - - cmd := exec.Command("go", cmdArgs...) - //cmd.Dir = filepath.Dir(scriptFile) - result, err := cmd.CombinedOutput() - if err != nil { - return err - } - - fmt.Println(string(result)) - - return nil -} diff --git a/internal/data/go_script_engine_test.go b/internal/data/go_script_engine_test.go deleted file mode 100644 index b8cfdf9..0000000 --- a/internal/data/go_script_engine_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package data - -import ( - "fmt" - "github.com/stretchr/testify/assert" - "io/ioutil" - "os" - "path/filepath" - "testing" -) - -func setup() { - path := tempPath() - - status0 := ` - package main - - import "os" - - func main() { - os.Exit(0) - } - ` - - status1 := ` - package main - - import "os" - - func main() { - os.Exit(1) - } - ` - err := ioutil.WriteFile(filepath.Join(path, string(os.PathSeparator), "status_0.go"), []byte(status0), os.ModePerm) - if err != nil { - panic(err.Error()) - } - - err = ioutil.WriteFile(filepath.Join(path, string(os.PathSeparator), "status_1.go"), []byte(status1), os.ModePerm) - if err != nil { - panic(err.Error()) - } -} - -func tempPath() string { - path := fmt.Sprintf("%s%c%s", os.TempDir(), os.PathSeparator, "packtest") - return path -} - -func TestGoScriptEngine_Run(t *testing.T) { - setup() - runner := NewGoScriptEngine() - - err := runner.Run(filepath.Join(tempPath(), string(os.PathSeparator), "status_0.go"), []string{}) - assert.Nil(t, err) - - err = runner.Run(filepath.Join(tempPath(), string(os.PathSeparator), "status_1.go"), []string{}) - assert.NotNil(t, err) -} diff --git a/internal/data/go_template_engine.go b/internal/data/go_template_engine.go deleted file mode 100644 index dba0469..0000000 --- a/internal/data/go_template_engine.go +++ /dev/null @@ -1,30 +0,0 @@ -package data - -import ( - "bytes" - "text/template" -) - -type GoTemplateEngine struct { -} - -func NewGoTemplateEngine() *GoTemplateEngine { - return &GoTemplateEngine{} -} - -func (this *GoTemplateEngine) Render(templateText string, data interface{}) (string, error) { - var out bytes.Buffer - t := template.New("template") - t.Delims("{{{", "}}}") - tree, err := t.Parse(templateText) - if err != nil { - return "", err - } - - err = tree.Execute(&out, data) - if err != nil { - return "", err - } - - return out.String(), nil -} diff --git a/internal/data/go_template_engine_test.go b/internal/data/go_template_engine_test.go deleted file mode 100644 index 0b97c10..0000000 --- a/internal/data/go_template_engine_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package data - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestGoTemplateEngine_Render(t *testing.T) { - - template := `Helo {{{ .Name }}} this is test num: {{{ .Number }}}` - expected := `Helo World this is test num: 1` - type data struct { - Name string - Number int - } - - engine := NewGoTemplateEngine() - rendered, err := engine.Render(template, data{Name: "World", Number: 1}) - - assert.Nil(t, err) - assert.Equal(t, expected, rendered) -} - -func TestGoTemplateEngine_Render_Missing_Arg(t *testing.T) { - - template := `Helo {{{ .Name }}} this is test num: {{{ .Number }}}` - type data struct { - Name string - } - - engine := NewGoTemplateEngine() - rendered, err := engine.Render(template, data{Name: "World"}) - - assert.NotNil(t, err) - assert.Equal(t, "", rendered) -} diff --git a/internal/data/golang_template_engine.go b/internal/data/golang_template_engine.go new file mode 100644 index 0000000..90c4d0e --- /dev/null +++ b/internal/data/golang_template_engine.go @@ -0,0 +1,41 @@ +package data + +import ( + "bytes" + "github.com/securenative/packman/internal/etc" + "text/template" +) + +type golangTemplateEngine struct { +} + +func NewGolangTemplateEngine() *golangTemplateEngine { + return &golangTemplateEngine{} +} + +func (this *golangTemplateEngine) Run(filePath string, data map[string]interface{}) error { + var out bytes.Buffer + t := template.New("template") + t.Delims("{{{", "}}}") + + etc.PrintInfo("Trying to template the following file: %s...", filePath) + templateText, err := etc.ReadFile(filePath) + + tree, err := t.Parse(templateText) + if err != nil { + return err + } + + err = tree.Execute(&out, data) + if err != nil { + return err + } + + err = etc.WriteFile(filePath, out.String(), etc.StringEncoder) + if err != nil { + return nil + } + + etc.PrintSuccess(" OK\n") + return nil +} diff --git a/internal/data/golang_template_engine_test.go b/internal/data/golang_template_engine_test.go new file mode 100644 index 0000000..996e35e --- /dev/null +++ b/internal/data/golang_template_engine_test.go @@ -0,0 +1,25 @@ +package data + +import ( + "github.com/securenative/packman/internal/etc" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestGolangTemplateEngine_Run(t *testing.T) { + filePath := filepath.Join(os.TempDir(), "packman_template_test.any") + te := NewGolangTemplateEngine() + + defer os.Remove(filePath) + err := etc.WriteFile(filePath, "{{{ .Key }}} {{{ .Value }}}", etc.StringEncoder) + assert.Nil(t, err) + + err = te.Run(filePath, map[string]interface{}{"Key": "key", "Value": "value"}) + assert.Nil(t, err) + + content, err := etc.ReadFile(filePath) + assert.Nil(t, err) + assert.EqualValues(t, "key value", content) +} diff --git a/internal/data/local_config_store.go b/internal/data/local_config_store.go deleted file mode 100644 index 6f3c5c3..0000000 --- a/internal/data/local_config_store.go +++ /dev/null @@ -1,40 +0,0 @@ -package data - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" -) - -type LocalConfigStore struct { - dataPath string -} - -func NewLocalConfigStore(dataPath string) *LocalConfigStore { - return &LocalConfigStore{dataPath: dataPath} -} - -func (this *LocalConfigStore) Put(key string, value interface{}) error { - bytes, err := json.Marshal(value) - if err != nil { - return err - } - - path := this.key2path(key) - return ioutil.WriteFile(path, bytes, os.ModePerm) -} - -func (this *LocalConfigStore) Get(key string, valueOut interface{}) bool { - bytes, err := ioutil.ReadFile(this.key2path(key)) - if err != nil { - return false - } - - return json.Unmarshal(bytes, valueOut) == nil -} - -func (this *LocalConfigStore) key2path(key string) string { - path := fmt.Sprintf(this.dataPath, os.PathSeparator, key) - return path -} diff --git a/internal/data/local_config_store_test.go b/internal/data/local_config_store_test.go deleted file mode 100644 index c48068a..0000000 --- a/internal/data/local_config_store_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package data - -import ( - "fmt" - "github.com/stretchr/testify/assert" - "os" - "testing" -) - -var configStore *LocalConfigStore - -func TestMain(m *testing.M) { - - path := fmt.Sprintf("%s%c%s", os.TempDir(), os.PathSeparator, "packtest") - - err := os.MkdirAll(path, os.ModePerm) - if err != nil { - panic(err.Error()) - } - - configStore = NewLocalConfigStore(path) - - status := m.Run() - - err = os.RemoveAll(path) - if err != nil { - panic(err) - } - - os.Exit(status) -} - -func TestLocalConfigStore_IntegrationTest(t *testing.T) { - const key = "mykey" - type data struct { - Name string - Age int - } - - var nilData *data - notFound := configStore.Get(key, nilData) - assert.Nil(t, nilData) - assert.False(t, notFound) - - obj := &data{Name: "matan", Age: 31} - err := configStore.Put(key, obj) - assert.Nil(t, err) - - var objData data - found := configStore.Get(key, &objData) - assert.True(t, found) - assert.NotNil(t, obj) - assert.EqualValues(t, *obj, objData) -} diff --git a/internal/data/manifest.go b/internal/data/manifest.go index 90e50ac..e795d29 100644 --- a/internal/data/manifest.go +++ b/internal/data/manifest.go @@ -1,25 +1,27 @@ package data -// A place we can store packages -type Backend interface { - Push(name string, source string) error - Pull(name string, destination string) error - ConfigKey() string +type RemoteStorage interface { + Pull(remotePath string, localPath string) error + Push(localPath string, remotePath string) error } -// A simple kv store to load configuration -type ConfigStore interface { - Put(key string, value interface{}) error - Get(key string, valueOut interface{}) bool +type ScriptEngine interface { + Run(scriptPath string, flags map[string]string) (map[string]interface{}, error) } -// Takes a template and a data structure -// will expand the template based on the provided data type TemplateEngine interface { - Render(templateText string, data interface{}) (string, error) + Run(filePath string, data map[string]interface{}) error } -// Will run a script file with the provided arguments -type ScriptEngine interface { - Run(scriptFile string, args []string) error +type LocalStorage interface { + Put(key, value string) error + Get(key string) (string, error) } + +type ConfigKeys string + +const ( + GitUsername ConfigKeys = "GIT_USERNAME" + GitPassword ConfigKeys = "GIT_PASSWORD" + DefaultScript ConfigKeys = "DEFAULT_SCRIPT" +) diff --git a/internal/etc/console.go b/internal/etc/console.go new file mode 100644 index 0000000..450bf30 --- /dev/null +++ b/internal/etc/console.go @@ -0,0 +1,23 @@ +package etc + +import "fmt" + +func PrintHeader(message string, args ...interface{}) { + fmt.Printf(message, args...) +} + +func PrintInfo(message string, args ...interface{}) { + fmt.Printf(message, args...) +} + +func PrintResponse(message string, args ...interface{}) { + fmt.Printf(message, args...) +} + +func PrintError(message string, args ...interface{}) { + fmt.Printf(message, args...) +} + +func PrintSuccess(message string, args ...interface{}) { + fmt.Printf(message, args...) +} diff --git a/internal/etc/files.go b/internal/etc/files.go new file mode 100644 index 0000000..39ee067 --- /dev/null +++ b/internal/etc/files.go @@ -0,0 +1,35 @@ +package etc + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" +) + +func FileExists(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} + +func ReadFile(path string) (string, error) { + bytes, err := ioutil.ReadFile(path) + return string(bytes), err +} + +func WriteFile(path string, data interface{}, encoder func(interface{}) ([]byte, error)) error { + dataBytes, err := encoder(data) + if err != nil { + return err + } + + return ioutil.WriteFile(path, dataBytes, os.ModePerm) +} + +var StringEncoder = func(input interface{}) ([]byte, error) { + return []byte(fmt.Sprintf("%s", input)), nil +} + +var JsonEncoder = func(input interface{}) ([]byte, error) { + return json.Marshal(input) +} diff --git a/pkg/flags.go b/pkg/flags.go deleted file mode 100644 index 8c33816..0000000 --- a/pkg/flags.go +++ /dev/null @@ -1,22 +0,0 @@ -package pkg - -import "fmt" - -const PackageNameFlag = "package_name" -const PackagePathFlag = "package_path" - -func ParseFlags(cmdArgs []string) map[string]string { - - if len(cmdArgs)%2 != 0 { - panic(fmt.Sprintf("incoming flags must be an even array but got %v", cmdArgs)) - } - - out := make(map[string]string) - for idx, item := range cmdArgs { - if idx%2 == 0 { - out[item] = cmdArgs[idx+1] - } - } - - return out -} diff --git a/pkg/script_response.go b/pkg/script_response.go deleted file mode 100644 index c1553d2..0000000 --- a/pkg/script_response.go +++ /dev/null @@ -1,41 +0,0 @@ -package pkg - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" -) - -const replyFile = "reply.json" - -func Reply(data interface{}) error { - - bytes, err := json.Marshal(data) - if err != nil { - return err - } - - destPath := filepath.Join(os.Getenv("PACKMAN_PROJECT"), replyFile) - if err = ioutil.WriteFile(destPath, bytes, os.ModePerm); err != nil { - return err - } - - return nil -} - -func ReadReply(projectPath string) (interface{}, error) { - - destPath := filepath.Join(projectPath, replyFile) - bytes, err := ioutil.ReadFile(destPath) - if err != nil { - return nil, err - } - - var data = new(interface{}) - if err = json.Unmarshal(bytes, data); err != nil { - return nil, err - } - - return data, nil -} From 22448775abce65437e63a6ce5c8f07bdc2e86b94 Mon Sep 17 00:00:00 2001 From: matang Date: Mon, 25 Nov 2019 09:22:23 +0200 Subject: [PATCH 2/5] Refactor to the cli controllers --- cmd/packman/controllers/config.go | 46 +++++++++++++++++++++++++++++++ cmd/packman/controllers/pack.go | 27 ++++++++++++++++++ cmd/packman/controllers/render.go | 32 +++++++++++++++++++++ cmd/packman/controllers/unpack.go | 33 ++++++++++++++++++++++ cmd/packman/main.go | 33 ++++++++++++++++++++++ go.mod | 1 + internal/module.go | 43 +++++++++++++++++++++++++++++ 7 files changed, 215 insertions(+) create mode 100644 cmd/packman/controllers/config.go create mode 100644 cmd/packman/controllers/pack.go create mode 100644 cmd/packman/controllers/render.go create mode 100644 cmd/packman/controllers/unpack.go create mode 100644 cmd/packman/main.go create mode 100644 internal/module.go diff --git a/cmd/packman/controllers/config.go b/cmd/packman/controllers/config.go new file mode 100644 index 0000000..940a926 --- /dev/null +++ b/cmd/packman/controllers/config.go @@ -0,0 +1,46 @@ +package controllers + +import ( + "fmt" + "github.com/securenative/packman/internal" + "github.com/urfave/cli" + "strings" +) + +var AuthController = cli.Command{ + Name: "auth", + Aliases: []string{"a"}, + Usage: "packman auth ", + UsageText: "saving the auth information to your git repositories", + Action: func(c *cli.Context) error { + return auth(c) + }, +} + +var ScriptEngineController = cli.Command{ + Name: "script", + Aliases: []string{"s"}, + Usage: "packman script ", + UsageText: "changes the command which meant to run the packman template script", + Action: func(c *cli.Context) error { + return changeScriptEngine(c) + }, +} + +func auth(c *cli.Context) error { + if c.NArg() != 2 { + return fmt.Errorf("auth expects exactly 2 arguments but got %d arguments", c.NArg()) + } + username := c.Args().Get(0) + password := c.Args().Get(1) + return internal.M.ConfigService.SetAuth(username, password) +} + +func changeScriptEngine(c *cli.Context) error { + if c.NArg() != 1 { + return fmt.Errorf("script expects exactly 1 arguments but got %d arguments", c.NArg()) + } + script := c.Args().Get(0) + script = strings.ReplaceAll(script, `"`, "") + return internal.M.ConfigService.SetDefaultEngine(script) +} diff --git a/cmd/packman/controllers/pack.go b/cmd/packman/controllers/pack.go new file mode 100644 index 0000000..1b44c07 --- /dev/null +++ b/cmd/packman/controllers/pack.go @@ -0,0 +1,27 @@ +package controllers + +import ( + "fmt" + "github.com/securenative/packman/internal" + "github.com/urfave/cli" +) + +var PackController = cli.Command{ + Name: "pack", + Aliases: []string{"p"}, + Usage: "packman pack ", + UsageText: "packing a folder by pushing it to the configured git's remote", + Action: func(c *cli.Context) error { + return pack(c) + }, +} + +func pack(c *cli.Context) error { + if c.NArg() != 2 { + return fmt.Errorf("pack expects exactly 2 arguments but got %d arguments", c.NArg()) + } + + path := c.Args().Get(0) + remote := c.Args().Get(1) + return internal.M.TemplatingService.Pack(remote, path) +} diff --git a/cmd/packman/controllers/render.go b/cmd/packman/controllers/render.go new file mode 100644 index 0000000..87c2d16 --- /dev/null +++ b/cmd/packman/controllers/render.go @@ -0,0 +1,32 @@ +package controllers + +import ( + "fmt" + "github.com/securenative/packman/internal" + "github.com/urfave/cli" +) + +var RenderController = cli.Command{ + Name: "render", + Aliases: []string{"r"}, + Usage: "packman render [-flagName flagValue]...", + UsageText: "unpacking a template project with the given flags", + Action: func(c *cli.Context) error { + return render(c) + }, +} + +func render(c *cli.Context) error { + if c.NArg() != 1 { + return fmt.Errorf("unpack expects exactly 1 argument but got %d arguments", c.NArg()) + } + + path := c.Args().Get(0) + flagsMap := make(map[string]string) + + for _, flagName := range c.FlagNames() { + flagsMap[flagName] = c.String(flagName) + } + + return internal.M.TemplatingService.Render(path, fmt.Sprintf("%s-rendered", path), flagsMap) +} diff --git a/cmd/packman/controllers/unpack.go b/cmd/packman/controllers/unpack.go new file mode 100644 index 0000000..9837f0e --- /dev/null +++ b/cmd/packman/controllers/unpack.go @@ -0,0 +1,33 @@ +package controllers + +import ( + "fmt" + "github.com/securenative/packman/internal" + "github.com/urfave/cli" +) + +var UnpackController = cli.Command{ + Name: "unpack", + Aliases: []string{"u"}, + Usage: "packman unpack [-flagName flagValue]...", + UsageText: "unpacking a template project with the given flags", + Action: func(c *cli.Context) error { + return unpack(c) + }, +} + +func unpack(c *cli.Context) error { + if c.NArg() != 2 { + return fmt.Errorf("unpack expects exactly 2 arguments but got %d arguments", c.NArg()) + } + + path := c.Args().Get(0) + remote := c.Args().Get(1) + flagsMap := make(map[string]string) + + for _, flagName := range c.FlagNames() { + flagsMap[flagName] = c.String(flagName) + } + + return internal.M.TemplatingService.Unpack(remote, path, flagsMap) +} diff --git a/cmd/packman/main.go b/cmd/packman/main.go new file mode 100644 index 0000000..8c34b38 --- /dev/null +++ b/cmd/packman/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "github.com/securenative/packman/cmd/packman/controllers" + "github.com/securenative/packman/internal/etc" + "github.com/urfave/cli" + "os" +) + +func main() { + commands := []cli.Command{ + controllers.PackController, + controllers.UnpackController, + controllers.RenderController, + + controllers.AuthController, + controllers.ScriptEngineController, + } + + app := cli.App{ + Name: "packman", + Version: "0.2", + Commands: commands, + } + + err := app.Run(os.Args) + if err != nil { + etc.PrintError(err.Error()) + os.Exit(1) + } else { + os.Exit(0) + } +} diff --git a/go.mod b/go.mod index f47af67..c162de2 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.12 require ( github.com/otiai10/copy v1.0.2 github.com/stretchr/testify v1.3.0 + github.com/urfave/cli v1.22.2 gopkg.in/src-d/go-git.v4 v4.13.1 ) diff --git a/internal/module.go b/internal/module.go new file mode 100644 index 0000000..fab9413 --- /dev/null +++ b/internal/module.go @@ -0,0 +1,43 @@ +package internal + +import ( + "github.com/securenative/packman/internal/business" + "github.com/securenative/packman/internal/data" +) + +type Module struct { + remoteStorage data.RemoteStorage + scriptEngine data.ScriptEngine + templateEngine data.TemplateEngine + localStorage data.LocalStorage + + TemplatingService business.TemplatingService + ConfigService business.ConfigService +} + +var M *Module + +func init() { + localStorage, err := data.NewFileLocalStorage("") + if err != nil { + panic(err) + } + + scriptCommand, err := localStorage.Get(string(data.DefaultScript)) + if err != nil { + scriptCommand = "go run" + } + + remoteStorage := data.NewGitRemoteStorage(localStorage) + scriptEngine := data.NewGenericScriptEngine(scriptCommand) + templateEngine := data.NewGolangTemplateEngine() + + M = &Module{ + remoteStorage: remoteStorage, + scriptEngine: scriptEngine, + templateEngine: templateEngine, + localStorage: localStorage, + TemplatingService: business.NewTemplateService(remoteStorage, scriptEngine, templateEngine), + ConfigService: business.NewConfigService(localStorage), + } +} From ec18220d218fffef06a2550e021032c45c4e1572 Mon Sep 17 00:00:00 2001 From: matang Date: Mon, 25 Nov 2019 11:40:26 +0200 Subject: [PATCH 3/5] Bug fixes from testing the actual cli --- cmd/packman/controllers/render.go | 9 +++---- cmd/packman/controllers/unpack.go | 15 +++++------ go.mod | 3 +++ internal/business/template_service.go | 10 ++++++- internal/data/generic_script_engine.go | 17 +++++++++--- internal/data/git_remote_storage.go | 4 ++- internal/etc/args.go | 21 +++++++++++++++ internal/etc/console.go | 31 +++++++++++++++------- internal/module.go | 10 ++++++- pkg/packman.go | 36 ++++++++++++++++++++++++++ 10 files changed, 125 insertions(+), 31 deletions(-) create mode 100644 internal/etc/args.go create mode 100644 pkg/packman.go diff --git a/cmd/packman/controllers/render.go b/cmd/packman/controllers/render.go index 87c2d16..c7069e8 100644 --- a/cmd/packman/controllers/render.go +++ b/cmd/packman/controllers/render.go @@ -3,6 +3,7 @@ package controllers import ( "fmt" "github.com/securenative/packman/internal" + "github.com/securenative/packman/internal/etc" "github.com/urfave/cli" ) @@ -17,16 +18,12 @@ var RenderController = cli.Command{ } func render(c *cli.Context) error { - if c.NArg() != 1 { + if c.NArg() < 1 { return fmt.Errorf("unpack expects exactly 1 argument but got %d arguments", c.NArg()) } path := c.Args().Get(0) - flagsMap := make(map[string]string) - - for _, flagName := range c.FlagNames() { - flagsMap[flagName] = c.String(flagName) - } + flagsMap := etc.ArgsToFlagsMap(c.Args()[1:]) return internal.M.TemplatingService.Render(path, fmt.Sprintf("%s-rendered", path), flagsMap) } diff --git a/cmd/packman/controllers/unpack.go b/cmd/packman/controllers/unpack.go index 9837f0e..7090faf 100644 --- a/cmd/packman/controllers/unpack.go +++ b/cmd/packman/controllers/unpack.go @@ -3,13 +3,14 @@ package controllers import ( "fmt" "github.com/securenative/packman/internal" + "github.com/securenative/packman/internal/etc" "github.com/urfave/cli" ) var UnpackController = cli.Command{ Name: "unpack", Aliases: []string{"u"}, - Usage: "packman unpack [-flagName flagValue]...", + Usage: "packman unpack [-flagName flagValue]...", UsageText: "unpacking a template project with the given flags", Action: func(c *cli.Context) error { return unpack(c) @@ -17,17 +18,13 @@ var UnpackController = cli.Command{ } func unpack(c *cli.Context) error { - if c.NArg() != 2 { + if c.NArg() < 2 { return fmt.Errorf("unpack expects exactly 2 arguments but got %d arguments", c.NArg()) } - path := c.Args().Get(0) - remote := c.Args().Get(1) - flagsMap := make(map[string]string) - - for _, flagName := range c.FlagNames() { - flagsMap[flagName] = c.String(flagName) - } + remote := c.Args().Get(0) + path := c.Args().Get(1) + flagsMap := etc.ArgsToFlagsMap(c.Args()[2:]) return internal.M.TemplatingService.Unpack(remote, path, flagsMap) } diff --git a/go.mod b/go.mod index c162de2..9d1d538 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/securenative/packman go 1.12 require ( + github.com/fatih/color v1.7.0 + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.10 // indirect github.com/otiai10/copy v1.0.2 github.com/stretchr/testify v1.3.0 github.com/urfave/cli v1.22.2 diff --git a/internal/business/template_service.go b/internal/business/template_service.go index d990ba7..59ea500 100644 --- a/internal/business/template_service.go +++ b/internal/business/template_service.go @@ -45,8 +45,16 @@ func (this *templateService) Render(templatePath string, packagePath string, fla } return nil }) + if err != nil { + return err + } + + err = os.RemoveAll(filepath.Join(packagePath, "packman")) + if err != nil { + return err + } - return err + return nil } func (this *templateService) Pack(remoteUrl string, packagePath string) error { diff --git a/internal/data/generic_script_engine.go b/internal/data/generic_script_engine.go index ba242a8..88ae07e 100644 --- a/internal/data/generic_script_engine.go +++ b/internal/data/generic_script_engine.go @@ -35,17 +35,23 @@ func (this *genericScriptEngine) Run(scriptPath string, flags map[string]string) cmdArgs = append(cmdArgs, replyFile) cmd := exec.Command(mainCommand, cmdArgs...) - etc.PrintInfo(fmt.Sprintf("Running %s script file with: '%s'\n", scriptPath, cmd.String())) + etc.PrintInfo(fmt.Sprintf("Running '%s'", cmd.String())) result, err := cmd.CombinedOutput() if err != nil { + if result != nil { + etc.PrintError(" FAILED\n") + etc.PrintError(string(result) + "\n") + } return nil, err } + etc.PrintSuccess(" OK\n") + etc.PrintResponse(string(result)) - etc.PrintResponse(string(result) + "\n") - etc.PrintSuccess("Script was run successfully.\n") - etc.PrintInfo("Trying to read reply file: %s...\n", replyFile) + etc.PrintInfo("Trying to read reply file: %s...", replyFile) content, err := etc.ReadFile(replyFile) if err != nil { + etc.PrintError(" FAILED\n") + etc.PrintError("Unable to read reply file from: %s\n", replyFile) return nil, err } @@ -58,6 +64,9 @@ func (this *genericScriptEngine) Run(scriptPath string, flags map[string]string) os.Remove(flagsFile) os.Remove(replyFile) + etc.PrintSuccess(" OK\n") + etc.PrettyPrintJson(out) + return out, nil } diff --git a/internal/data/git_remote_storage.go b/internal/data/git_remote_storage.go index 32f40dd..b095a15 100644 --- a/internal/data/git_remote_storage.go +++ b/internal/data/git_remote_storage.go @@ -1,6 +1,7 @@ package data import ( + "github.com/securenative/packman/internal/etc" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/transport" @@ -32,6 +33,7 @@ func (this *gitRemoteStorage) getAuth() transport.AuthMethod { } func (this *gitRemoteStorage) Pull(remotePath string, localPath string) error { + etc.PrintInfo("Pulling %s into %s...\n", remotePath, localPath) _, err := git.PlainClone(localPath, false, &git.CloneOptions{ URL: remotePath, Auth: this.getAuth(), @@ -79,6 +81,6 @@ func (this *gitRemoteStorage) Push(localPath string, remotePath string) error { if err != nil { return err } - + etc.PrintSuccess("Project was pushed to %s successfully.\n", remotePath) return nil } diff --git a/internal/etc/args.go b/internal/etc/args.go new file mode 100644 index 0000000..1d853e2 --- /dev/null +++ b/internal/etc/args.go @@ -0,0 +1,21 @@ +package etc + +import "strings" + +func ArgsToFlagsMap(args []string) map[string]string { + out := make(map[string]string) + for idx, arg := range args { + if isFlagKey(arg) { + key := strings.TrimPrefix(arg, "-") + out[key] = args[idx+1] + } + } + return out +} + +func isFlagKey(arg string) bool { + if strings.HasPrefix(arg, "-") { + return true + } + return false +} diff --git a/internal/etc/console.go b/internal/etc/console.go index 450bf30..e598901 100644 --- a/internal/etc/console.go +++ b/internal/etc/console.go @@ -1,23 +1,36 @@ package etc -import "fmt" - -func PrintHeader(message string, args ...interface{}) { - fmt.Printf(message, args...) -} +import ( + "encoding/json" + "github.com/fatih/color" +) func PrintInfo(message string, args ...interface{}) { - fmt.Printf(message, args...) + c := color.New(color.FgCyan) + _, _ = c.Printf(message, args...) } func PrintResponse(message string, args ...interface{}) { - fmt.Printf(message, args...) + c := color.New(color.FgYellow).Add(color.Italic) + _, _ = c.Printf(message, args...) +} + +func PrettyPrintJson(m map[string]interface{}) { + bytes, err := json.MarshalIndent(m, "", " ") + if err != nil { + PrintError(err.Error()) + return + } + + PrintResponse("%s\n", string(bytes)) } func PrintError(message string, args ...interface{}) { - fmt.Printf(message, args...) + c := color.New(color.FgRed).Add(color.Bold) + _, _ = c.Printf(message, args...) } func PrintSuccess(message string, args ...interface{}) { - fmt.Printf(message, args...) + c := color.New(color.FgGreen).Add(color.Bold) + _, _ = c.Printf(message, args...) } diff --git a/internal/module.go b/internal/module.go index fab9413..07fa48e 100644 --- a/internal/module.go +++ b/internal/module.go @@ -3,6 +3,8 @@ package internal import ( "github.com/securenative/packman/internal/business" "github.com/securenative/packman/internal/data" + "os" + "path/filepath" ) type Module struct { @@ -18,7 +20,13 @@ type Module struct { var M *Module func init() { - localStorage, err := data.NewFileLocalStorage("") + home, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + localFilePath := filepath.Join(home, "packman_config.json") + localStorage, err := data.NewFileLocalStorage(localFilePath) if err != nil { panic(err) } diff --git a/pkg/packman.go b/pkg/packman.go new file mode 100644 index 0000000..750ba18 --- /dev/null +++ b/pkg/packman.go @@ -0,0 +1,36 @@ +package packman + +import ( + "encoding/json" + "io/ioutil" + "os" +) + +func ReadFlags() map[string]string { + var out map[string]string + path := os.Args[1] + flagsContent, err := ioutil.ReadFile(path) + if err != nil { + panic(err) + } + + err = json.Unmarshal(flagsContent, &out) + if err != nil { + panic(err) + } + + return out +} + +func WriteReply(model interface{}) { + bytes, err := json.Marshal(model) + if err != nil { + panic(err) + } + + path := os.Args[2] + err = ioutil.WriteFile(path, bytes, os.ModePerm) + if err != nil { + panic(err) + } +} From f430a942deb57157f0330e1e66c27caa8bf4212b Mon Sep 17 00:00:00 2001 From: matang Date: Mon, 25 Nov 2019 12:04:12 +0200 Subject: [PATCH 4/5] Adding support for branches --- internal/data/git_remote_storage.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/data/git_remote_storage.go b/internal/data/git_remote_storage.go index b095a15..58b14a5 100644 --- a/internal/data/git_remote_storage.go +++ b/internal/data/git_remote_storage.go @@ -3,10 +3,12 @@ package data import ( "github.com/securenative/packman/internal/etc" "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/transport" "gopkg.in/src-d/go-git.v4/plumbing/transport/http" "os" + "strings" "time" ) @@ -34,10 +36,17 @@ func (this *gitRemoteStorage) getAuth() transport.AuthMethod { func (this *gitRemoteStorage) Pull(remotePath string, localPath string) error { etc.PrintInfo("Pulling %s into %s...\n", remotePath, localPath) + + remote := strings.Split(remotePath, "@") + if len(remote) == 1 { + remote = append(remote, "master") + } + _, err := git.PlainClone(localPath, false, &git.CloneOptions{ - URL: remotePath, - Auth: this.getAuth(), - Progress: os.Stdout, + URL: remote[0], + Auth: this.getAuth(), + Progress: os.Stdout, + ReferenceName: plumbing.NewBranchReferenceName(remote[1]), }) if err != nil { return err From dae74cb46e192982d40c8de8522c7c7f0f3fad54 Mon Sep 17 00:00:00 2001 From: matang Date: Mon, 25 Nov 2019 12:18:52 +0200 Subject: [PATCH 5/5] When templating files ignore dirs and dot paths --- internal/business/template_service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/business/template_service.go b/internal/business/template_service.go index 59ea500..bc09531 100644 --- a/internal/business/template_service.go +++ b/internal/business/template_service.go @@ -39,6 +39,10 @@ func (this *templateService) Render(templatePath string, packagePath string, fla } err = filepath.Walk(packagePath, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() && strings.Contains(path, ".") { + return nil + } + ierr := this.templateEngine.Run(path, scriptData) if ierr != nil { return ierr