diff --git a/cmd/apply.go b/cmd/apply.go index e8ad6e09734a83e45d67ada13f173adef185b299..e04c08fe0d4dae4727c553006c8468e2e565ee94 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -21,7 +21,6 @@ import ( "github.com/spf13/cobra" - "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -104,7 +103,6 @@ var applyCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) c.ClientPool, c.Discovery, err = restClientPool(cmd, &env) if err != nil { @@ -120,14 +118,14 @@ var applyCmd = &cobra.Command{ cmd: cmd, env: env, components: componentNames, - cwd: wd, + cwd: cwd, }) objs, err := te.Expand() if err != nil { return err } - return c.Run(objs, wd) + return c.Run(objs, cwd) }, Long: ` The ` + "`apply`" + `command uses local manifest(s) to update (and optionally create) diff --git a/cmd/delete.go b/cmd/delete.go index 9e39e57024da913e3025e5810666c60e93c58293..1f91e4314fe225c8cf9337330ac7a9828e6e363f 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -21,7 +21,6 @@ import ( "github.com/spf13/cobra" - "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -66,7 +65,6 @@ var deleteCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) c.ClientPool, c.Discovery, err = restClientPool(cmd, &env) if err != nil { @@ -82,7 +80,7 @@ var deleteCmd = &cobra.Command{ cmd: cmd, env: env, components: componentNames, - cwd: wd, + cwd: cwd, }) objs, err := te.Expand() if err != nil { diff --git a/cmd/diff.go b/cmd/diff.go index 4cb362ac99359ff3b4035a2e67fd3c4a401d977f..13bf1b8128c4bd27faac1c9f44f953eb71bb8e7b 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -61,7 +61,6 @@ var diffCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) componentNames, err := flags.GetStringArray(flagComponent) if err != nil { @@ -83,7 +82,7 @@ var diffCmd = &cobra.Command{ return err } - c, err := initDiffCmd(appFs, cmd, wd, env1, env2, componentNames, diffStrategy) + c, err := initDiffCmd(appFs, cmd, cwd, env1, env2, componentNames, diffStrategy) if err != nil { return err } @@ -141,7 +140,7 @@ ks diff dev -c redis `, } -func initDiffCmd(fs afero.Fs, cmd *cobra.Command, wd metadata.AbsPath, envFq1, envFq2 *string, files []string, diffStrategy string) (kubecfg.DiffCmd, error) { +func initDiffCmd(fs afero.Fs, cmd *cobra.Command, wd string, envFq1, envFq2 *string, files []string, diffStrategy string) (kubecfg.DiffCmd, error) { const ( remote = "remote" local = "local" @@ -186,7 +185,7 @@ func initDiffCmd(fs afero.Fs, cmd *cobra.Command, wd metadata.AbsPath, envFq1, e } // initDiffSingleEnv sets up configurations for diffing using one environment -func initDiffSingleEnv(fs afero.Fs, env, diffStrategy string, files []string, cmd *cobra.Command, wd metadata.AbsPath) (kubecfg.DiffCmd, error) { +func initDiffSingleEnv(fs afero.Fs, env, diffStrategy string, files []string, cmd *cobra.Command, wd string) (kubecfg.DiffCmd, error) { c := kubecfg.DiffRemoteCmd{} c.DiffStrategy = diffStrategy c.Client = &kubecfg.Client{} @@ -322,8 +321,11 @@ func expandEnvObjs(fs afero.Fs, cmd *cobra.Command, env string, manager metadata return nil, err } - libPath, vendorPath := manager.LibPaths() - metadataPath, mainPath, paramsPath := manager.EnvPaths(env) + _, vendorPath := manager.LibPaths() + libPath, mainPath, paramsPath, err := manager.EnvPaths(env) + if err != nil { + return nil, err + } componentPaths, err := manager.ComponentPaths() if err != nil { return nil, err @@ -339,7 +341,7 @@ func expandEnvObjs(fs afero.Fs, cmd *cobra.Command, env string, manager metadata return nil, err } - expander.FlagJpath = append([]string{string(libPath), string(vendorPath), string(metadataPath)}, expander.FlagJpath...) + expander.FlagJpath = append([]string{string(vendorPath), string(libPath)}, expander.FlagJpath...) expander.ExtCodes = append([]string{baseObj, params, envSpec}, expander.ExtCodes...) envFiles := []string{string(mainPath)} diff --git a/cmd/env.go b/cmd/env.go index 638b7edb8a41ea780681a141416b70d5f834a53c..ca74f4986315c9f57b855cbe44e6641974705e77 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -125,9 +125,8 @@ var envAddCmd = &cobra.Command{ if err != nil { return err } - appRoot := metadata.AbsPath(appDir) - manager, err := metadata.Find(appRoot) + manager, err := metadata.Find(appDir) if err != nil { return err } @@ -209,9 +208,8 @@ var envRmCmd = &cobra.Command{ if err != nil { return err } - appRoot := metadata.AbsPath(appDir) - manager, err := metadata.Find(appRoot) + manager, err := metadata.Find(appDir) if err != nil { return err } @@ -258,9 +256,8 @@ var envListCmd = &cobra.Command{ if err != nil { return err } - appRoot := metadata.AbsPath(appDir) - manager, err := metadata.Find(appRoot) + manager, err := metadata.Find(appDir) if err != nil { return err } @@ -301,9 +298,8 @@ var envSetCmd = &cobra.Command{ if err != nil { return err } - appRoot := metadata.AbsPath(appDir) - manager, err := metadata.Find(appRoot) + manager, err := metadata.Find(appDir) if err != nil { return err } diff --git a/cmd/init.go b/cmd/init.go index 2b44b57ca2ba652d44aad865d5ba29b9b990e113..b8361112e8ae4f5df52b37754b01b78e34a444ba 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -21,7 +21,6 @@ import ( "os" "path/filepath" - "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -62,13 +61,11 @@ var initCmd = &cobra.Command{ return err } - path, err := genKsRoot(appName, wd, initDir) + appRoot, err := genKsRoot(appName, wd, initDir) if err != nil { return err } - appRoot := metadata.AbsPath(path) - specFlag, err := flags.GetString(flagAPISpec) if err != nil { return err @@ -80,7 +77,7 @@ var initCmd = &cobra.Command{ } log.Infof("Creating a new app '%s' at path '%s'", appName, appRoot) - c, err := kubecfg.NewInitCmd(appName, appRoot, specFlag, &server, &namespace) + c, err := kubecfg.NewInitCmd(appName, appRoot, &specFlag, &server, &namespace) if err != nil { return err } diff --git a/cmd/pkg.go b/cmd/pkg.go index 3a7baca813b935d8930105780f919a0263d5cfcc..68728cde2d42dd456c29ee3392f071f9660eb392 100644 --- a/cmd/pkg.go +++ b/cmd/pkg.go @@ -22,7 +22,7 @@ import ( "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/metadata/parts" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" "github.com/spf13/cobra" ) @@ -101,9 +101,8 @@ var pkgInstallCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err != nil { return err } @@ -165,9 +164,8 @@ var pkgDescribeCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err != nil { return err } @@ -238,9 +236,8 @@ var pkgListCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err != nil { return err } @@ -273,7 +270,7 @@ var pkgListCmd = &cobra.Command{ } } - formatted, err := utils.PadRows(rows) + formatted, err := str.PadRows(rows) if err != nil { return err } diff --git a/cmd/prototype.go b/cmd/prototype.go index 5df4520bb1d4e111d750fc0a525b5e8463c8656c..821c589286cd2bbebe96102ede1d73616eff60f5 100644 --- a/cmd/prototype.go +++ b/cmd/prototype.go @@ -26,7 +26,7 @@ import ( "github.com/ksonnet/ksonnet/prototype" "github.com/ksonnet/ksonnet/prototype/snippet" "github.com/ksonnet/ksonnet/prototype/snippet/jsonnet" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" "github.com/spf13/cobra" ) @@ -87,9 +87,8 @@ var prototypeListCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err != nil { return err } @@ -143,10 +142,9 @@ var prototypeDescribeCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) extProtos := prototype.SpecificationSchemas{} - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err == nil { extProtos, err = manager.GetAllPrototypes() if err != nil { @@ -255,10 +253,9 @@ var prototypePreviewCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) extProtos := prototype.SpecificationSchemas{} - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err == nil { extProtos, err = manager.GetAllPrototypes() if err != nil { @@ -356,7 +353,7 @@ var prototypeUseCmd = &cobra.Command{ if err != nil { return err } - manager, err := metadata.Find(metadata.AbsPath(cwd)) + manager, err := metadata.Find(cwd) if err != nil { return fmt.Errorf("Command can only be run in a ksonnet application directory:\n\n%v", err) } @@ -500,7 +497,7 @@ func expandPrototype(proto *prototype.SpecificationSchema, templateType prototyp } if templateType == prototype.Jsonnet { componentsText := "components." + componentName - if !utils.IsASCIIIdentifier(componentName) { + if !str.IsASCIIIdentifier(componentName) { componentsText = fmt.Sprintf(`components["%s"]`, componentName) } template = append([]string{ diff --git a/cmd/registry.go b/cmd/registry.go index 341032babcf94eaf40540d4df25f1fc45aac0703..7d385272843871ad61d37080428d217b85b508a7 100644 --- a/cmd/registry.go +++ b/cmd/registry.go @@ -7,7 +7,7 @@ import ( "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" "github.com/spf13/cobra" ) @@ -76,9 +76,8 @@ var registryListCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err != nil { return err } @@ -100,7 +99,7 @@ var registryListCmd = &cobra.Command{ rows = append(rows, []string{name, regRef.Protocol, regRef.URI}) } - formatted, err := utils.PadRows(rows) + formatted, err := str.PadRows(rows) if err != nil { return err } @@ -136,9 +135,8 @@ var registryDescribeCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) - manager, err := metadata.Find(wd) + manager, err := metadata.Find(cwd) if err != nil { return err } diff --git a/cmd/root.go b/cmd/root.go index bb5668f789fad5ddba58d172bef04e427bcceadf..9dd9f72245b08a3f736b2ba90bcdf20dcb6b0a60 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -39,6 +39,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "github.com/ksonnet/ksonnet/metadata" + str "github.com/ksonnet/ksonnet/strings" "github.com/ksonnet/ksonnet/template" "github.com/ksonnet/ksonnet/utils" @@ -332,9 +333,8 @@ func overrideCluster(envName string, clientConfig clientcmd.ClientConfig, overri if err != nil { return err } - wd := metadata.AbsPath(cwd) - metadataManager, err := metadata.Find(wd) + metadataManager, err := metadata.Find(cwd) if err != nil { return err } @@ -346,7 +346,7 @@ func overrideCluster(envName string, clientConfig clientcmd.ClientConfig, overri var servers = make(map[string]string) for name, cluster := range rawConfig.Clusters { - server, err := utils.NormalizeURL(cluster.Server) + server, err := str.NormalizeURL(cluster.Server) if err != nil { return err } @@ -366,7 +366,7 @@ func overrideCluster(envName string, clientConfig clientcmd.ClientConfig, overri } // TODO support multi-cluster deployment. - server, err := utils.NormalizeURL(env.Destinations[0].Server) + server, err := str.NormalizeURL(env.Destinations[0].Server) if err != nil { return err } @@ -392,7 +392,7 @@ type cmdObjExpanderConfig struct { cmd *cobra.Command env string components []string - cwd metadata.AbsPath + cwd string } // cmdObjExpander finds and expands templates for the family of commands of @@ -432,10 +432,13 @@ func (te *cmdObjExpander) Expand() ([]*unstructured.Unstructured, error) { return nil, errors.Wrap(err, "find metadata") } - libPath, vendorPath := manager.LibPaths() - metadataPath, mainPath, paramsPath := manager.EnvPaths(te.config.env) + _, vendorPath := manager.LibPaths() + libPath, mainPath, paramsPath, err := manager.EnvPaths(te.config.env) + if err != nil { + return nil, err + } - expander.FlagJpath = append([]string{string(libPath), string(vendorPath), string(metadataPath)}, expander.FlagJpath...) + expander.FlagJpath = append([]string{string(vendorPath), string(libPath)}, expander.FlagJpath...) componentPaths, err := manager.ComponentPaths() if err != nil { @@ -524,7 +527,7 @@ func constructBaseObj(componentPaths, componentNames []string) (string, error) { // Emit object field. Sanitize the name to guarantee we generate valid // Jsonnet. - componentName = utils.QuoteNonASCII(componentName) + componentName = str.QuoteNonASCII(componentName) fmt.Fprintf(&obj, " %s: %s,\n", componentName, importExpr) } diff --git a/cmd/show.go b/cmd/show.go index 42196e59af845ffa95c20615ac7643778be10113..1b98572cae9b992201b203a165373f8c11d7211e 100644 --- a/cmd/show.go +++ b/cmd/show.go @@ -21,7 +21,6 @@ import ( "github.com/spf13/cobra" - "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -97,13 +96,12 @@ ks show dev -c redis -c nginx-server if err != nil { return err } - wd := metadata.AbsPath(cwd) te := newCmdObjExpander(cmdObjExpanderConfig{ cmd: cmd, env: env, components: componentNames, - cwd: wd, + cwd: cwd, }) objs, err := te.Expand() if err != nil { diff --git a/cmd/validate.go b/cmd/validate.go index 64afd822ba3238ac4785575db2b32ff6d9ad1619..f84b4cb6bd892ddf3b06a6408d42bfc1c3d2b64e 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -21,7 +21,6 @@ import ( "github.com/spf13/cobra" - "github.com/ksonnet/ksonnet/metadata" "github.com/ksonnet/ksonnet/pkg/kubecfg" ) @@ -54,7 +53,6 @@ var validateCmd = &cobra.Command{ if err != nil { return err } - wd := metadata.AbsPath(cwd) componentNames, err := flags.GetStringArray(flagComponent) if err != nil { @@ -70,7 +68,7 @@ var validateCmd = &cobra.Command{ cmd: cmd, env: env, components: componentNames, - cwd: wd, + cwd: cwd, }) objs, err := te.Expand() if err != nil { diff --git a/integration/fixtures/sampleapp/environments/default/.metadata/k.libsonnet b/integration/fixtures/sampleapp/lib/v1.7.0/k.libsonnet similarity index 100% rename from integration/fixtures/sampleapp/environments/default/.metadata/k.libsonnet rename to integration/fixtures/sampleapp/lib/v1.7.0/k.libsonnet diff --git a/integration/fixtures/sampleapp/environments/default/.metadata/k8s.libsonnet b/integration/fixtures/sampleapp/lib/v1.7.0/k8s.libsonnet similarity index 100% rename from integration/fixtures/sampleapp/environments/default/.metadata/k8s.libsonnet rename to integration/fixtures/sampleapp/lib/v1.7.0/k8s.libsonnet diff --git a/integration/fixtures/sampleapp/environments/default/.metadata/swagger.json b/integration/fixtures/sampleapp/lib/v1.7.0/swagger.json similarity index 100% rename from integration/fixtures/sampleapp/environments/default/.metadata/swagger.json rename to integration/fixtures/sampleapp/lib/v1.7.0/swagger.json diff --git a/integration/integration_suite_test.go b/integration/integration_suite_test.go index 76998eadb0f6c94f39cf4bb6a4fea948d5e39d42..d848136eb1d326a90cd5245f8f7785c65033056b 100644 --- a/integration/integration_suite_test.go +++ b/integration/integration_suite_test.go @@ -100,6 +100,7 @@ func runKsonnetWith(flags []string, host, ns string) error { Server: host, }, }, + KubernetesVersion: "v1.7.0", }, }, } diff --git a/metadata/clusterspec.go b/metadata/clusterspec.go deleted file mode 100644 index 57412a7eb6c1fa246feebf28438044c2887a7635..0000000000000000000000000000000000000000 --- a/metadata/clusterspec.go +++ /dev/null @@ -1,88 +0,0 @@ -package metadata - -import ( - "fmt" - "io/ioutil" - "net/http" - "path/filepath" - "strings" - - "github.com/spf13/afero" -) - -const ( - k8sVersionURLTemplate = "https://raw.githubusercontent.com/kubernetes/kubernetes/%s/api/openapi-spec/swagger.json" -) - -func parseClusterSpec(specFlag string, fs afero.Fs) (ClusterSpec, error) { - split := strings.SplitN(specFlag, ":", 2) - if len(split) <= 1 || split[1] == "" { - return nil, fmt.Errorf("Invalid API specification '%s'", specFlag) - } - - switch split[0] { - case "version": - return &clusterSpecVersion{k8sVersion: split[1]}, nil - case "file": - abs, err := filepath.Abs(split[1]) - if err != nil { - return nil, err - } - absPath := AbsPath(abs) - return &clusterSpecFile{specPath: absPath, fs: fs}, nil - case "url": - return &clusterSpecLive{apiServerURL: split[1]}, nil - default: - return nil, fmt.Errorf("Could not parse cluster spec '%s'", specFlag) - } -} - -type clusterSpecFile struct { - specPath AbsPath - fs afero.Fs -} - -func (cs *clusterSpecFile) OpenAPI() ([]byte, error) { - return afero.ReadFile(cs.fs, string(cs.specPath)) -} - -func (cs *clusterSpecFile) Resource() string { - return string(cs.specPath) -} - -type clusterSpecLive struct { - apiServerURL string -} - -func (cs *clusterSpecLive) OpenAPI() ([]byte, error) { - return nil, fmt.Errorf("Initializing from OpenAPI spec in live cluster is not implemented") -} - -func (cs *clusterSpecLive) Resource() string { - return string(cs.apiServerURL) -} - -type clusterSpecVersion struct { - k8sVersion string -} - -func (cs *clusterSpecVersion) OpenAPI() ([]byte, error) { - versionURL := fmt.Sprintf(k8sVersionURLTemplate, cs.k8sVersion) - resp, err := http.Get(versionURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf( - "Recieved status code '%d' when trying to retrieve OpenAPI schema for cluster version '%s' from URL '%s'", - resp.StatusCode, cs.k8sVersion, versionURL) - } - - return ioutil.ReadAll(resp.Body) -} - -func (cs *clusterSpecVersion) Resource() string { - return string(cs.k8sVersion) -} diff --git a/metadata/component.go b/metadata/component.go index 709ee716b5d3331a0db025594a5e34a5e61b0644..b5ff387bf4cb4361152104e5a4ea733f833a0340 100644 --- a/metadata/component.go +++ b/metadata/component.go @@ -23,13 +23,14 @@ import ( param "github.com/ksonnet/ksonnet/metadata/params" "github.com/ksonnet/ksonnet/prototype" + str "github.com/ksonnet/ksonnet/strings" log "github.com/sirupsen/logrus" "github.com/spf13/afero" ) -func (m *manager) ComponentPaths() (AbsPaths, error) { - paths := AbsPaths{} - err := afero.Walk(m.appFS, string(m.componentsPath), func(p string, info os.FileInfo, err error) error { +func (m *manager) ComponentPaths() ([]string, error) { + paths := []string{} + err := afero.Walk(m.appFS, m.componentsPath, func(p string, info os.FileInfo, err error) error { if err != nil { return err } @@ -67,7 +68,7 @@ func (m *manager) CreateComponent(name string, text string, params param.Params, return fmt.Errorf("Component name '%s' is not valid; must not contain punctuation, spaces, or begin or end with a slash", name) } - componentPath := string(appendToAbsPath(m.componentsPath, name)) + componentPath := str.AppendToPath(m.componentsPath, name) switch templateType { case prototype.YAML: componentPath = componentPath + ".yaml" @@ -105,7 +106,7 @@ func (m *manager) DeleteComponent(name string) error { } // Build the new component/params.libsonnet file. - componentParamsFile, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + componentParamsFile, err := afero.ReadFile(m.appFS, m.componentParamsPath) if err != nil { return err } @@ -122,8 +123,8 @@ func (m *manager) DeleteComponent(name string) error { return err } for _, env := range envs { - path := appendToAbsPath(m.environmentsPath, env.Name, paramsFileName) - envParamsFile, err := afero.ReadFile(m.appFS, string(path)) + path := str.AppendToPath(m.environmentsPath, env.Name, paramsFileName) + envParamsFile, err := afero.ReadFile(m.appFS, path) if err != nil { return err } @@ -141,16 +142,16 @@ func (m *manager) DeleteComponent(name string) error { // Remove the references in component/params.libsonnet. log.Debugf("... deleting references in %s", m.componentParamsPath) - err = afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(componentJsonnet), defaultFilePermissions) + err = afero.WriteFile(m.appFS, m.componentParamsPath, []byte(componentJsonnet), defaultFilePermissions) if err != nil { return err } // Remove the component references in each environment's // environment/<env>/params.libsonnet. for _, env := range envs { - path := appendToAbsPath(m.environmentsPath, env.Name, paramsFileName) + path := str.AppendToPath(m.environmentsPath, env.Name, paramsFileName) log.Debugf("... deleting references in %s", path) - err = afero.WriteFile(m.appFS, string(path), []byte(envJsonnets[env.Name]), defaultFilePermissions) + err = afero.WriteFile(m.appFS, path, []byte(envJsonnets[env.Name]), defaultFilePermissions) if err != nil { return err } @@ -172,7 +173,7 @@ func (m *manager) DeleteComponent(name string) error { } func (m *manager) GetComponentParams(component string) (param.Params, error) { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + text, err := afero.ReadFile(m.appFS, m.componentParamsPath) if err != nil { return nil, err } @@ -181,7 +182,7 @@ func (m *manager) GetComponentParams(component string) (param.Params, error) { } func (m *manager) GetAllComponentParams() (map[string]param.Params, error) { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + text, err := afero.ReadFile(m.appFS, m.componentParamsPath) if err != nil { return nil, err } @@ -190,7 +191,7 @@ func (m *manager) GetAllComponentParams() (map[string]param.Params, error) { } func (m *manager) SetComponentParams(component string, params param.Params) error { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + text, err := afero.ReadFile(m.appFS, m.componentParamsPath) if err != nil { return err } @@ -200,7 +201,7 @@ func (m *manager) SetComponentParams(component string, params param.Params) erro return err } - return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(jsonnet), defaultFilePermissions) + return afero.WriteFile(m.appFS, m.componentParamsPath, []byte(jsonnet), defaultFilePermissions) } func (m *manager) findComponentPath(name string) (string, error) { @@ -232,7 +233,7 @@ func (m *manager) findComponentPath(name string) (string, error) { } func (m *manager) writeComponentParams(componentName string, params param.Params) error { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + text, err := afero.ReadFile(m.appFS, m.componentParamsPath) if err != nil { return err } @@ -242,7 +243,7 @@ func (m *manager) writeComponentParams(componentName string, params param.Params return err } - return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(appended), defaultFilePermissions) + return afero.WriteFile(m.appFS, m.componentParamsPath, []byte(appended), defaultFilePermissions) } func genComponentParamsContent() []byte { diff --git a/metadata/component_test.go b/metadata/component_test.go index d5021bcd819ca377f810fcdab8d4df6396ca05ca..4027284e6c61e037b0975b74c772f2fb01b1a1d9 100644 --- a/metadata/component_test.go +++ b/metadata/component_test.go @@ -21,6 +21,8 @@ import ( "sort" "strings" "testing" + + str "github.com/ksonnet/ksonnet/strings" ) const ( @@ -31,42 +33,39 @@ const ( ) func populateComponentPaths(t *testing.T) *manager { - spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS) - if err != nil { - t.Fatalf("Failed to parse cluster spec: %v", err) - } + specFlag := fmt.Sprintf("file:%s", blankSwagger) - appPath := AbsPath(componentsPath) + appPath := componentsPath reg := newMockRegistryManager("incubator") - m, err := initManager("componentPaths", appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS) + m, err := initManager("componentPaths", appPath, &specFlag, &mockAPIServer, &mockNamespace, reg, testFS) if err != nil { t.Fatalf("Failed to init cluster spec: %v", err) } // Create empty app file. - components := appendToAbsPath(appPath, componentsDir) - appFile1 := appendToAbsPath(components, componentFile1) - f1, err := testFS.OpenFile(string(appFile1), os.O_RDONLY|os.O_CREATE, 0777) + components := str.AppendToPath(appPath, componentsDir) + appFile1 := str.AppendToPath(components, componentFile1) + f1, err := testFS.OpenFile(appFile1, os.O_RDONLY|os.O_CREATE, 0777) if err != nil { t.Fatalf("Failed to touch app file '%s'\n%v", appFile1, err) } f1.Close() // Create empty file in a nested directory. - appSubdir := appendToAbsPath(components, componentSubdir) - err = testFS.MkdirAll(string(appSubdir), os.ModePerm) + appSubdir := str.AppendToPath(components, componentSubdir) + err = testFS.MkdirAll(appSubdir, os.ModePerm) if err != nil { t.Fatalf("Failed to create directory '%s'\n%v", appSubdir, err) } - appFile2 := appendToAbsPath(appSubdir, componentFile2) - f2, err := testFS.OpenFile(string(appFile2), os.O_RDONLY|os.O_CREATE, 0777) + appFile2 := str.AppendToPath(appSubdir, componentFile2) + f2, err := testFS.OpenFile(appFile2, os.O_RDONLY|os.O_CREATE, 0777) if err != nil { t.Fatalf("Failed to touch app file '%s'\n%v", appFile1, err) } f2.Close() // Create a directory that won't be listed in the call to `ComponentPaths`. - unlistedDir := string(appendToAbsPath(components, "doNotListMe")) + unlistedDir := str.AppendToPath(components, "doNotListMe") err = testFS.MkdirAll(unlistedDir, os.ModePerm) if err != nil { t.Fatalf("Failed to create directory '%s'\n%v", unlistedDir, err) diff --git a/metadata/environment.go b/metadata/environment.go index e5474b32dafe563bf3215b472e855d477248244f..73900d805600d38b77aa14bf9732030f0ec41b4c 100644 --- a/metadata/environment.go +++ b/metadata/environment.go @@ -22,56 +22,42 @@ import ( "path/filepath" "github.com/ksonnet/ksonnet/metadata/app" + "github.com/ksonnet/ksonnet/metadata/lib" + str "github.com/ksonnet/ksonnet/strings" log "github.com/sirupsen/logrus" "github.com/spf13/afero" - "github.com/ksonnet/ksonnet/generator" param "github.com/ksonnet/ksonnet/metadata/params" ) const ( - defaultEnvName = "default" - metadataDirName = ".metadata" - - // hidden metadata files - schemaFilename = "swagger.json" - extensionsLibFilename = "k.libsonnet" - k8sLibFilename = "k8s.libsonnet" + defaultEnvName = "default" // primary environment files envFileName = "main.jsonnet" paramsFileName = "params.libsonnet" - specFilename = "spec.json" ) var envPaths = []string{ - // metadata Dir.wh - metadataDirName, // environment base override file envFileName, // params file paramsFileName, - // spec file - specFilename, } -func (m *manager) CreateEnvironment(name, server, namespace string, spec ClusterSpec) error { - b, err := spec.OpenAPI() +func (m *manager) CreateEnvironment(name, server, namespace, k8sSpecFlag string) error { + // generate the lib data for this kubernetes version + libManager, err := lib.NewManagerWithSpec(k8sSpecFlag, m.appFS, m.libPath) if err != nil { return err } - kl, err := generator.Ksonnet(b) - if err != nil { - log.Debugf("Failed to write '%s'", specFilename) + if err := libManager.GenerateLibData(); err != nil { return err } - return m.createEnvironment(name, server, namespace, kl.K, kl.K8s, kl.Swagger) -} - -func (m *manager) createEnvironment(name, server, namespace string, extensionsLibData, k8sLibData, specData []byte) error { + // add the environment to the app spec appSpec, err := m.AppSpec() if err != nil { return err @@ -92,55 +78,32 @@ func (m *manager) createEnvironment(name, server, namespace string, extensionsLi log.Infof("Creating environment '%s' with namespace '%s', pointing at server at address '%s'", name, namespace, server) - envPath := appendToAbsPath(m.environmentsPath, name) - err = m.appFS.MkdirAll(string(envPath), defaultFolderPermissions) + envPath := str.AppendToPath(m.environmentsPath, name) + err = m.appFS.MkdirAll(envPath, defaultFolderPermissions) if err != nil { return err } - metadataPath := appendToAbsPath(envPath, metadataDirName) - err = m.appFS.MkdirAll(string(metadataPath), defaultFolderPermissions) - if err != nil { - return err - } - - log.Infof("Generating environment metadata at path '%s'", envPath) - metadata := []struct { - path AbsPath + path string data []byte }{ - { - // schema file - appendToAbsPath(metadataPath, schemaFilename), - specData, - }, - { - // k8s file - appendToAbsPath(metadataPath, k8sLibFilename), - k8sLibData, - }, - { - // extensions file - appendToAbsPath(metadataPath, extensionsLibFilename), - extensionsLibData, - }, { // environment base override file - appendToAbsPath(envPath, envFileName), + str.AppendToPath(envPath, envFileName), m.generateOverrideData(), }, { // params file - appendToAbsPath(envPath, paramsFileName), + str.AppendToPath(envPath, paramsFileName), m.generateParamsData(), }, } for _, a := range metadata { - fileName := path.Base(string(a.path)) + fileName := path.Base(a.path) log.Debugf("Generating '%s', length: %d", fileName, len(a.data)) - if err = afero.WriteFile(m.appFS, string(a.path), a.data, defaultFilePermissions); err != nil { + if err = afero.WriteFile(m.appFS, a.path, a.data, defaultFilePermissions); err != nil { log.Debugf("Failed to write '%s'", fileName) return err } @@ -156,7 +119,7 @@ func (m *manager) createEnvironment(name, server, namespace string, extensionsLi Namespace: namespace, }, }, - // TODO specify k8s version once metadata is moved. + KubernetesVersion: libManager.K8sVersion, }) if err != nil { @@ -177,12 +140,12 @@ func (m *manager) DeleteEnvironment(name string) error { return fmt.Errorf("Environment '%s' does not exist", name) } - envPath := appendToAbsPath(m.environmentsPath, env.Path) + envPath := str.AppendToPath(m.environmentsPath, env.Path) log.Infof("Deleting environment '%s' with metadata at path '%s'", name, envPath) // Remove the directory and all files within the environment path. - err = m.appFS.RemoveAll(string(envPath)) + err = m.appFS.RemoveAll(envPath) if err != nil { log.Debugf("Failed to remove environment directory at path '%s'", envPath) return err @@ -282,9 +245,9 @@ func (m *manager) SetEnvironment(name, desiredName string) error { // reflect the change. // - pathOld := appendToAbsPath(m.environmentsPath, name) - pathNew := appendToAbsPath(m.environmentsPath, desiredName) - exists, err = afero.DirExists(m.appFS, string(pathNew)) + pathOld := str.AppendToPath(m.environmentsPath, name) + pathNew := str.AppendToPath(m.environmentsPath, desiredName) + exists, err = afero.DirExists(m.appFS, pathNew) if err != nil { return err } @@ -294,26 +257,26 @@ func (m *manager) SetEnvironment(name, desiredName string) error { // the check earlier. This is an intermediate directory. // We need to move the file contents. m.tryMvEnvDir(pathOld, pathNew) - } else if filepath.HasPrefix(string(pathNew), string(pathOld)) { + } else if filepath.HasPrefix(pathNew, pathOld) { // the new directory is a child of the old directory -- // rename won't work. - err = m.appFS.MkdirAll(string(pathNew), defaultFolderPermissions) + err = m.appFS.MkdirAll(pathNew, defaultFolderPermissions) if err != nil { return err } m.tryMvEnvDir(pathOld, pathNew) } else { // Need to first create subdirectories that don't exist - intermediatePath := path.Dir(string(pathNew)) - log.Debugf("Moving directory at path '%s' to '%s'", string(pathOld), string(pathNew)) + intermediatePath := path.Dir(pathNew) + log.Debugf("Moving directory at path '%s' to '%s'", pathOld, pathNew) err = m.appFS.MkdirAll(intermediatePath, defaultFolderPermissions) if err != nil { return err } // finally, move the directory - err = m.appFS.Rename(string(pathOld), string(pathNew)) + err = m.appFS.Rename(pathOld, pathNew) if err != nil { - log.Debugf("Failed to move path '%s' to '%s", string(pathOld), string(pathNew)) + log.Debugf("Failed to move path '%s' to '%s", pathOld, pathNew) return err } } @@ -340,8 +303,8 @@ func (m *manager) GetEnvironmentParams(name string) (map[string]param.Params, er } // Get the environment specific params - envParamsPath := appendToAbsPath(m.environmentsPath, name, paramsFileName) - envParamsText, err := afero.ReadFile(m.appFS, string(envParamsPath)) + envParamsPath := str.AppendToPath(m.environmentsPath, name, paramsFileName) + envParamsText, err := afero.ReadFile(m.appFS, envParamsPath) if err != nil { return nil, err } @@ -369,9 +332,9 @@ func (m *manager) SetEnvironmentParams(env, component string, params param.Param return fmt.Errorf("Environment '%s' does not exist", env) } - path := appendToAbsPath(m.environmentsPath, env, paramsFileName) + path := str.AppendToPath(m.environmentsPath, env, paramsFileName) - text, err := afero.ReadFile(m.appFS, string(path)) + text, err := afero.ReadFile(m.appFS, path) if err != nil { return err } @@ -381,7 +344,7 @@ func (m *manager) SetEnvironmentParams(env, component string, params param.Param return err } - err = afero.WriteFile(m.appFS, string(path), []byte(appended), defaultFilePermissions) + err = afero.WriteFile(m.appFS, path, []byte(appended), defaultFilePermissions) if err != nil { return err } @@ -390,10 +353,35 @@ func (m *manager) SetEnvironmentParams(env, component string, params param.Param return nil } -func (m *manager) tryMvEnvDir(dirPathOld, dirPathNew AbsPath) error { +func (m *manager) EnvPaths(env string) (libPath, mainPath, paramsPath string, err error) { + app, err := m.AppSpec() + if err != nil { + return + } + + envSpec, ok := app.GetEnvironmentSpec(env) + if !ok { + err = fmt.Errorf("Environment '%s' does not exist", env) + return + } + + libManager := lib.NewManager(envSpec.KubernetesVersion, m.appFS, m.libPath) + + envPath := str.AppendToPath(m.environmentsPath, env) + + // main.jsonnet file + mainPath = str.AppendToPath(envPath, envFileName) + // params.libsonnet file + paramsPath = str.AppendToPath(envPath, componentParamsFile) + // ksonnet-lib file directory + libPath, err = libManager.GetLibPath() + return +} + +func (m *manager) tryMvEnvDir(dirPathOld, dirPathNew string) error { // first ensure none of these paths exists in the new directory for _, p := range envPaths { - path := string(appendToAbsPath(dirPathNew, p)) + path := str.AppendToPath(dirPathNew, p) if exists, err := afero.Exists(m.appFS, path); err != nil { return err } else if exists { @@ -404,16 +392,16 @@ func (m *manager) tryMvEnvDir(dirPathOld, dirPathNew AbsPath) error { // note: afero and go does not provide simple ways to move the // contents. We'll have to rename them individually. for _, p := range envPaths { - err := m.appFS.Rename(string(appendToAbsPath(dirPathOld, p)), string(appendToAbsPath(dirPathNew, p))) + err := m.appFS.Rename(str.AppendToPath(dirPathOld, p), str.AppendToPath(dirPathNew, p)) if err != nil { return err } } // clean up the old directory if it is empty - if empty, err := afero.IsEmpty(m.appFS, string(dirPathOld)); err != nil { + if empty, err := afero.IsEmpty(m.appFS, dirPathOld); err != nil { return err } else if empty { - return m.appFS.RemoveAll(string(dirPathOld)) + return m.appFS.RemoveAll(dirPathOld) } return nil } @@ -424,7 +412,7 @@ func (m *manager) cleanEmptyParentDirs(name string) error { parentDir := name for parentDir != "." { parentDir = filepath.Dir(parentDir) - parentPath := string(appendToAbsPath(m.environmentsPath, parentDir)) + parentPath := str.AppendToPath(m.environmentsPath, parentDir) isEmpty, err := afero.IsEmpty(m.appFS, parentPath) if err != nil { @@ -449,7 +437,7 @@ func (m *manager) generateOverrideData() []byte { var buf bytes.Buffer buf.WriteString(fmt.Sprintf("local base = import \"%s\";\n", relBaseLibsonnetPath)) - buf.WriteString(fmt.Sprintf("local k = import \"%s\";\n\n", extensionsLibFilename)) + buf.WriteString(fmt.Sprintf("local k = import \"%s\";\n\n", lib.ExtensionsLibFilename)) buf.WriteString("base + {\n") buf.WriteString(" // Insert user-specified overrides here. For example if a component is named \"nginx-deployment\", you might have something like:\n") buf.WriteString(" // \"nginx-deployment\"+: k.deployment.mixin.metadata.labels({foo: \"bar\"})\n") diff --git a/metadata/environment_test.go b/metadata/environment_test.go index beea033ae70cd13c2e2c4b712948cdc212416c94..017ef0ef3457b0fde8e80e5e087dad12d6a89947 100644 --- a/metadata/environment_test.go +++ b/metadata/environment_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/ksonnet/ksonnet/metadata/app" + str "github.com/ksonnet/ksonnet/strings" param "github.com/ksonnet/ksonnet/metadata/params" "github.com/spf13/afero" @@ -44,38 +45,34 @@ func mockEnvironments(t *testing.T, appName string) *manager { } func mockEnvironmentsWith(t *testing.T, appName string, envNames []string) *manager { - spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS) - if err != nil { - t.Fatalf("Failed to parse cluster spec: %v", err) - } + specFlag := fmt.Sprintf("file:%s", blankSwagger) - appPath := AbsPath(appName) reg := newMockRegistryManager("incubator") - m, err := initManager(appName, appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS) + m, err := initManager(appName, appName, &specFlag, &mockAPIServer, &mockNamespace, reg, testFS) if err != nil { t.Fatalf("Failed to init cluster spec: %v", err) } for _, env := range envNames { - envPath := appendToAbsPath(m.environmentsPath, env) - testFS.Mkdir(string(envPath), defaultFolderPermissions) - testDirExists(t, string(envPath)) + envPath := str.AppendToPath(m.environmentsPath, env) + testFS.Mkdir(envPath, defaultFolderPermissions) + testDirExists(t, envPath) - envFilePath := appendToAbsPath(envPath, envFileName) + envFilePath := str.AppendToPath(envPath, envFileName) envFileData := m.generateOverrideData() - err = afero.WriteFile(testFS, string(envFilePath), envFileData, defaultFilePermissions) + err = afero.WriteFile(testFS, envFilePath, envFileData, defaultFilePermissions) if err != nil { t.Fatalf("Could not write file at path: %s", envFilePath) } - testFileExists(t, string(envFilePath)) + testFileExists(t, envFilePath) - paramsPath := appendToAbsPath(envPath, paramsFileName) + paramsPath := str.AppendToPath(envPath, paramsFileName) paramsData := m.generateParamsData() - err = afero.WriteFile(testFS, string(paramsPath), paramsData, defaultFilePermissions) + err = afero.WriteFile(testFS, paramsPath, paramsData, defaultFilePermissions) if err != nil { t.Fatalf("Could not write file at path: %s", paramsPath) } - testFileExists(t, string(paramsPath)) + testFileExists(t, paramsPath) appSpec, err := m.AppSpec() if err != nil { @@ -129,26 +126,26 @@ func TestDeleteEnvironment(t *testing.T) { m := mockEnvironments(t, appName) // Test that both directory and empty parent directory is deleted. - expectedPath := appendToAbsPath(m.environmentsPath, mockEnvName3) + expectedPath := str.AppendToPath(m.environmentsPath, mockEnvName3) parentDir := strings.Split(mockEnvName3, "/")[0] - expectedParentPath := appendToAbsPath(m.environmentsPath, parentDir) + expectedParentPath := str.AppendToPath(m.environmentsPath, parentDir) err := m.DeleteEnvironment(mockEnvName3) if err != nil { t.Fatalf("Expected %s to be deleted but got err:\n %s", mockEnvName3, err) } - testDirNotExists(t, string(expectedPath)) - testDirNotExists(t, string(expectedParentPath)) + testDirNotExists(t, expectedPath) + testDirNotExists(t, expectedParentPath) // Test that only leaf directory is deleted if parent directory is shared - expectedPath = appendToAbsPath(m.environmentsPath, mockEnvName2) + expectedPath = str.AppendToPath(m.environmentsPath, mockEnvName2) parentDir = strings.Split(mockEnvName2, "/")[0] - expectedParentPath = appendToAbsPath(m.environmentsPath, parentDir) + expectedParentPath = str.AppendToPath(m.environmentsPath, parentDir) err = m.DeleteEnvironment(mockEnvName2) if err != nil { t.Fatalf("Expected %s to be deleted but got err:\n %s", mockEnvName3, err) } - testDirNotExists(t, string(expectedPath)) - testDirExists(t, string(expectedParentPath)) + testDirNotExists(t, expectedPath) + testDirExists(t, expectedParentPath) } func TestGetEnvironments(t *testing.T) { @@ -199,11 +196,11 @@ func TestSetEnvironment(t *testing.T) { } // Ensure new env directory is created, and old directory no longer exists. - envPath := appendToAbsPath(AbsPath(appName), environmentsDir) - expectedPathExists := appendToAbsPath(envPath, setName) - expectedPathNotExists := appendToAbsPath(envPath, mockEnvName) - testDirExists(t, string(expectedPathExists)) - testDirNotExists(t, string(expectedPathNotExists)) + envPath := str.AppendToPath(appName, environmentsDir) + expectedPathExists := str.AppendToPath(envPath, setName) + expectedPathNotExists := str.AppendToPath(envPath, mockEnvName) + testDirExists(t, expectedPathExists) + testDirNotExists(t, expectedPathNotExists) // BUG: https://github.com/spf13/afero/issues/141 // we aren't able to test this until the above is fixed. @@ -246,8 +243,8 @@ func TestSetEnvironment(t *testing.T) { t.Fatalf("Could not set '%s', got:\n %s", v.nameOld, err) } // Ensure new env directory is created - expectedPath := appendToAbsPath(AbsPath(v.appName), environmentsDir, v.nameNew) - testDirExists(t, string(expectedPath)) + expectedPath := str.AppendToPath(v.appName, environmentsDir, v.nameNew) + testDirExists(t, expectedPath) } } diff --git a/metadata/interface.go b/metadata/interface.go index d440f60f5def0cfb7c604faa403fa3091dbdfddc..9fe0996a4d33ce15ffcce6bddde62db971aea3ee 100644 --- a/metadata/interface.go +++ b/metadata/interface.go @@ -32,24 +32,16 @@ var appFS afero.Fs var defaultFolderPermissions = os.FileMode(0755) var defaultFilePermissions = os.FileMode(0644) -// AbsPath is an advisory type that represents an absolute path. It is advisory -// in that it is not forced to be absolute, but rather, meant to indicate -// intent, and make code easier to read. -type AbsPath string - -// AbsPaths is a slice of `AbsPath`. -type AbsPaths []string - // Manager abstracts over a ksonnet application's metadata, allowing users to do // things like: create and delete environments; search for prototypes; vendor // libraries; and other non-core-application tasks. type Manager interface { - Root() AbsPath - LibPaths() (libPath, vendorPath AbsPath) - EnvPaths(env string) (metadataPath, mainPath, paramsPath AbsPath) + Root() string + LibPaths() (libPath, vendorPath string) + EnvPaths(env string) (libPath, mainPath, paramsPath string, err error) // Components API. - ComponentPaths() (AbsPaths, error) + ComponentPaths() ([]string, error) GetAllComponents() ([]string, error) CreateComponent(name string, text string, params param.Params, templateType prototype.TemplateType) error DeleteComponent(name string) error @@ -66,7 +58,7 @@ type Manager interface { SetEnvironmentParams(env, component string, params param.Params) error // Environment API. - CreateEnvironment(name, uri, namespace string, spec ClusterSpec) error + CreateEnvironment(name, uri, namespace, spec string) error DeleteEnvironment(name string) error GetEnvironments() (app.EnvironmentSpecs, error) GetEnvironment(name string) (*app.EnvironmentSpec, error) @@ -88,14 +80,12 @@ type Manager interface { // Find will recursively search the current directory and its parents for a // `.ksonnet` folder, which marks the application root. Returns error if there // is no application root. -func Find(path AbsPath) (Manager, error) { +func Find(path string) (Manager, error) { return findManager(path, afero.NewOsFs()) } -// Init will retrieve a cluster API specification, generate a -// capabilities-compliant version of ksonnet-lib, and then generate the -// directory tree for an application. -func Init(name string, rootPath AbsPath, spec ClusterSpec, serverURI, namespace *string) (Manager, error) { +// Init will generate the directory tree for a ksonnet project. +func Init(name, rootPath string, k8sSpecFlag, serverURI, namespace *string) (Manager, error) { // Generate `incubator` registry. We do this before before creating // directory tree, in case the network call fails. const ( @@ -112,24 +102,7 @@ func Init(name string, rootPath AbsPath, spec ClusterSpec, serverURI, namespace return nil, err } - return initManager(name, rootPath, spec, serverURI, namespace, gh, appFS) -} - -// ClusterSpec represents the API supported by some cluster. There are several -// ways to specify a cluster, including: querying the API server, reading an -// OpenAPI spec in some file, or consulting the OpenAPI spec released in a -// specific version of Kubernetes. -type ClusterSpec interface { - OpenAPI() ([]byte, error) - Resource() string // For testing parsing logic. -} - -// ParseClusterSpec will parse a cluster spec flag and output a well-formed -// ClusterSpec object. For example, if the flag is `--version:v1.7.1`, then we -// will output a ClusterSpec representing the cluster specification associated -// with the `v1.7.1` build of Kubernetes. -func ParseClusterSpec(specFlag string) (ClusterSpec, error) { - return parseClusterSpec(specFlag, appFS) + return initManager(name, rootPath, k8sSpecFlag, serverURI, namespace, gh, appFS) } // isValidName returns true if a name (e.g., for an environment) is valid. diff --git a/metadata/lib/clusterspec.go b/metadata/lib/clusterspec.go new file mode 100644 index 0000000000000000000000000000000000000000..6a0e45d0c6d429ff3c5910b0a8ec56a1cd873eae --- /dev/null +++ b/metadata/lib/clusterspec.go @@ -0,0 +1,151 @@ +// Copyright 2017 The ksonnet 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 lib + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + + "github.com/spf13/afero" +) + +const ( + k8sVersionURLTemplate = "https://raw.githubusercontent.com/kubernetes/kubernetes/%s/api/openapi-spec/swagger.json" +) + +// ClusterSpec represents the API supported by some cluster. There are several +// ways to specify a cluster, including: querying the API server, reading an +// OpenAPI spec in some file, or consulting the OpenAPI spec released in a +// specific version of Kubernetes. +type ClusterSpec interface { + OpenAPI() ([]byte, error) + Resource() string // For testing parsing logic. + Version() (string, error) +} + +// ParseClusterSpec will parse a cluster spec flag and output a well-formed +// ClusterSpec object. For example, if the flag is `--version:v1.7.1`, then we +// will output a ClusterSpec representing the cluster specification associated +// with the `v1.7.1` build of Kubernetes. +func ParseClusterSpec(specFlag string, fs afero.Fs) (ClusterSpec, error) { + split := strings.SplitN(specFlag, ":", 2) + if len(split) <= 1 || split[1] == "" { + return nil, fmt.Errorf("Invalid API specification '%s'", specFlag) + } + + switch split[0] { + case "version": + return &clusterSpecVersion{k8sVersion: split[1]}, nil + case "file": + p, err := filepath.Abs(split[1]) + if err != nil { + return nil, err + } + return &clusterSpecFile{specPath: p, fs: fs}, nil + case "url": + return &clusterSpecLive{apiServerURL: split[1]}, nil + default: + return nil, fmt.Errorf("Could not parse cluster spec '%s'", specFlag) + } +} + +type clusterSpecFile struct { + specPath string + fs afero.Fs +} + +func (cs *clusterSpecFile) OpenAPI() ([]byte, error) { + return afero.ReadFile(cs.fs, string(cs.specPath)) +} + +func (cs *clusterSpecFile) Resource() string { + return string(cs.specPath) +} + +func (cs *clusterSpecFile) Version() (string, error) { + // + // Condensed representation of the spec file, containing the minimal + // information necessary to retrieve the spec version. + // + type Info struct { + Version string `json:"version"` + } + + type Spec struct { + Info Info `json:"info"` + } + + bytes, err := cs.OpenAPI() + if err != nil { + return "", err + } + + var spec *Spec + if err := json.Unmarshal(bytes, &spec); err != nil { + return "", err + } + + return spec.Info.Version, nil +} + +type clusterSpecLive struct { + apiServerURL string +} + +func (cs *clusterSpecLive) OpenAPI() ([]byte, error) { + return nil, fmt.Errorf("Initializing from OpenAPI spec in live cluster is not implemented") +} + +func (cs *clusterSpecLive) Resource() string { + return string(cs.apiServerURL) +} + +func (cs *clusterSpecLive) Version() (string, error) { + return "", fmt.Errorf("Retrieving version spec in live cluster is not implemented") +} + +type clusterSpecVersion struct { + k8sVersion string +} + +func (cs *clusterSpecVersion) OpenAPI() ([]byte, error) { + versionURL := fmt.Sprintf(k8sVersionURLTemplate, cs.k8sVersion) + resp, err := http.Get(versionURL) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf( + "Recieved status code '%d' when trying to retrieve OpenAPI schema for cluster version '%s' from URL '%s'", + resp.StatusCode, cs.k8sVersion, versionURL) + } + + return ioutil.ReadAll(resp.Body) +} + +func (cs *clusterSpecVersion) Resource() string { + return string(cs.k8sVersion) +} + +func (cs *clusterSpecVersion) Version() (string, error) { + return string(cs.k8sVersion), nil +} diff --git a/metadata/clusterspec_test.go b/metadata/lib/clusterspec_test.go similarity index 73% rename from metadata/clusterspec_test.go rename to metadata/lib/clusterspec_test.go index f300b128cefdc32dd3362a17361c5894bab4a8fc..5c095cbf0c6aa33cfd41f330a381290ca630abc3 100644 --- a/metadata/clusterspec_test.go +++ b/metadata/lib/clusterspec_test.go @@ -1,4 +1,19 @@ -package metadata +// Copyright 2017 The ksonnet 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 lib import ( "path/filepath" @@ -18,7 +33,7 @@ var successTests = []parseSuccess{ func TestClusterSpecParsingSuccess(t *testing.T) { for _, test := range successTests { - parsed, err := parseClusterSpec(test.input, testFS) + parsed, err := ParseClusterSpec(test.input, testFS) if err != nil { t.Errorf("Failed to parse spec: %v", err) } @@ -66,7 +81,7 @@ var failureTests = []parseFailure{ func TestClusterSpecParsingFailure(t *testing.T) { for _, test := range failureTests { - _, err := parseClusterSpec(test.input, testFS) + _, err := ParseClusterSpec(test.input, testFS) if err == nil { t.Errorf("Cluster spec parse for '%s' should have failed, but succeeded", test.input) } else if msg := err.Error(); msg != test.errorMsg { diff --git a/metadata/lib/lib.go b/metadata/lib/lib.go new file mode 100644 index 0000000000000000000000000000000000000000..df5fd18600e4a236c582b18060a04475566e18f1 --- /dev/null +++ b/metadata/lib/lib.go @@ -0,0 +1,154 @@ +// Copyright 2017 The ksonnet 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 lib + +import ( + "fmt" + "os" + "path" + + str "github.com/ksonnet/ksonnet/strings" + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" + + "github.com/ksonnet/ksonnet/generator" +) + +const ( + schemaFilename = "swagger.json" + k8sLibFilename = "k8s.libsonnet" + + // ExtensionsLibFilename is the file name with the contents of the + // generated ksonnet-lib + ExtensionsLibFilename = "k.libsonnet" +) + +// Manager operates on the files in the lib directory of a ksonnet project. +// This included generating the ksonnet-lib files needed to compile the +// ksonnet (jsonnet) code. +type Manager struct { + // K8sVersion is the Kubernetes version of the Open API spec. + K8sVersion string + + spec ClusterSpec + libPath string + fs afero.Fs +} + +// NewManager creates a crew instance of lib.Manager +func NewManager(k8sVersion string, fs afero.Fs, libPath string) *Manager { + return &Manager{K8sVersion: k8sVersion, fs: fs, libPath: libPath} +} + +// NewManagerWithSpec creates a new instance of lib.Manager with the cluster spec initialized. +func NewManagerWithSpec(k8sSpecFlag string, fs afero.Fs, libPath string) (*Manager, error) { + // + // Generate the program text for ksonnet-lib. + // + spec, err := ParseClusterSpec(k8sSpecFlag, fs) + if err != nil { + return nil, err + } + + version, err := spec.Version() + if err != nil { + return nil, err + } + + return &Manager{K8sVersion: version, fs: fs, libPath: libPath, spec: spec}, nil +} + +// GenerateLibData will generate the swagger and ksonnet-lib files in the lib +// directory of a ksonnet project. The swagger and ksonnet-lib files are +// unique to each Kubernetes API version. If the files already exist for a +// specific Kubernetes API version, they won't be re-generated here. +func (m *Manager) GenerateLibData() error { + if m.spec == nil { + return fmt.Errorf("Uninitialized ClusterSpec") + } + + b, err := m.spec.OpenAPI() + if err != nil { + return err + } + + kl, err := generator.Ksonnet(b) + if err != nil { + return err + } + + versionPath := str.AppendToPath(m.libPath, m.K8sVersion) + ok, err := afero.DirExists(m.fs, string(versionPath)) + if err != nil { + return err + } + if ok { + // Already have lib data for this k8s api version + return nil + } + + err = m.fs.MkdirAll(string(versionPath), os.FileMode(0755)) + if err != nil { + return err + } + + files := []struct { + path string + data []byte + }{ + { + // schema file + str.AppendToPath(versionPath, schemaFilename), + kl.Swagger, + }, + { + // k8s file + str.AppendToPath(versionPath, k8sLibFilename), + kl.K8s, + }, + { + // extensions file + str.AppendToPath(versionPath, ExtensionsLibFilename), + kl.K, + }, + } + + log.Infof("Generating ksonnet-lib data at path '%s'", versionPath) + + for _, a := range files { + fileName := path.Base(string(a.path)) + if err = afero.WriteFile(m.fs, string(a.path), a.data, os.FileMode(0644)); err != nil { + log.Debugf("Failed to write '%s'", fileName) + return err + } + } + + return nil +} + +// GetLibPath returns the absolute path pointing to the directory with the +// metadata files for the provided k8sVersion. +func (m *Manager) GetLibPath() (string, error) { + path := str.AppendToPath(m.libPath, m.K8sVersion) + ok, err := afero.DirExists(m.fs, string(path)) + if err != nil { + return "", err + } + if !ok { + return "", fmt.Errorf("Expected lib directory '%s' but was not found", m.K8sVersion) + } + return path, err +} diff --git a/metadata/lib/lib_test.go b/metadata/lib/lib_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b0792e9de22c7da5b65875164f3f9fb6b040202a --- /dev/null +++ b/metadata/lib/lib_test.go @@ -0,0 +1,88 @@ +// Copyright 2017 The ksonnet 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 lib + +import ( + "fmt" + "os" + "testing" + + "github.com/spf13/afero" + + str "github.com/ksonnet/ksonnet/strings" +) + +const ( + blankSwagger = "/blankSwagger.json" + blankSwaggerData = `{ + "swagger": "2.0", + "info": { + "title": "Kubernetes", + "version": "v1.7.0" + }, + "paths": { + }, + "definitions": { + } +}` + blankK8sLib = `// AUTOGENERATED from the Kubernetes OpenAPI specification. DO NOT MODIFY. +// Kubernetes version: v1.7.0 + +{ + local hidden = { + }, +} +` +) + +var testFS = afero.NewMemMapFs() + +func init() { + afero.WriteFile(testFS, blankSwagger, []byte(blankSwaggerData), os.ModePerm) +} + +func TestGenerateLibData(t *testing.T) { + specFlag := fmt.Sprintf("file:%s", blankSwagger) + libPath := "lib" + + libManager, err := NewManagerWithSpec(specFlag, testFS, libPath) + if err != nil { + t.Fatal("Failed to initialize lib.Manager") + } + + err = libManager.GenerateLibData() + if err != nil { + t.Fatal("Failed to generate lib data") + } + + // Verify contents of lib. + versionPath := str.AppendToPath(libPath, "v1.7.0") + + schemaPath := str.AppendToPath(versionPath, schemaFilename) + bytes, err := afero.ReadFile(testFS, string(schemaPath)) + if err != nil { + t.Fatalf("Failed to read swagger file at '%s':\n%v", schemaPath, err) + } else if actualSwagger := string(bytes); actualSwagger != blankSwaggerData { + t.Fatalf("Expected swagger file at '%s' to have value: '%s', got: '%s'", schemaPath, blankSwaggerData, actualSwagger) + } + + k8sLibPath := str.AppendToPath(versionPath, k8sLibFilename) + k8sLibBytes, err := afero.ReadFile(testFS, string(k8sLibPath)) + if err != nil { + t.Fatalf("Failed to read ksonnet-lib file at '%s':\n%v", k8sLibPath, err) + } else if actualK8sLib := string(k8sLibBytes); actualK8sLib != blankK8sLib { + t.Fatalf("Expected swagger file at '%s' to have value: '%s', got: '%s'", k8sLibPath, blankK8sLib, actualK8sLib) + } +} diff --git a/metadata/manager.go b/metadata/manager.go index 58c2d12f1816bc3f311dbf662e7b9254c9cdca7e..6c751d4545ce17ed9effe5b06b74c2002f3313fc 100644 --- a/metadata/manager.go +++ b/metadata/manager.go @@ -21,18 +21,13 @@ import ( "path" "path/filepath" - "github.com/ksonnet/ksonnet/generator" "github.com/ksonnet/ksonnet/metadata/app" "github.com/ksonnet/ksonnet/metadata/registry" + str "github.com/ksonnet/ksonnet/strings" log "github.com/sirupsen/logrus" "github.com/spf13/afero" ) -func appendToAbsPath(originalPath AbsPath, toAppend ...string) AbsPath { - paths := append([]string{string(originalPath)}, toAppend...) - return AbsPath(path.Join(paths...)) -} - const ( ksonnetDir = ".ksonnet" registriesDir = ksonnetDir + "/registries" @@ -64,26 +59,26 @@ type manager struct { appFS afero.Fs // Application paths. - rootPath AbsPath - ksonnetPath AbsPath - registriesPath AbsPath - libPath AbsPath - componentsPath AbsPath - environmentsPath AbsPath - vendorPath AbsPath - - componentParamsPath AbsPath - baseLibsonnetPath AbsPath - appYAMLPath AbsPath + rootPath string + ksonnetPath string + registriesPath string + libPath string + componentsPath string + environmentsPath string + vendorPath string + + componentParamsPath string + baseLibsonnetPath string + appYAMLPath string // User-level paths. - userKsonnetRootPath AbsPath - pkgSrcCachePath AbsPath + userKsonnetRootPath string + pkgSrcCachePath string } -func findManager(abs AbsPath, appFS afero.Fs) (*manager, error) { +func findManager(p string, appFS afero.Fs) (*manager, error) { var lastBase string - currBase := string(abs) + currBase := p for { currPath := path.Join(currBase, ksonnetDir) @@ -92,7 +87,7 @@ func findManager(abs AbsPath, appFS afero.Fs) (*manager, error) { return nil, err } if exists { - return newManager(AbsPath(currBase), appFS) + return newManager(currBase, appFS) } lastBase = currBase @@ -103,30 +98,12 @@ func findManager(abs AbsPath, appFS afero.Fs) (*manager, error) { } } -func initManager(name string, rootPath AbsPath, spec ClusterSpec, serverURI, namespace *string, incubatorReg registry.Manager, appFS afero.Fs) (*manager, error) { +func initManager(name, rootPath string, k8sSpecFlag, serverURI, namespace *string, incubatorReg registry.Manager, appFS afero.Fs) (*manager, error) { m, err := newManager(rootPath, appFS) if err != nil { return nil, err } - // - // Generate the program text for ksonnet-lib. - // - // IMPLEMENTATION NOTE: We get the cluster specification and generate - // ksonnet-lib before initializing the directory structure so that failure of - // either (e.g., GET'ing the spec from a live cluster returns 404) does not - // result in a partially-initialized directory structure. - // - b, err := spec.OpenAPI() - if err != nil { - return nil, err - } - - kl, err := generator.Ksonnet(b) - if err != nil { - return nil, err - } - // Retrieve `registry.yaml`. registryYAMLData, err := generateRegistryYAMLData(incubatorReg) if err != nil { @@ -154,7 +131,7 @@ func initManager(name string, rootPath AbsPath, spec ClusterSpec, serverURI, nam // Initialize environment, and cache specification data. if serverURI != nil { - err := m.createEnvironment(defaultEnvName, *serverURI, *namespace, kl.K, kl.K8s, kl.Swagger) + err := m.CreateEnvironment(defaultEnvName, *serverURI, *namespace, *k8sSpecFlag) if err != nil { return nil, errorOnCreateFailure(name, err) } @@ -170,64 +147,51 @@ func initManager(name string, rootPath AbsPath, spec ClusterSpec, serverURI, nam return m, nil } -func newManager(rootPath AbsPath, appFS afero.Fs) (*manager, error) { +func newManager(rootPath string, appFS afero.Fs) (*manager, error) { usr, err := user.Current() if err != nil { return nil, err } - userRootPath := appendToAbsPath(AbsPath(usr.HomeDir), userKsonnetRootDir) + userRootPath := str.AppendToPath(usr.HomeDir, userKsonnetRootDir) return &manager{ appFS: appFS, // Application paths. rootPath: rootPath, - ksonnetPath: appendToAbsPath(rootPath, ksonnetDir), - registriesPath: appendToAbsPath(rootPath, registriesDir), - libPath: appendToAbsPath(rootPath, libDir), - componentsPath: appendToAbsPath(rootPath, componentsDir), - environmentsPath: appendToAbsPath(rootPath, environmentsDir), - vendorPath: appendToAbsPath(rootPath, vendorDir), + ksonnetPath: str.AppendToPath(rootPath, ksonnetDir), + registriesPath: str.AppendToPath(rootPath, registriesDir), + libPath: str.AppendToPath(rootPath, libDir), + componentsPath: str.AppendToPath(rootPath, componentsDir), + environmentsPath: str.AppendToPath(rootPath, environmentsDir), + vendorPath: str.AppendToPath(rootPath, vendorDir), - componentParamsPath: appendToAbsPath(rootPath, componentsDir, componentParamsFile), - baseLibsonnetPath: appendToAbsPath(rootPath, environmentsDir, baseLibsonnetFile), - appYAMLPath: appendToAbsPath(rootPath, appYAMLFile), + componentParamsPath: str.AppendToPath(rootPath, componentsDir, componentParamsFile), + baseLibsonnetPath: str.AppendToPath(rootPath, environmentsDir, baseLibsonnetFile), + appYAMLPath: str.AppendToPath(rootPath, appYAMLFile), // User-level paths. userKsonnetRootPath: userRootPath, - pkgSrcCachePath: appendToAbsPath(userRootPath, pkgSrcCacheDir), + pkgSrcCachePath: str.AppendToPath(userRootPath, pkgSrcCacheDir), }, nil } -func (m *manager) Root() AbsPath { +func (m *manager) Root() string { return m.rootPath } -func (m *manager) LibPaths() (libPath, vendorPath AbsPath) { +func (m *manager) LibPaths() (libPath, vendorPath string) { return m.libPath, m.vendorPath } -func (m *manager) EnvPaths(env string) (metadataPath, mainPath, paramsPath AbsPath) { - envPath := appendToAbsPath(m.environmentsPath, env) - - // .metadata directory - metadataPath = appendToAbsPath(envPath, metadataDirName) - // main.jsonnet file - mainPath = appendToAbsPath(envPath, envFileName) - // params.libsonnet file - paramsPath = appendToAbsPath(envPath, componentParamsFile) - - return -} - func (m *manager) createUserDirTree() error { - dirPaths := []AbsPath{ + dirPaths := []string{ m.userKsonnetRootPath, m.pkgSrcCachePath, } for _, p := range dirPaths { - if err := m.appFS.MkdirAll(string(p), defaultFolderPermissions); err != nil { + if err := m.appFS.MkdirAll(p, defaultFolderPermissions); err != nil { return err } } @@ -236,14 +200,14 @@ func (m *manager) createUserDirTree() error { } func (m *manager) createAppDirTree(name string, appYAMLData, baseLibData []byte, gh registry.Manager) error { - exists, err := afero.DirExists(m.appFS, string(m.rootPath)) + exists, err := afero.DirExists(m.appFS, m.rootPath) if err != nil { return fmt.Errorf("Could not check existance of directory '%s':\n%v", m.rootPath, err) } else if exists { return fmt.Errorf("Could not create app; directory '%s' already exists", m.rootPath) } - dirPaths := []AbsPath{ + dirPaths := []string{ m.rootPath, m.ksonnetPath, m.registriesPath, @@ -262,7 +226,7 @@ func (m *manager) createAppDirTree(name string, appYAMLData, baseLibData []byte, } filePaths := []struct { - path AbsPath + path string content []byte }{ { @@ -285,7 +249,7 @@ func (m *manager) createAppDirTree(name string, appYAMLData, baseLibData []byte, for _, f := range filePaths { log.Debugf("Creating file '%s'", f.path) - if err := afero.WriteFile(m.appFS, string(f.path), f.content, defaultFilePermissions); err != nil { + if err := afero.WriteFile(m.appFS, f.path, f.content, defaultFilePermissions); err != nil { return err } } diff --git a/metadata/manager_test.go b/metadata/manager_test.go index 254bced3175f2a3867dbb3031c131d215bf0b659..a8f02018acbd50f6fee94139c6fa520eaaa3de12 100644 --- a/metadata/manager_test.go +++ b/metadata/manager_test.go @@ -21,6 +21,7 @@ import ( "path" "testing" + str "github.com/ksonnet/ksonnet/strings" "github.com/spf13/afero" ) @@ -54,21 +55,18 @@ func init() { } func TestInitSuccess(t *testing.T) { - spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS) - if err != nil { - t.Fatalf("Failed to parse cluster spec: %v", err) - } + specFlag := fmt.Sprintf("file:%s", blankSwagger) - appPath := AbsPath("/fromEmptySwagger") + appPath := "/fromEmptySwagger" reg := newMockRegistryManager("incubator") - _, err = initManager("fromEmptySwagger", appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS) + _, err := initManager("fromEmptySwagger", appPath, &specFlag, &mockAPIServer, &mockNamespace, reg, testFS) if err != nil { t.Fatalf("Failed to init cluster spec: %v", err) } // Verify path locations. - defaultEnvDir := appendToAbsPath(environmentsDir, defaultEnvName) - paths := []AbsPath{ + defaultEnvDir := str.AppendToPath(environmentsDir, defaultEnvName) + paths := []string{ ksonnetDir, libDir, componentsDir, @@ -78,8 +76,8 @@ func TestInitSuccess(t *testing.T) { } for _, p := range paths { - path := appendToAbsPath(appPath, string(p)) - exists, err := afero.DirExists(testFS, string(path)) + path := str.AppendToPath(appPath, p) + exists, err := afero.DirExists(testFS, path) if err != nil { t.Fatalf("Expected to create directory '%s', but failed:\n%v", p, err) } else if !exists { @@ -87,7 +85,7 @@ func TestInitSuccess(t *testing.T) { } } - paths = []AbsPath{ + paths = []string{ pkgSrcCacheDir, } @@ -95,11 +93,11 @@ func TestInitSuccess(t *testing.T) { if err != nil { t.Fatalf("Could not get user information:\n%v", err) } - userRootPath := appendToAbsPath(AbsPath(usr.HomeDir), userKsonnetRootDir) + userRootPath := str.AppendToPath(usr.HomeDir, userKsonnetRootDir) for _, p := range paths { - path := appendToAbsPath(userRootPath, string(p)) - exists, err := afero.DirExists(testFS, string(path)) + path := str.AppendToPath(userRootPath, p) + exists, err := afero.DirExists(testFS, path) if err != nil { t.Fatalf("Expected to create directory '%s', but failed:\n%v", p, err) } else if !exists { @@ -108,59 +106,34 @@ func TestInitSuccess(t *testing.T) { } // Verify contents of metadata. - envPath := appendToAbsPath(appPath, string(environmentsDir)) - metadataPath := appendToAbsPath(appPath, string(defaultEnvDir), string(metadataDirName)) - - schemaPath := appendToAbsPath(metadataPath, schemaFilename) - bytes, err := afero.ReadFile(testFS, string(schemaPath)) - if err != nil { - t.Fatalf("Failed to read swagger file at '%s':\n%v", schemaPath, err) - } else if actualSwagger := string(bytes); actualSwagger != blankSwaggerData { - t.Fatalf("Expected swagger file at '%s' to have value: '%s', got: '%s'", schemaPath, blankSwaggerData, actualSwagger) - } - - k8sLibPath := appendToAbsPath(metadataPath, k8sLibFilename) - k8sLibBytes, err := afero.ReadFile(testFS, string(k8sLibPath)) - if err != nil { - t.Fatalf("Failed to read ksonnet-lib file at '%s':\n%v", k8sLibPath, err) - } else if actualK8sLib := string(k8sLibBytes); actualK8sLib != blankK8sLib { - t.Fatalf("Expected swagger file at '%s' to have value: '%s', got: '%s'", k8sLibPath, blankK8sLib, actualK8sLib) - } - - extensionsLibPath := appendToAbsPath(metadataPath, extensionsLibFilename) - extensionsLibBytes, err := afero.ReadFile(testFS, string(extensionsLibPath)) - if err != nil { - t.Fatalf("Failed to read ksonnet-lib file at '%s':\n%v", extensionsLibPath, err) - } else if string(extensionsLibBytes) == "" { - t.Fatalf("Expected extension library file at '%s' to be non-empty", extensionsLibPath) - } + envPath := str.AppendToPath(appPath, environmentsDir) - componentParamsPath := appendToAbsPath(appPath, string(componentsDir), componentParamsFile) - componentParamsBytes, err := afero.ReadFile(testFS, string(componentParamsPath)) + componentParamsPath := str.AppendToPath(appPath, componentsDir, componentParamsFile) + componentParamsBytes, err := afero.ReadFile(testFS, componentParamsPath) if err != nil { t.Fatalf("Failed to read params.libsonnet file at '%s':\n%v", componentParamsPath, err) } else if len(componentParamsBytes) == 0 { t.Fatalf("Expected params.libsonnet at '%s' to be non-empty", componentParamsPath) } - baseLibsonnetPath := appendToAbsPath(envPath, baseLibsonnetFile) - baseLibsonnetBytes, err := afero.ReadFile(testFS, string(baseLibsonnetPath)) + baseLibsonnetPath := str.AppendToPath(envPath, baseLibsonnetFile) + baseLibsonnetBytes, err := afero.ReadFile(testFS, baseLibsonnetPath) if err != nil { t.Fatalf("Failed to read base.libsonnet file at '%s':\n%v", baseLibsonnetPath, err) } else if len(baseLibsonnetBytes) == 0 { t.Fatalf("Expected base.libsonnet at '%s' to be non-empty", baseLibsonnetPath) } - appYAMLPath := appendToAbsPath(appPath, appYAMLFile) - appYAMLBytes, err := afero.ReadFile(testFS, string(appYAMLPath)) + appYAMLPath := str.AppendToPath(appPath, appYAMLFile) + appYAMLBytes, err := afero.ReadFile(testFS, appYAMLPath) if err != nil { t.Fatalf("Failed to read app.yaml file at '%s':\n%v", appYAMLPath, err) } else if len(appYAMLBytes) == 0 { t.Fatalf("Expected app.yaml at '%s' to be non-empty", appYAMLPath) } - registryYAMLPath := appendToAbsPath(appPath, registriesDir, "incubator", "master.yaml") - registryYAMLBytes, err := afero.ReadFile(testFS, string(registryYAMLPath)) + registryYAMLPath := str.AppendToPath(appPath, registriesDir, "incubator", "master.yaml") + registryYAMLBytes, err := afero.ReadFile(testFS, registryYAMLPath) if err != nil { t.Fatalf("Failed to read registry.yaml file at '%s':\n%v", registryYAMLPath, err) } else if len(registryYAMLBytes) == 0 { @@ -169,7 +142,7 @@ func TestInitSuccess(t *testing.T) { } func TestFindSuccess(t *testing.T) { - findSuccess := func(t *testing.T, appDir, currDir AbsPath) { + findSuccess := func(t *testing.T, appDir, currDir string) { m, err := findManager(currDir, testFS) if err != nil { t.Fatalf("Failed to find manager at path '%s':\n%v", currDir, err) @@ -178,26 +151,23 @@ func TestFindSuccess(t *testing.T) { } } - spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS) - if err != nil { - t.Fatalf("Failed to parse cluster spec: %v", err) - } + specFlag := fmt.Sprintf("file:%s", blankSwagger) - appPath := AbsPath("/findSuccess") + appPath := "/findSuccess" reg := newMockRegistryManager("incubator") - _, err = initManager("findSuccess", appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS) + _, err := initManager("findSuccess", appPath, &specFlag, &mockAPIServer, &mockNamespace, reg, testFS) if err != nil { t.Fatalf("Failed to init cluster spec: %v", err) } findSuccess(t, appPath, appPath) - components := appendToAbsPath(appPath, componentsDir) + components := str.AppendToPath(appPath, componentsDir) findSuccess(t, appPath, components) // Create empty app file. - appFile := appendToAbsPath(components, "app.jsonnet") - f, err := testFS.OpenFile(string(appFile), os.O_RDONLY|os.O_CREATE, 0777) + appFile := str.AppendToPath(components, "app.jsonnet") + f, err := testFS.OpenFile(appFile, os.O_RDONLY|os.O_CREATE, 0777) if err != nil { t.Fatalf("Failed to touch app file '%s'\n%v", appFile, err) } @@ -213,36 +183,35 @@ func TestLibPaths(t *testing.T) { m := mockEnvironments(t, appName) libPath, vendorPath := m.LibPaths() - if string(libPath) != expectedLibPath { + if libPath != expectedLibPath { t.Fatalf("Expected lib path to be:\n '%s'\n, got:\n '%s'", expectedLibPath, libPath) } - if string(vendorPath) != expectedVendorPath { + if vendorPath != expectedVendorPath { t.Fatalf("Expected vendor lib path to be:\n '%s'\n, got:\n '%s'", expectedVendorPath, vendorPath) } } func TestEnvPaths(t *testing.T) { appName := "test-env-paths" - expectedMetadataPath := path.Join(appName, environmentsDir, mockEnvName, metadataDirName) expectedMainPath := path.Join(appName, environmentsDir, mockEnvName, envFileName) expectedParamsPath := path.Join(appName, environmentsDir, mockEnvName, paramsFileName) m := mockEnvironments(t, appName) - metadataPath, mainPath, paramsPath := m.EnvPaths(mockEnvName) - - if string(metadataPath) != expectedMetadataPath { - t.Fatalf("Expected environment metadata dir path to be:\n '%s'\n, got:\n '%s'", expectedMetadataPath, metadataPath) + _, mainPath, paramsPath, err := m.EnvPaths(mockEnvName) + if err != nil { + t.Fatalf("Failure retrieving EnvPaths") } - if string(mainPath) != expectedMainPath { + + if mainPath != expectedMainPath { t.Fatalf("Expected environment main path to be:\n '%s'\n, got:\n '%s'", expectedMainPath, mainPath) } - if string(paramsPath) != expectedParamsPath { + if paramsPath != expectedParamsPath { t.Fatalf("Expected environment params path to be:\n '%s'\n, got:\n '%s'", expectedParamsPath, paramsPath) } } func TestFindFailure(t *testing.T) { - findFailure := func(t *testing.T, currDir AbsPath) { + findFailure := func(t *testing.T, currDir string) { _, err := findManager(currDir, testFS) if err == nil { t.Fatalf("Expected to fail to find ksonnet app in '%s', but succeeded", currDir) @@ -255,20 +224,17 @@ func TestFindFailure(t *testing.T) { } func TestDoubleNewFailure(t *testing.T) { - spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS) - if err != nil { - t.Fatalf("Failed to parse cluster spec: %v", err) - } + specFlag := fmt.Sprintf("file:%s", blankSwagger) - appPath := AbsPath("/doubleNew") + appPath := "/doubleNew" reg := newMockRegistryManager("incubator") - _, err = initManager("doubleNew", appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS) + _, err := initManager("doubleNew", appPath, &specFlag, &mockAPIServer, &mockNamespace, reg, testFS) if err != nil { t.Fatalf("Failed to init cluster spec: %v", err) } targetErr := fmt.Sprintf("Could not create app; directory '%s' already exists", appPath) - _, err = initManager("doubleNew", appPath, spec, &mockAPIServer, &mockNamespace, reg, testFS) + _, err = initManager("doubleNew", appPath, &specFlag, &mockAPIServer, &mockNamespace, reg, testFS) if err == nil || err.Error() != targetErr { t.Fatalf("Expected to fail to create app with message '%s', got '%s'", targetErr, err.Error()) } diff --git a/metadata/params/params.go b/metadata/params/params.go index 20a10ab105d90587396cbfd062cf08120e8f9570..da12d76f89dd78fa6830eb065bc4f1aedc09d204 100644 --- a/metadata/params/params.go +++ b/metadata/params/params.go @@ -22,7 +22,7 @@ import ( "strconv" "strings" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" "github.com/google/go-jsonnet/ast" "github.com/google/go-jsonnet/parser" @@ -172,7 +172,7 @@ func writeParams(indent int, params Params) string { buffer.WriteString("\n") for i, key := range keys { param := params[key] - key := utils.QuoteNonASCII(key) + key := str.QuoteNonASCII(key) if strings.HasPrefix(param, "|||\n") { // every line in a block string needs to be indented @@ -245,7 +245,7 @@ func appendComponent(component, snippet string, params Params) (string, error) { // Create the jsonnet resembling the component params var buffer bytes.Buffer - buffer.WriteString(" " + utils.QuoteNonASCII(component) + ": {") + buffer.WriteString(" " + str.QuoteNonASCII(component) + ": {") buffer.WriteString(writeParams(6, params)) buffer.WriteString(" },") @@ -362,7 +362,7 @@ func setEnvironmentParams(component, snippet string, params Params) (string, err lines := strings.Split(snippet, "\n") if !hasComponent { var buffer bytes.Buffer - buffer.WriteString(fmt.Sprintf("\n %s +: {", utils.QuoteNonASCII(component))) + buffer.WriteString(fmt.Sprintf("\n %s +: {", str.QuoteNonASCII(component))) buffer.WriteString(writeParams(6, params)) buffer.WriteString(" },\n") paramsSnippet = buffer.String() diff --git a/metadata/registry.go b/metadata/registry.go index 42e6a54432d4a121e046a5ce585e572b792d6e09..7c8f7652430c91b70fa1fabb088ef6a16be339fc 100644 --- a/metadata/registry.go +++ b/metadata/registry.go @@ -9,6 +9,7 @@ import ( "github.com/ksonnet/ksonnet/metadata/parts" "github.com/ksonnet/ksonnet/metadata/registry" "github.com/ksonnet/ksonnet/prototype" + str "github.com/ksonnet/ksonnet/strings" log "github.com/sirupsen/logrus" "github.com/spf13/afero" ) @@ -48,7 +49,7 @@ func (m *manager) AddRegistry(name, protocol, uri, version string) (*registry.Sp return nil, err } - err = afero.WriteFile(m.appFS, string(m.appYAMLPath), specBytes, defaultFilePermissions) + err = afero.WriteFile(m.appFS, m.appYAMLPath, specBytes, defaultFilePermissions) if err != nil { return nil, err } @@ -118,8 +119,8 @@ func (m *manager) GetDependency(libName string) (*parts.Spec, error) { return nil, fmt.Errorf("Library '%s' is not a dependency in current ksonnet app", libName) } - partsYAMLPath := appendToAbsPath(m.vendorPath, libRef.Registry, libName, partsYAMLFile) - partsBytes, err := afero.ReadFile(m.appFS, string(partsYAMLPath)) + partsYAMLPath := str.AppendToPath(m.vendorPath, libRef.Registry, libName, partsYAMLFile) + partsBytes, err := afero.ReadFile(m.appFS, partsYAMLPath) if err != nil { return nil, err } @@ -166,18 +167,18 @@ func (m *manager) CacheDependency(registryName, libID, libName, libVersion strin // Get all directories and files first, then write to disk. This // protects us from failing with a half-cached dependency because of // a network failure. - directories := []AbsPath{} - files := map[AbsPath][]byte{} + directories := []string{} + files := map[string][]byte{} parts, libRef, err := registryManager.ResolveLibrary( libID, libName, libVersion, func(relPath string, contents []byte) error { - files[appendToAbsPath(m.vendorPath, relPath)] = contents + files[str.AppendToPath(m.vendorPath, relPath)] = contents return nil }, func(relPath string) error { - directories = append(directories, appendToAbsPath(m.vendorPath, relPath)) + directories = append(directories, str.AppendToPath(m.vendorPath, relPath)) return nil }) if err != nil { @@ -195,18 +196,18 @@ func (m *manager) CacheDependency(registryName, libID, libName, libVersion strin log.Infof("Retrieved %d files", len(files)) for _, dir := range directories { - if err := m.appFS.MkdirAll(string(dir), defaultFolderPermissions); err != nil { + if err := m.appFS.MkdirAll(dir, defaultFolderPermissions); err != nil { return nil, err } } for path, content := range files { - if err := afero.WriteFile(m.appFS, string(path), content, defaultFilePermissions); err != nil { + if err := afero.WriteFile(m.appFS, path, content, defaultFilePermissions); err != nil { return nil, err } } - err = afero.WriteFile(m.appFS, string(m.appYAMLPath), appSpecData, defaultFilePermissions) + err = afero.WriteFile(m.appFS, m.appYAMLPath, appSpecData, defaultFilePermissions) if err != nil { return nil, err } @@ -217,7 +218,7 @@ func (m *manager) CacheDependency(registryName, libID, libName, libVersion strin func (m *manager) GetPrototypesForDependency(registryName, libID string) (prototype.SpecificationSchemas, error) { // TODO: Remove `registryName` when we flatten vendor/. specs := prototype.SpecificationSchemas{} - protos := string(appendToAbsPath(m.vendorPath, registryName, libID, "prototypes")) + protos := str.AppendToPath(m.vendorPath, registryName, libID, "prototypes") exists, err := afero.DirExists(m.appFS, protos) if err != nil { return nil, err @@ -270,12 +271,12 @@ func (m *manager) GetAllPrototypes() (prototype.SpecificationSchemas, error) { return specs, nil } -func (m *manager) registryDir(regManager registry.Manager) AbsPath { - return appendToAbsPath(m.registriesPath, regManager.RegistrySpecDir()) +func (m *manager) registryDir(regManager registry.Manager) string { + return str.AppendToPath(m.registriesPath, regManager.RegistrySpecDir()) } -func (m *manager) registryPath(regManager registry.Manager) AbsPath { - return appendToAbsPath(m.registriesPath, regManager.RegistrySpecFilePath()) +func (m *manager) registryPath(regManager registry.Manager) string { + return str.AppendToPath(m.registriesPath, regManager.RegistrySpecFilePath()) } func (m *manager) getRegistryManager(registryName string) (registry.Manager, string, error) { @@ -312,14 +313,13 @@ func (m *manager) getRegistryManagerFor(registryRefSpec *app.RegistryRefSpec) (r return manager, protocol, nil } -func (m *manager) registrySpecFromFile(path AbsPath) (*registry.Spec, bool, error) { - registrySpecFile := string(path) - exists, err := afero.Exists(m.appFS, registrySpecFile) +func (m *manager) registrySpecFromFile(path string) (*registry.Spec, bool, error) { + exists, err := afero.Exists(m.appFS, path) if err != nil { return nil, false, err } - isDir, err := afero.IsDir(m.appFS, registrySpecFile) + isDir, err := afero.IsDir(m.appFS, path) if err != nil { return nil, false, err } @@ -328,7 +328,7 @@ func (m *manager) registrySpecFromFile(path AbsPath) (*registry.Spec, bool, erro // fine, most filesystems allow you to have a directory and file of // the same name. if exists && !isDir { - registrySpecBytes, err := afero.ReadFile(m.appFS, registrySpecFile) + registrySpecBytes, err := afero.ReadFile(m.appFS, path) if err != nil { return nil, false, err } @@ -362,13 +362,13 @@ func (m *manager) getOrCacheRegistry(gh registry.Manager) (*registry.Spec, error // NOTE: We call mkdir after getting the registry spec, since a // network call might fail and leave this half-initialized empty // directory. - registrySpecDir := appendToAbsPath(m.registriesPath, gh.RegistrySpecDir()) - err = m.appFS.MkdirAll(string(registrySpecDir), defaultFolderPermissions) + registrySpecDir := str.AppendToPath(m.registriesPath, gh.RegistrySpecDir()) + err = m.appFS.MkdirAll(registrySpecDir, defaultFolderPermissions) if err != nil { return nil, err } - err = afero.WriteFile(m.appFS, string(registrySpecFile), registrySpecBytes, defaultFilePermissions) + err = afero.WriteFile(m.appFS, registrySpecFile, registrySpecBytes, defaultFilePermissions) if err != nil { return nil, err } diff --git a/pkg/kubecfg/apply.go b/pkg/kubecfg/apply.go index 86badd53fd19f5c0324f7bfa323961514d182662..df6b3dcf0de95a0250a1695f2a4ae9abe21bcf37 100644 --- a/pkg/kubecfg/apply.go +++ b/pkg/kubecfg/apply.go @@ -5,6 +5,7 @@ import ( "fmt" "sort" + "github.com/ksonnet/ksonnet/utils" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -17,9 +18,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" - - "github.com/ksonnet/ksonnet/metadata" - "github.com/ksonnet/ksonnet/utils" ) const ( @@ -51,7 +49,8 @@ type ApplyCmd struct { DryRun bool } -func (c ApplyCmd) Run(apiObjects []*unstructured.Unstructured, wd metadata.AbsPath) error { +// Run applies the components to the designated environment cluster. +func (c ApplyCmd) Run(apiObjects []*unstructured.Unstructured, wd string) error { dryRunText := "" if c.DryRun { dryRunText = " (dry-run)" diff --git a/pkg/kubecfg/component.go b/pkg/kubecfg/component.go index 0c4fb9e994f8c0664f3bc114c39917c741504043..88a275507699ec30961fa224c55f0ecb9cfa6125 100644 --- a/pkg/kubecfg/component.go +++ b/pkg/kubecfg/component.go @@ -21,7 +21,7 @@ import ( "sort" "strings" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" ) const ( @@ -85,7 +85,7 @@ func printComponents(out io.Writer, components []string) (string, error) { rows = append(rows, []string{component}) } - formatted, err := utils.PadRows(rows) + formatted, err := str.PadRows(rows) if err != nil { return "", err } diff --git a/pkg/kubecfg/env.go b/pkg/kubecfg/env.go index 67e2643980af2704272818457b3e54b5a5f134b9..c990fda88d3fda2e6a0d4414eebe0ced8abd60bb 100644 --- a/pkg/kubecfg/env.go +++ b/pkg/kubecfg/env.go @@ -23,29 +23,21 @@ import ( "github.com/ksonnet/ksonnet/metadata/app" - log "github.com/sirupsen/logrus" - "github.com/ksonnet/ksonnet/metadata" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" ) type EnvAddCmd struct { name string server string namespace string + spec string - spec metadata.ClusterSpec manager metadata.Manager } func NewEnvAddCmd(name, server, namespace, specFlag string, manager metadata.Manager) (*EnvAddCmd, error) { - spec, err := metadata.ParseClusterSpec(specFlag) - if err != nil { - return nil, err - } - log.Debugf("Generating ksonnetLib data with spec: %s", specFlag) - - return &EnvAddCmd{name: name, server: server, namespace: namespace, spec: spec, manager: manager}, nil + return &EnvAddCmd{name: name, server: server, namespace: namespace, spec: specFlag, manager: manager}, nil } func (c *EnvAddCmd) Run() error { @@ -114,7 +106,7 @@ func (c *EnvListCmd) Run(out io.Writer) error { } } - formattedEnvsList, err := utils.PadRows(rows) + formattedEnvsList, err := str.PadRows(rows) if err != nil { return err } diff --git a/pkg/kubecfg/init.go b/pkg/kubecfg/init.go index 9189374b748a390d0035fdfcdb73bd3c65725362..f61ddea0b07c7d944492feccb5dc74c4ec00a5d7 100644 --- a/pkg/kubecfg/init.go +++ b/pkg/kubecfg/init.go @@ -6,27 +6,19 @@ import ( ) type InitCmd struct { - name string - rootPath metadata.AbsPath - spec metadata.ClusterSpec - serverURI *string - namespace *string + name string + rootPath string + k8sSpecFlag *string + serverURI *string + namespace *string } -func NewInitCmd(name string, rootPath metadata.AbsPath, specFlag string, serverURI, namespace *string) (*InitCmd, error) { - // NOTE: We're taking `rootPath` here as an absolute path (rather than a partial path we expand to an absolute path) - // to make it more testable. - - spec, err := metadata.ParseClusterSpec(specFlag) - if err != nil { - return nil, err - } - - return &InitCmd{name: name, rootPath: rootPath, spec: spec, serverURI: serverURI, namespace: namespace}, nil +func NewInitCmd(name, rootPath string, k8sSpecFlag, serverURI, namespace *string) (*InitCmd, error) { + return &InitCmd{name: name, rootPath: rootPath, k8sSpecFlag: k8sSpecFlag, serverURI: serverURI, namespace: namespace}, nil } func (c *InitCmd) Run() error { - _, err := metadata.Init(c.name, c.rootPath, c.spec, c.serverURI, c.namespace) + _, err := metadata.Init(c.name, c.rootPath, c.k8sSpecFlag, c.serverURI, c.namespace) if err == nil { log.Info("ksonnet app successfully created! Next, try creating a component with `ks generate`.") } diff --git a/pkg/kubecfg/param.go b/pkg/kubecfg/param.go index f52c677131c03302838246f68dc504e8702b27e8..bcebf13502ff2482ca298fc1b5faa4a47008250d 100644 --- a/pkg/kubecfg/param.go +++ b/pkg/kubecfg/param.go @@ -24,7 +24,7 @@ import ( "strings" param "github.com/ksonnet/ksonnet/metadata/params" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" "github.com/fatih/color" log "github.com/sirupsen/logrus" @@ -167,7 +167,7 @@ func outputParamsFor(component string, params param.Params, out io.Writer) error rows = append(rows, []string{k, params[k]}) } - formatted, err := utils.PadRows(rows) + formatted, err := str.PadRows(rows) if err != nil { return err } @@ -194,7 +194,7 @@ func outputParams(params map[string]param.Params, out io.Writer) error { } } - formatted, err := utils.PadRows(rows) + formatted, err := str.PadRows(rows) if err != nil { return err } diff --git a/pkg/kubecfg/shared.go b/pkg/kubecfg/shared.go index dba80a948e5488d566cc92d4d43c0f3005e75889..57efd0ed9bf34dd9bd5d0441de1cf6e815ef766e 100644 --- a/pkg/kubecfg/shared.go +++ b/pkg/kubecfg/shared.go @@ -26,7 +26,6 @@ func manager() (metadata.Manager, error) { if err != nil { return nil, err } - appRoot := metadata.AbsPath(appDir) - return metadata.Find(appRoot) + return metadata.Find(appDir) } diff --git a/prototype/specification.go b/prototype/specification.go index 056ccbe136d62c617dd1553a75311be2df61fbb3..2302b63591a7ca966b2fa3f04258e19c44cf511e 100644 --- a/prototype/specification.go +++ b/prototype/specification.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/blang/semver" - "github.com/ksonnet/ksonnet/utils" + str "github.com/ksonnet/ksonnet/strings" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -173,7 +173,7 @@ func (ss SpecificationSchemas) String() string { rows = append(rows, []string{proto.Name, proto.Template.ShortDescription}) } - formatted, err := utils.PadRows(rows) + formatted, err := str.PadRows(rows) if err != nil { log.Errorf("Failed to print spec rows:\n%v", err) } diff --git a/utils/strings.go b/strings/strings.go similarity index 90% rename from utils/strings.go rename to strings/strings.go index d239da447d6e9d9b802cebed2d2148ea7b03d46d..0edd61810b5fad96d740e2de114c6102c3806f20 100644 --- a/utils/strings.go +++ b/strings/strings.go @@ -1,4 +1,4 @@ -// Copyright 2017 The kubecfg authors +// Copyright 2017 The ksonnet authors // // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,11 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils +package strings import ( "bytes" "fmt" + "path" "strings" "github.com/PuerkitoBio/purell" @@ -107,3 +108,9 @@ func PadRows(rows [][]string) (string, error) { return buf.String(), nil } + +// AppendToPath appends one or more paths to the specified original path. +func AppendToPath(originalPath string, toAppend ...string) string { + paths := append([]string{originalPath}, toAppend...) + return path.Join(paths...) +} diff --git a/utils/strings_test.go b/strings/strings_test.go similarity index 83% rename from utils/strings_test.go rename to strings/strings_test.go index d002d70f6d4237f021519b59ffea63e1fa5cd6d2..7f9e47a6e3e34a2f54148d3256eefe50e8aeba36 100644 --- a/utils/strings_test.go +++ b/strings/strings_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils +package strings import ( "fmt" @@ -182,3 +182,32 @@ Hi World require.EqualValues(t, test.expected, padded) } } + +func TestAppendToPath(t *testing.T) { + tests := []struct { + originalPath string + toAppend string + expected string + }{ + { + originalPath: "host/path/", + toAppend: "appended", + expected: "host/path/appended", + }, + { + originalPath: "host/path", + toAppend: "appended/", + expected: "host/path/appended", + }, + { + originalPath: "host/path/", + toAppend: "//appended//", + expected: "host/path/appended", + }, + } + for _, test := range tests { + result := AppendToPath(test.originalPath, test.toAppend) + + require.EqualValues(t, test.expected, result) + } +} diff --git a/testdata/testapp/app.yaml b/testdata/testapp/app.yaml index 2240d9f397ac0e313c04df9a3ee9cdf8fecf1912..0f8e9878e3d922653b3b389135316695a5e6af87 100644 --- a/testdata/testapp/app.yaml +++ b/testdata/testapp/app.yaml @@ -13,6 +13,6 @@ environments: destinations: - namespace: foo server: foo - k8sVersion: "1.8.1" + k8sVersion: "v1.8.1" path: default version: 0.0.1 diff --git a/testdata/testapp/lib/v1.8.1/.keep b/testdata/testapp/lib/v1.8.1/.keep new file mode 100644 index 0000000000000000000000000000000000000000..9470d4088dfef16c4eeb7d5e845e86a63e78627a --- /dev/null +++ b/testdata/testapp/lib/v1.8.1/.keep @@ -0,0 +1,4 @@ +Git will not check in empty directories. + +This directory is needed for testing ks project structure. This file exists so +that its parent directory can be checked in.