pax_global_header00006660000000000000000000000064126772467030014530gustar00rootroot0000000000000052 comment=a9f06a2e5e7e897acc6a1fbcf21236a406315111 goaci-0.1.1/000077500000000000000000000000001267724670300126115ustar00rootroot00000000000000goaci-0.1.1/.gitignore000066400000000000000000000004511267724670300146010ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof bin/ gopath/ *~ *.aci /goaci goaci-0.1.1/LICENSE000066400000000000000000000260751267724670300136300ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. goaci-0.1.1/README.md000066400000000000000000000016721267724670300140760ustar00rootroot00000000000000# goaci `goaci` is a simple command-line tool to build go projects into ACIs which conform to the [app container specification][appc-spec]. [appc-spec]: https://github.com/appc/spec ## Usage Use `goaci` as you would `go get`: $ goaci github.com/coreos/etcd Wrote etcd.aci $ actool -debug validate etcd.aci etcd.aci: valid app container image `goaci` provides options for specifying assets, adding arguments for an application, selecting binary is going to be packaged in final ACI and so on. Use --help to read about them. ## How it works `goaci` creates a temporary directory and uses it as a `GOPATH` (unless it is overridden with `--go-path` option); it then `go get`s the specified package and compiles it statically. Then it generates an image manifest (using mostly default values) and leverages the [appc/spec](https://github.com/appc/spec) libraries to construct an ACI. ## TODO Lots, check out https://github.com/appc/goaci/issues goaci-0.1.1/build000077500000000000000000000006121267724670300136350ustar00rootroot00000000000000#!/bin/bash -eu ORG_PATH="github.com/appc" REPO_PATH="${ORG_PATH}/goaci" if [ ! -h gopath/src/${REPO_PATH} ]; then mkdir -p gopath/src/${ORG_PATH} ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255 fi export GOBIN=${PWD}/bin export GOPATH=${PWD}/gopath eval $(go env) # TODO(jonboulle): vendor go get github.com/appc/spec/... go get golang.org/x/tools/go/vcs go install ${REPO_PATH} goaci-0.1.1/buildercommand.go000066400000000000000000000036271267724670300161350ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "flag" "fmt" "github.com/appc/goaci/proj2aci" ) // parameterMapper is an interface which should handle command line // parameter handling specific to a proj2aci.BuilderCustomizations // implementation. type parameterMapper interface { Name() string SetupParameters(parameters *flag.FlagSet) GetBuilderCustomizations() proj2aci.BuilderCustomizations } // builderCommand is an implementation of command interface which // mainly maps command line parameters to proj2aci.Builder's // configuration and runs the builder. type builderCommand struct { mapper parameterMapper } func newBuilderCommand(mapper parameterMapper) command { return &builderCommand{ mapper: mapper, } } func (cmd *builderCommand) Name() string { custom := cmd.mapper.GetBuilderCustomizations() return custom.Name() } func (cmd *builderCommand) Run(name string, args []string) error { parameters := flag.NewFlagSet(name, flag.ExitOnError) cmd.mapper.SetupParameters(parameters) if err := parameters.Parse(args); err != nil { return err } if len(parameters.Args()) != 1 { return fmt.Errorf("Expected exactly one project to build, got %d", len(args)) } custom := cmd.mapper.GetBuilderCustomizations() custom.GetCommonConfiguration().Project = parameters.Args()[0] builder := proj2aci.NewBuilder(custom) return builder.Run() } goaci-0.1.1/command.go000066400000000000000000000016631267724670300145640ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main // command provides an interface for named actions for command line // purposes. type command interface { // Name should return a name of a command usable at command // line. Name() string // Run should parse given args and perform some action. name // parameter is given for usage purposes. Run(name string, args []string) error } goaci-0.1.1/commands.go000066400000000000000000000015521267724670300147440ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main var ( commandsMap map[string]command = make(map[string]command) ) func init() { commands := []command{ newBuilderCommand(newGoParameterMapper()), newBuilderCommand(newCmakeParameterMapper()), } for _, c := range commands { commandsMap[c.Name()] = c } } goaci-0.1.1/main.go000066400000000000000000000034031267724670300140640ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "os" "sort" "github.com/appc/goaci/proj2aci" ) type CmdLineError struct { what string } func (err *CmdLineError) Error() string { return fmt.Sprintf("Command line error: %s", err.what) } func newCmdLineError(format string, args ...interface{}) error { return &CmdLineError{ what: fmt.Sprintf(format, args...), } } func main() { if err := mainWithError(); err != nil { proj2aci.Warn(err) if _, ok := err.(*CmdLineError); ok { printUsage() } os.Exit(1) } } func mainWithError() error { proj2aci.InitDebug() if len(os.Args) < 2 { return newCmdLineError("No command specified") } if c, ok := commandsMap[os.Args[1]]; ok { name := fmt.Sprintf("%s %s", os.Args[0], os.Args[1]) return c.Run(name, os.Args[2:]) } else { return newCmdLineError("No such command: %q", os.Args[1]) } } func printUsage() { fmt.Println("Available commands:") commands := make([]string, 0, len(commandsMap)) for c := range commandsMap { commands = append(commands, c) } sort.Strings(commands) for _, c := range commands { fmt.Printf(" %s\n", c) } fmt.Printf("Type %s --help to get possible options for chosen command\n", os.Args[0]) } goaci-0.1.1/mappers.go000066400000000000000000000127151267724670300146150ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "flag" "os/exec" "sort" "strings" "github.com/appc/goaci/proj2aci" ) // stringSliceWrapper is an implementation of flag.Value // interface. It is basically a proxy that append strings to already // existing strings slice. type stringSliceWrapper struct { vector *[]string } func (wrapper *stringSliceWrapper) String() string { if len(*wrapper.vector) > 0 { return `["` + strings.Join(*wrapper.vector, `" "`) + `"]` } return "[]" } func (wrapper *stringSliceWrapper) Set(str string) error { *wrapper.vector = append(*wrapper.vector, str) return nil } // commonParameterMapper maps command line parameters to // proj2aci.CommonConfiguration. type commonParameterMapper struct { custom proj2aci.BuilderCustomizations config *proj2aci.CommonConfiguration execWrapper stringSliceWrapper assetWrapper stringSliceWrapper } func (mapper *commonParameterMapper) setupCommonParameters(parameters *flag.FlagSet) { // --exec mapper.execWrapper.vector = &mapper.config.Exec parameters.Var(&mapper.execWrapper, "exec", "Parameters passed to app, can be used multiple times") // --use-binary parameters.StringVar(&mapper.config.UseBinary, "use-binary", "", "Which executable to put in ACI image") // --asset mapper.assetWrapper.vector = &mapper.config.Assets parameters.Var(&mapper.assetWrapper, "asset", "Additional assets, can be used multiple times; format: "+proj2aci.GetAssetString("", "")+"; available placeholders for use: "+mapper.getPlaceholders()) // --keep-tmp-dir parameters.BoolVar(&mapper.config.KeepTmpDir, "keep-tmp-dir", false, "Do not delete temporary directory used for creating ACI") // --tmp-dir parameters.StringVar(&mapper.config.TmpDir, "tmp-dir", "", "Use this directory for build a project and an ACI image") // --reuse-tmp-dir parameters.StringVar(&mapper.config.ReuseTmpDir, "reuse-tmp-dir", "", "Use this already existing directory with built project to build an ACI image; ACI specific contents in this directory are removed before reuse") } func (mapper *commonParameterMapper) getPlaceholders() string { mapping := mapper.custom.GetPlaceholderMapping() placeholders := make([]string, 0, len(mapping)) for p := range mapping { placeholders = append(placeholders, p) } sort.Strings(placeholders) return strings.Join(placeholders, ", ") } func (mapper *commonParameterMapper) Name() string { return mapper.custom.Name() } func (mapper *commonParameterMapper) GetBuilderCustomizations() proj2aci.BuilderCustomizations { return mapper.custom } // goParameterMapper maps command line parameters to // proj2aci.GoConfiguration. type goParameterMapper struct { commonParameterMapper goCustom *proj2aci.GoCustomizations } func newGoParameterMapper() parameterMapper { custom := &proj2aci.GoCustomizations{} return &goParameterMapper{ commonParameterMapper: commonParameterMapper{ custom: custom, config: custom.GetCommonConfiguration(), }, goCustom: custom, } } func (mapper *goParameterMapper) SetupParameters(parameters *flag.FlagSet) { // common params mapper.setupCommonParameters(parameters) // --go-binary goDefaultBinaryDesc := "Go binary to use" gocmd, err := exec.LookPath("go") if err != nil { goDefaultBinaryDesc += " (default: none found in $PATH, so it must be provided)" } else { goDefaultBinaryDesc += " (default: whatever go in $PATH)" } parameters.StringVar(&mapper.goCustom.Configuration.GoBinary, "go-binary", gocmd, goDefaultBinaryDesc) // --go-path parameters.StringVar(&mapper.goCustom.Configuration.GoPath, "go-path", "", "Custom GOPATH (default: a temporary directory)") } // cmakeParameterMapper maps command line parameters to // proj2aci.CmakeConfiguration. type cmakeParameterMapper struct { commonParameterMapper cmakeCustom *proj2aci.CmakeCustomizations cmakeParamWrapper stringSliceWrapper } func newCmakeParameterMapper() parameterMapper { custom := &proj2aci.CmakeCustomizations{} return &cmakeParameterMapper{ commonParameterMapper: commonParameterMapper{ custom: custom, config: custom.GetCommonConfiguration(), }, cmakeCustom: custom, } } func (mapper *cmakeParameterMapper) SetupParameters(parameters *flag.FlagSet) { // common params mapper.setupCommonParameters(parameters) // --binary-dir parameters.StringVar(&mapper.cmakeCustom.Configuration.BinDir, "binary-dir", "", "Look for binaries in this directory (relative to install path, eg passing /usr/local/mysql/bin would look for a binary in /install/usr/local/mysql/bin") // --reuse-src-dir parameters.StringVar(&mapper.cmakeCustom.Configuration.ReuseSrcDir, "reuse-src-dir", "", "Instead of downloading a project, use this path with already downloaded sources") // --cmake-param mapper.cmakeParamWrapper.vector = &mapper.cmakeCustom.Configuration.CmakeParams parameters.Var(&mapper.cmakeParamWrapper, "cmake-param", "Parameters passed to cmake, can be used multiple times") } goaci-0.1.1/proj2aci/000077500000000000000000000000001267724670300143225ustar00rootroot00000000000000goaci-0.1.1/proj2aci/asset.go000066400000000000000000000217531267724670300160000ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "bytes" "fmt" "io" "os" "path/filepath" "regexp" "strings" ) // GetAssetString returns a properly formatted asset string. func GetAssetString(aciAsset, localAsset string) string { return getAssetString(aciAsset, localAsset) } // PrepareAssets copies given assets to ACI rootfs directory. It also // tries to copy required shared libraries if an asset is a // dynamically linked executable or library. placeholderMapping maps // placeholders (like "") to actual paths (usually // something inside temporary directory). func PrepareAssets(assets []string, rootfs string, placeholderMapping map[string]string) error { newAssets := assets processedAssets := make(map[string]struct{}) for len(newAssets) > 0 { assetsToProcess := newAssets newAssets = nil for _, asset := range assetsToProcess { splitAsset := filepath.SplitList(asset) if len(splitAsset) != 2 { return fmt.Errorf("Malformed asset option: '%v' - expected two absolute paths separated with %v", asset, listSeparator()) } evalSrc, srcErr := evalPath(splitAsset[0]) if srcErr != nil { return fmt.Errorf("Could not evaluate symlinks in source asset %q: %v", evalSrc, srcErr) } evalDest, destErr := evalPath(splitAsset[1]) asset = getAssetString(evalSrc, evalDest) Debug("Processing asset:", asset) if _, ok := processedAssets[asset]; ok { Debug(" skipped") continue } additionalAssets, err := processAsset(asset, rootfs, placeholderMapping) if destErr != nil { evalDest, destErr = evalPath(evalDest) if destErr != nil { return fmt.Errorf("Could not evaluate symlinks in destination asset %q, even after it was copied to: %v", evalDest, destErr) } asset = getAssetString(evalSrc, evalDest) } if err != nil { return err } processedAssets[asset] = struct{}{} newAssets = append(newAssets, additionalAssets...) } } return nil } func evalPath(path string) (string, error) { symlinkDir := filepath.Dir(path) symlinkBase := filepath.Base(path) realSymlinkDir, err := filepath.EvalSymlinks(symlinkDir) if err != nil { return path, err } return filepath.Join(realSymlinkDir, symlinkBase), nil } // processAsset validates an asset, replaces placeholders with real // paths and does the copying. It may return additional assets to be // processed when asset is an executable or a library. func processAsset(asset, rootfs string, placeholderMapping map[string]string) ([]string, error) { splitAsset := filepath.SplitList(asset) if len(splitAsset) != 2 { return nil, fmt.Errorf("Malformed asset option: '%v' - expected two absolute paths separated with %v", asset, listSeparator()) } ACIAsset := replacePlaceholders(splitAsset[0], placeholderMapping) localAsset := replacePlaceholders(splitAsset[1], placeholderMapping) if err := validateAsset(ACIAsset, localAsset); err != nil { return nil, err } ACIAssetSubPath := filepath.Join(rootfs, filepath.Dir(ACIAsset)) err := os.MkdirAll(ACIAssetSubPath, 0755) if err != nil { return nil, fmt.Errorf("Failed to create directory tree for asset '%v': %v", asset, err) } err = copyTree(localAsset, filepath.Join(rootfs, ACIAsset)) if err != nil { return nil, fmt.Errorf("Failed to copy assets for %q: %v", asset, err) } additionalAssets, err := getSoLibs(localAsset) if err != nil { return nil, fmt.Errorf("Failed to get dependent assets for %q: %v", localAsset, err) } // HACK! if we are copying libc then try to copy libnss_* libs // (glibc name service switch libs used in networking) if matched, err := filepath.Match("libc.*", filepath.Base(localAsset)); err == nil && matched { toGlob := filepath.Join(filepath.Dir(localAsset), "libnss_*") if matches, err := filepath.Glob(toGlob); err == nil && len(matches) > 0 { matchesAsAssets := make([]string, 0, len(matches)) for _, f := range matches { matchesAsAssets = append(matchesAsAssets, getAssetString(f, f)) } additionalAssets = append(additionalAssets, matchesAsAssets...) } } return additionalAssets, nil } func replacePlaceholders(path string, placeholderMapping map[string]string) string { Debug("Processing path: ", path) newPath := path for placeholder, replacement := range placeholderMapping { newPath = strings.Replace(newPath, placeholder, replacement, -1) } Debug("Processed path: ", newPath) return newPath } func validateAsset(ACIAsset, localAsset string) error { if !filepath.IsAbs(ACIAsset) { return fmt.Errorf("Wrong ACI asset: '%v' - ACI asset has to be absolute path", ACIAsset) } if !filepath.IsAbs(localAsset) { return fmt.Errorf("Wrong local asset: '%v' - local asset has to be absolute path", localAsset) } fi, err := os.Lstat(localAsset) if err != nil { return fmt.Errorf("Error stating %v: %v", localAsset, err) } mode := fi.Mode() if mode.IsDir() || mode.IsRegular() || isSymlink(mode) { return nil } return fmt.Errorf("Can't handle local asset %v - not a file, not a dir, not a symlink", fi.Name()) } func copyTree(src, dest string) error { return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { if err != nil { return err } rootLess := path[len(src):] target := filepath.Join(dest, rootLess) mode := info.Mode() switch { case mode.IsDir(): err := os.Mkdir(target, mode.Perm()) if err != nil { return err } case mode.IsRegular(): if err := copyRegularFile(path, target); err != nil { return err } case isSymlink(mode): if err := copySymlink(path, target); err != nil { return err } default: return fmt.Errorf("Unsupported node %q in assets, only regular files, directories and symlinks are supported.", path, mode.String()) } return nil }) } func copyRegularFile(src, dest string) error { srcFile, err := os.Open(src) if err != nil { return err } defer srcFile.Close() destFile, err := os.Create(dest) if err != nil { return err } defer destFile.Close() if _, err := io.Copy(destFile, srcFile); err != nil { return err } fi, err := srcFile.Stat() if err != nil { return err } if err := destFile.Chmod(fi.Mode().Perm()); err != nil { return err } return nil } func copySymlink(src, dest string) error { symTarget, err := os.Readlink(src) if err != nil { return err } if err := os.Symlink(symTarget, dest); err != nil { return err } return nil } // getSoLibs tries to run ldd on given path and to process its output // to get a list of shared libraries to copy. This list is returned as // an array of assets. // // man ldd says that running ldd on untrusted executables is dangerous // (it might run an executable to get the libraries), so possibly this // should be replaced with objdump. Problem with objdump is that it // just gives library names, while ldd gives absolute paths to those // libraries - to use objdump we need to know the $libdir. func getSoLibs(path string) ([]string, error) { assets := []string{} buf := new(bytes.Buffer) args := []string{ "ldd", path, } if err := RunCmdFull("", args, nil, "", buf, nil); err != nil { if _, ok := err.(CmdFailedError); !ok { return nil, err } } else { re := regexp.MustCompile(`(?m)^\t(?:\S+\s+=>\s+)?(\/\S+)\s+\([0-9a-fA-Fx]+\)$`) for _, matches := range re.FindAllStringSubmatch(string(buf.Bytes()), -1) { lib := matches[1] if lib == "" { continue } symlinkedAssets, err := getSymlinkedAssets(lib) if err != nil { return nil, err } assets = append(assets, symlinkedAssets...) } } return assets, nil } // getSymlinkedAssets returns an array of many assets if given path is // a symlink - useful for getting shared libraries, which are often // surrounded with a bunch of symlinks. func getSymlinkedAssets(path string) ([]string, error) { assets := []string{} maxLevels := 100 levels := maxLevels for { if levels < 1 { return nil, fmt.Errorf("Too many levels of symlinks (>$d)", maxLevels) } fi, err := os.Lstat(path) if err != nil { return nil, err } asset := getAssetString(path, path) assets = append(assets, asset) if !isSymlink(fi.Mode()) { break } symTarget, err := os.Readlink(path) if err != nil { return nil, err } if filepath.IsAbs(symTarget) { path = symTarget } else { path = filepath.Join(filepath.Dir(path), symTarget) } levels-- } return assets, nil } func getAssetString(aciAsset, localAsset string) string { return fmt.Sprintf("%s%s%s", aciAsset, listSeparator(), localAsset) } func isSymlink(mode os.FileMode) bool { return mode&os.ModeSymlink == os.ModeSymlink } goaci-0.1.1/proj2aci/binary.go000066400000000000000000000036231267724670300161410ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "fmt" "io/ioutil" "strings" ) // GetBinaryName checks if useBinary is in binDir and returns it. If // useBinary is empty it returns a binary name if there is only one // such in binDir. Otherwise it returns an error. func GetBinaryName(binDir, useBinary string) (string, error) { fi, err := ioutil.ReadDir(binDir) if err != nil { return "", err } switch { case len(fi) < 1: return "", fmt.Errorf("No binaries found in %q", binDir) case len(fi) == 1: name := fi[0].Name() if useBinary != "" && name != useBinary { return "", fmt.Errorf("No such binary found in %q: %q. There is only %q", binDir, useBinary, name) } Debug("found binary: ", name) return name, nil case len(fi) > 1: names := []string{} for _, v := range fi { names = append(names, v.Name()) } if useBinary == "" { return "", fmt.Errorf("Found multiple binaries in %q, but no specific binary is preferred. Please specify which binary to pick up. Following binaries are available: %q", binDir, strings.Join(names, `", "`)) } for _, v := range names { if v == useBinary { return v, nil } } return "", fmt.Errorf("No such binary found in %q: %q. There are following binaries available: %q", binDir, useBinary, strings.Join(names, `", "`)) } panic("Reaching this point shouldn't be possible.") } goaci-0.1.1/proj2aci/builder.go000066400000000000000000000216351267724670300163060ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "archive/tar" "compress/gzip" "fmt" "io/ioutil" "os" "path/filepath" "runtime" "github.com/appc/spec/aci" "github.com/appc/spec/schema" "github.com/appc/spec/schema/types" ) // CommonConfiguration keeps configuration items common for all the // builders. Users of a Builder are supposed to create a // BuilderCustomizations instance, get a CommonConfiguration instance // via GetCommonConfiguration function and modify it before running // Builder.Run(). type CommonConfiguration struct { Exec []string UseBinary string Assets []string KeepTmpDir bool TmpDir string ReuseTmpDir string Project string } // CommonPaths keeps some paths common for all builders. Implementers // of new BuilderCustomizations can read those after they are set up. type CommonPaths struct { TmpDir string AciDir string RootFS string } // BuilderCustomizations is an interface for customizing a build // process. The order of function roughly describe the build process. type BuilderCustomizations interface { Name() string GetCommonConfiguration() *CommonConfiguration ValidateConfiguration() error GetCommonPaths() *CommonPaths SetupPaths() error GetDirectoriesToMake() []string PrepareProject() error GetPlaceholderMapping() map[string]string GetAssets(aciBinDir string) ([]string, error) GetImageName() (*types.ACIdentifier, error) GetBinaryName() (string, error) GetRepoPath() (string, error) GetImageFileName() (string, error) } type Builder struct { manifest *schema.ImageManifest aciBinDir string custom BuilderCustomizations } func NewBuilder(custom BuilderCustomizations) *Builder { return &Builder{ manifest: nil, aciBinDir: "/", custom: custom, } } func (cmd *Builder) Name() string { return cmd.custom.Name() } func (cmd *Builder) Run() error { Info("Validating builder configuration") if err := cmd.validateConfiguration(); err != nil { return err } Info("Setting up paths") if err := cmd.setupPaths(); err != nil { return err } config := cmd.custom.GetCommonConfiguration() paths := cmd.custom.GetCommonPaths() if config.KeepTmpDir { Info(fmt.Sprintf("Preserving temporary directory %q", paths.TmpDir)) } else { defer os.RemoveAll(paths.TmpDir) } if config.ReuseTmpDir != "" { Info("Reusing temporary directory") Info("Deleting old ACI contents") if err := os.RemoveAll(paths.AciDir); err != nil { return err } Info("Creating directories") if err := cmd.makeDirectories(); err != nil { return err } } else { Info("Creating directories") if err := cmd.makeDirectories(); err != nil { return err } Info("Preparing a project") if err := cmd.prepareProject(); err != nil { return err } } Info("Copying assets to ACI directory") if err := cmd.copyAssets(); err != nil { return err } Info("Preparing manifest") if err := cmd.prepareManifest(); err != nil { return err } Info("Writing ACI") if name, err := cmd.writeACI(); err != nil { return err } else { Info(fmt.Sprintf("Done, wrote %q", name)) } return nil } func (cmd *Builder) validateConfiguration() error { config := cmd.custom.GetCommonConfiguration() if config == nil { panic("common configuration is nil") } if config.Project == "" { fmt.Errorf("Got no project to build") } if config.TmpDir != "" && config.ReuseTmpDir != "" && config.TmpDir != config.ReuseTmpDir { return fmt.Errorf("Specified both tmp dir to reuse and a tmp dir and they are different. ") } if !DirExists(config.ReuseTmpDir) { return fmt.Errorf("Invalid tmp dir to reuse") } return cmd.custom.ValidateConfiguration() } func (cmd *Builder) setupPaths() error { config := cmd.custom.GetCommonConfiguration() paths := cmd.custom.GetCommonPaths() tmpDir := "" if config.TmpDir != "" { tmpDir = config.TmpDir } else if config.ReuseTmpDir != "" { tmpDir = config.ReuseTmpDir } else { tmpName := fmt.Sprintf("proj2aci-%s-", cmd.custom.Name()) aTmpDir, err := ioutil.TempDir("", tmpName) if err != nil { return fmt.Errorf("Failed to set up temporary directory: %v", err) } tmpDir = aTmpDir } paths.TmpDir = tmpDir paths.AciDir = filepath.Join(paths.TmpDir, "aci") paths.RootFS = filepath.Join(paths.AciDir, "rootfs") return cmd.custom.SetupPaths() } func (cmd *Builder) makeDirectories() error { paths := cmd.custom.GetCommonPaths() config := cmd.custom.GetCommonConfiguration() toMake := []string{} if config.ReuseTmpDir == "" && config.TmpDir != "" { tmpDirs, err := tmpDirList(paths.TmpDir) if err != nil { return err } toMake = append(toMake, tmpDirs...) } toMake = append(toMake, paths.AciDir, paths.RootFS) if config.ReuseTmpDir == "" { toMake = append(toMake, cmd.custom.GetDirectoriesToMake()...) } for _, dir := range toMake { Debug("mkdir ", dir) if err := os.Mkdir(dir, 0755); err != nil { return fmt.Errorf("Failed to make directory %q: %v", dir, err) } } return nil } func tmpDirList(path string) ([]string, error) { list := []string{} test := path for { if _, err := os.Stat(test); err != nil { if !os.IsNotExist(err) { return nil, err } // prepend list = append([]string{test}, list...) test = filepath.Dir(test) } else { break } } return list, nil } func (cmd *Builder) prepareProject() error { return cmd.custom.PrepareProject() } func (cmd *Builder) copyAssets() error { paths := cmd.custom.GetCommonPaths() config := cmd.custom.GetCommonConfiguration() mapping := cmd.custom.GetPlaceholderMapping() customAssets, err := cmd.custom.GetAssets(cmd.aciBinDir) if err != nil { return err } assets := append(config.Assets, customAssets...) if err := PrepareAssets(assets, paths.RootFS, mapping); err != nil { return err } return nil } func (cmd *Builder) prepareManifest() error { name, err := cmd.custom.GetImageName() if err != nil { return err } labels, err := cmd.getLabels() if err != nil { return err } app, err := cmd.getApp() if err != nil { return err } cmd.manifest = schema.BlankImageManifest() cmd.manifest.Name = *name cmd.manifest.App = app cmd.manifest.Labels = labels return nil } func (cmd *Builder) getApp() (*types.App, error) { binaryName, err := cmd.custom.GetBinaryName() if err != nil { return nil, err } exec := []string{filepath.Join(cmd.aciBinDir, binaryName)} config := cmd.custom.GetCommonConfiguration() return &types.App{ Exec: append(exec, config.Exec...), User: "0", Group: "0", }, nil } func (cmd *Builder) getLabels() (types.Labels, error) { arch, err := newLabel("arch", runtime.GOARCH) if err != nil { return nil, err } os, err := newLabel("os", runtime.GOOS) if err != nil { return nil, err } labels := types.Labels{ *arch, *os, } vcsLabel, err := cmd.getVCSLabel() if err != nil { return nil, err } else if vcsLabel != nil { labels = append(labels, *vcsLabel) } return labels, nil } func newLabel(name, value string) (*types.Label, error) { acName, err := types.NewACIdentifier(name) if err != nil { return nil, err } return &types.Label{ Name: *acName, Value: value, }, nil } func (cmd *Builder) getVCSLabel() (*types.Label, error) { repoPath, err := cmd.custom.GetRepoPath() if err != nil { return nil, err } if repoPath == "" { return nil, nil } name, value, err := GetVCSInfo(repoPath) if err != nil { return nil, fmt.Errorf("Failed to get VCS info: %v", err) } acname, err := types.NewACIdentifier(name) if err != nil { return nil, fmt.Errorf("Invalid VCS label: %v", err) } return &types.Label{ Name: *acname, Value: value, }, nil } func (cmd *Builder) writeACI() (string, error) { mode := os.O_CREATE | os.O_WRONLY | os.O_TRUNC filename, err := cmd.custom.GetImageFileName() if err != nil { return "", err } of, err := os.OpenFile(filename, mode, 0644) if err != nil { return "", fmt.Errorf("Error opening output file: %v", err) } defer of.Close() gw := gzip.NewWriter(of) defer gw.Close() tr := tar.NewWriter(gw) defer tr.Close() // FIXME: the files in the tar archive are added with the // wrong uid/gid. The uid/gid of the aci builder leaks in the // tar archive. See: https://github.com/appc/goaci/issues/16 iw := aci.NewImageWriter(*cmd.manifest, tr) paths := cmd.custom.GetCommonPaths() if err := filepath.Walk(paths.AciDir, aci.BuildWalker(paths.AciDir, iw, nil)); err != nil { return "", err } if err := iw.Close(); err != nil { return "", err } return of.Name(), nil } goaci-0.1.1/proj2aci/cmake.go000066400000000000000000000140721267724670300157350ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "fmt" "os" "path/filepath" "runtime" "strings" "github.com/appc/spec/schema" "github.com/appc/spec/schema/types" "golang.org/x/tools/go/vcs" ) type CmakeConfiguration struct { CommonConfiguration BinDir string ReuseSrcDir string CmakeParams []string } type CmakePaths struct { CommonPaths src string build string install string binDir string } type CmakeCustomizations struct { Configuration CmakeConfiguration paths CmakePaths fullBinPath string } func (custom *CmakeCustomizations) Name() string { return "cmake" } func (custom *CmakeCustomizations) GetCommonConfiguration() *CommonConfiguration { return &custom.Configuration.CommonConfiguration } func (custom *CmakeCustomizations) ValidateConfiguration() error { if !DirExists(custom.Configuration.ReuseSrcDir) { return fmt.Errorf("Invalid src dir to reuse") } return nil } func (custom *CmakeCustomizations) GetCommonPaths() *CommonPaths { return &custom.paths.CommonPaths } func (custom *CmakeCustomizations) SetupPaths() error { setupReusableDir(&custom.paths.src, custom.Configuration.ReuseSrcDir, filepath.Join(custom.paths.TmpDir, "src")) custom.paths.build = filepath.Join(custom.paths.TmpDir, "build") custom.paths.install = filepath.Join(custom.paths.TmpDir, "install") return nil } func setupReusableDir(path *string, reusePath, stockPath string) { if path == nil { panic("path in setupReusableDir cannot be nil") } if reusePath != "" { *path = reusePath } else { *path = stockPath } } func (custom *CmakeCustomizations) GetDirectoriesToMake() []string { dirs := []string{ custom.paths.build, custom.paths.install, } // not creating custom.paths.src, because go.vcs requires the // src directory to be nonexistent return dirs } func (custom *CmakeCustomizations) PrepareProject() error { if custom.Configuration.ReuseSrcDir == "" { if err := custom.createRepo(); err != nil { return err } } Info("Running cmake") if err := custom.runCmake(); err != nil { return err } Info("Running make") if err := custom.runMake(); err != nil { return err } Info("Running make install") if err := custom.runMakeInstall(); err != nil { return err } return nil } func (custom *CmakeCustomizations) createRepo() error { Info(fmt.Sprintf("Downloading %s", custom.Configuration.Project)) repo, err := vcs.RepoRootForImportPath(custom.Configuration.Project, false) if err != nil { return err } return repo.VCS.Create(custom.paths.src, repo.Repo) } func (custom *CmakeCustomizations) runCmake() error { args := []string{"cmake"} args = append(args, custom.Configuration.CmakeParams...) args = append(args, custom.paths.src) return RunCmd(args, nil, custom.paths.build) } func (custom *CmakeCustomizations) runMake() error { args := []string{ "make", fmt.Sprintf("-j%d", runtime.NumCPU()), } return RunCmd(args, nil, custom.paths.build) } func (custom *CmakeCustomizations) runMakeInstall() error { args := []string{ "make", "install", } env := append(os.Environ(), "DESTDIR="+custom.paths.install) return RunCmd(args, env, custom.paths.build) } func (custom *CmakeCustomizations) GetPlaceholderMapping() map[string]string { return map[string]string{ "": custom.paths.src, "": custom.paths.build, "": custom.paths.install, } } func (custom *CmakeCustomizations) GetAssets(aciBinDir string) ([]string, error) { binaryName, err := custom.GetBinaryName() if err != nil { return nil, err } rootBinary := filepath.Join(aciBinDir, binaryName) return []string{GetAssetString(rootBinary, custom.fullBinPath)}, nil } func (custom *CmakeCustomizations) getBinDir() (string, error) { if custom.Configuration.BinDir != "" { return filepath.Join(custom.paths.install, custom.Configuration.BinDir), nil } dirs := []string{ "/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin", } for _, dir := range dirs { path := filepath.Join(custom.paths.install, dir) _, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { continue } return "", err } return path, nil } return "", fmt.Errorf("Could not find any bin directory") } func (custom *CmakeCustomizations) GetImageName() (*types.ACIdentifier, error) { imageName := custom.Configuration.Project if filepath.Base(imageName) == "..." { imageName = filepath.Dir(imageName) if custom.Configuration.UseBinary != "" { imageName += "-" + custom.Configuration.UseBinary } } return types.NewACIdentifier(strings.ToLower(imageName)) } func (custom *CmakeCustomizations) GetBinaryName() (string, error) { if err := custom.findFullBinPath(); err != nil { return "", err } return filepath.Base(custom.fullBinPath), nil } func (custom *CmakeCustomizations) findFullBinPath() error { if custom.fullBinPath != "" { return nil } binDir, err := custom.getBinDir() if err != nil { return err } binary, err := GetBinaryName(binDir, custom.Configuration.UseBinary) if err != nil { return err } custom.fullBinPath = filepath.Join(binDir, binary) return nil } func (custom *CmakeCustomizations) GetRepoPath() (string, error) { return custom.paths.src, nil } func (custom *CmakeCustomizations) GetImageFileName() (string, error) { base := filepath.Base(custom.Configuration.Project) if base == "..." { base = filepath.Base(filepath.Dir(custom.Configuration.Project)) if custom.Configuration.UseBinary != "" { base += "-" + custom.Configuration.UseBinary } } return base + schema.ACIExtension, nil } goaci-0.1.1/proj2aci/go.go000066400000000000000000000124211267724670300152560ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/appc/spec/schema" "github.com/appc/spec/schema/types" ) type GoConfiguration struct { CommonConfiguration GoBinary string GoPath string } type GoPaths struct { CommonPaths project string realGo string fakeGo string goRoot string goBin string } type GoCustomizations struct { Configuration GoConfiguration paths GoPaths app string } func (custom *GoCustomizations) Name() string { return "go" } func (custom *GoCustomizations) GetCommonConfiguration() *CommonConfiguration { return &custom.Configuration.CommonConfiguration } func (custom *GoCustomizations) GetCommonPaths() *CommonPaths { return &custom.paths.CommonPaths } func (custom *GoCustomizations) ValidateConfiguration() error { if custom.Configuration.GoBinary == "" { return fmt.Errorf("Go binary not found") } return nil } func (custom *GoCustomizations) SetupPaths() error { custom.paths.realGo, custom.paths.fakeGo = custom.getGoPath() if os.Getenv("GOPATH") != "" { Warn("GOPATH env var is ignored, use --go-path=\"$GOPATH\" option instead") } custom.paths.goRoot = os.Getenv("GOROOT") if custom.paths.goRoot != "" { Warn("Overriding GOROOT env var to ", custom.paths.goRoot) } projectName := getProjectName(custom.Configuration.Project) // Project name is path-like string with slashes, but slash is // not a file separator on every OS. custom.paths.project = filepath.Join(custom.paths.realGo, "src", filepath.Join(strings.Split(projectName, "/")...)) custom.paths.goBin = filepath.Join(custom.paths.fakeGo, "bin") return nil } // getGoPath returns go path and fake go path. The former is either in // /tmp (which is a default) or some other path as specified by // --go-path parameter. The latter is always in /tmp. func (custom *GoCustomizations) getGoPath() (string, string) { fakeGoPath := filepath.Join(custom.paths.TmpDir, "gopath") if custom.Configuration.GoPath == "" { return fakeGoPath, fakeGoPath } return custom.Configuration.GoPath, fakeGoPath } func getProjectName(project string) string { if filepath.Base(project) != "..." { return project } return filepath.Dir(project) } func (custom *GoCustomizations) GetDirectoriesToMake() []string { return []string{ custom.paths.fakeGo, custom.paths.goBin, } } func (custom *GoCustomizations) PrepareProject() error { Info("Running go get") // Construct args for a go get that does a static build args := []string{ "go", "get", "-a", custom.Configuration.Project, } env := []string{ "GOPATH=" + custom.paths.realGo, "GOBIN=" + custom.paths.goBin, "PATH=" + os.Getenv("PATH"), } if custom.paths.goRoot != "" { env = append(env, "GOROOT="+custom.paths.goRoot) } cmd := exec.Cmd{ Env: env, Path: custom.Configuration.GoBinary, Args: args, Stderr: os.Stderr, Stdout: os.Stdout, } Debug("env: ", cmd.Env) Debug("running command: ", strings.Join(cmd.Args, " ")) if err := cmd.Run(); err != nil { return err } return nil } func (custom *GoCustomizations) GetPlaceholderMapping() map[string]string { return map[string]string{ "": custom.paths.project, "": custom.paths.realGo, } } func (custom *GoCustomizations) GetAssets(aciBinDir string) ([]string, error) { name, err := custom.GetBinaryName() if err != nil { return nil, err } aciAsset := filepath.Join(aciBinDir, name) localAsset := filepath.Join(custom.paths.goBin, name) return []string{GetAssetString(aciAsset, localAsset)}, nil } func (custom *GoCustomizations) GetImageName() (*types.ACIdentifier, error) { imageName := custom.Configuration.Project if filepath.Base(imageName) == "..." { imageName = filepath.Dir(imageName) if custom.Configuration.UseBinary != "" { imageName += "-" + custom.Configuration.UseBinary } } return types.NewACIdentifier(strings.ToLower(imageName)) } func (custom *GoCustomizations) GetBinaryName() (string, error) { if err := custom.findBinaryName(); err != nil { return "", err } return custom.app, nil } func (custom *GoCustomizations) findBinaryName() error { if custom.app != "" { return nil } binaryName, err := GetBinaryName(custom.paths.goBin, custom.Configuration.UseBinary) if err != nil { return err } custom.app = binaryName return nil } func (custom *GoCustomizations) GetRepoPath() (string, error) { return custom.paths.project, nil } func (custom *GoCustomizations) GetImageFileName() (string, error) { base := filepath.Base(custom.Configuration.Project) if base == "..." { base = filepath.Base(filepath.Dir(custom.Configuration.Project)) if custom.Configuration.UseBinary != "" { base += "-" + custom.Configuration.UseBinary } } return base + schema.ACIExtension, nil } goaci-0.1.1/proj2aci/run.go000066400000000000000000000041101267724670300154510ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "fmt" "io" "os" "os/exec" "strings" ) type CmdFailedError struct { Err error } func (e CmdFailedError) Error() string { return fmt.Sprintf("CmdFailedError: %s", e.Err.Error()) } type CmdNotFoundError struct { Err error } func (e CmdNotFoundError) Error() string { return fmt.Sprintf("CmdNotFoundError: %s", e.Err.Error()) } // RunCmdFull runs given execProg. execProg should be an absolute path // to a program or it can be an empty string. In the latter case first // string in args is taken and searched for in $PATH. // // If execution fails then CmdFailedError is returned. This can be // useful if we don't care if execution fails or not. CmdNotFoundError // is returned if executable is not found. func RunCmdFull(execProg string, args, env []string, cwd string, stdout, stderr io.Writer) error { if len(args) < 1 { return fmt.Errorf("No args to execute passed") } prog := execProg if prog == "" { pathProg, err := exec.LookPath(args[0]) if err != nil { return CmdNotFoundError{err} } prog = pathProg } else if _, err := os.Stat(prog); err != nil { return CmdNotFoundError{err} } cmd := exec.Cmd{ Path: prog, Args: args, Env: env, Dir: cwd, Stdout: stdout, Stderr: stderr, } Debug(`running command: "`, strings.Join(args, `" "`), `"`) if err := cmd.Run(); err != nil { return CmdFailedError{err} } return nil } func RunCmd(args, env []string, cwd string) error { return RunCmdFull("", args, env, cwd, os.Stdout, os.Stderr) } goaci-0.1.1/proj2aci/util.go000066400000000000000000000035751267724670300156400ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "fmt" "io" "os" "path/filepath" "strings" "unicode/utf8" ) var debugEnabled bool var pathListSep string // DirExists checks if directory exists if given path is not empty. // // This function is rather specific as it is mostly used for checking // overrides validity (like overriding temporary directory, where // empty string means "do not override"). func DirExists(path string) bool { if path != "" { fi, err := os.Stat(path) if err != nil || !fi.IsDir() { return false } } return true } func printTo(w io.Writer, i ...interface{}) { s := fmt.Sprint(i...) fmt.Fprintln(w, strings.TrimSuffix(s, "\n")) } func Warn(i ...interface{}) { printTo(os.Stderr, i...) } func Info(i ...interface{}) { printTo(os.Stdout, i...) } func Debug(i ...interface{}) { if debugEnabled { printTo(os.Stdout, i...) } } func InitDebug() { if os.Getenv("GOACI_DEBUG") != "" { debugEnabled = true } } // listSeparator returns filepath.ListSeparator rune as a string. func listSeparator() string { if pathListSep == "" { len := utf8.RuneLen(filepath.ListSeparator) if len < 0 { panic("filepath.ListSeparator is not valid utf8?!") } buf := make([]byte, len) len = utf8.EncodeRune(buf, filepath.ListSeparator) pathListSep = string(buf[:len]) } return pathListSep } goaci-0.1.1/proj2aci/vcs.go000066400000000000000000000061561267724670300154540ustar00rootroot00000000000000// Copyright 2016 The appc Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package proj2aci import ( "bytes" "fmt" "os" "os/exec" "path/filepath" "strings" ) func repoDirExists(projPath, repoDir string) bool { path := filepath.Join(projPath, repoDir) info, err := os.Stat(path) if err != nil { return false } return info.IsDir() } // getId gets first line of commands output which should hold some VCS // specific id of current code checkout. func getId(dir, cmd string, params []string) (string, error) { args := []string{cmd} args = append(args, params...) buffer := new(bytes.Buffer) cmdPath, err := exec.LookPath(cmd) if err != nil { return "", nil } process := &exec.Cmd{ Path: cmdPath, Args: args, Env: []string{ "PATH=" + os.Getenv("PATH"), }, Dir: dir, Stdout: buffer, } if err := process.Run(); err != nil { return "", err } output := string(buffer.Bytes()) if newline := strings.Index(output, "\n"); newline < 0 { return output, nil } else { return output[:newline], nil } } func getLabelAndId(label, path, cmd string, params []string) (string, string, error) { if info, err := getId(path, cmd, params); err != nil { return "", "", err } else { return label, info, nil } } type VCSInfo interface { IsValid(path string) bool GetLabelAndId(path string) (string, string, error) } type GitInfo struct{} func (info GitInfo) IsValid(path string) bool { return repoDirExists(path, ".git") } func (info GitInfo) GetLabelAndId(path string) (string, string, error) { return getLabelAndId("git", path, "git", []string{"rev-parse", "HEAD"}) } type HgInfo struct{} func (info HgInfo) IsValid(path string) bool { return repoDirExists(path, ".hg") } func (info HgInfo) GetLabelAndId(path string) (string, string, error) { return getLabelAndId("hg", path, "hg", []string{"id", "-i"}) } type SvnInfo struct{} func (info SvnInfo) IsValid(path string) bool { return repoDirExists(path, ".svn") } func (info SvnInfo) GetLabelAndId(path string) (string, string, error) { return getLabelAndId("svn", path, "svnversion", []string{}) } type BzrInfo struct{} func (info BzrInfo) IsValid(path string) bool { return repoDirExists(path, ".bzr") } func (info BzrInfo) GetLabelAndId(path string) (string, string, error) { return getLabelAndId("bzr", path, "bzr", []string{"revno"}) } func GetVCSInfo(projPath string) (string, string, error) { vcses := []VCSInfo{ GitInfo{}, HgInfo{}, SvnInfo{}, BzrInfo{}, } for _, vcs := range vcses { if vcs.IsValid(projPath) { return vcs.GetLabelAndId(projPath) } } return "", "", fmt.Errorf("Unknown code repository in %q", projPath) }