| 1 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,189 +0,0 @@ |
| 1 |
-package main |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "os" |
|
| 6 |
- |
|
| 7 |
- kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 8 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 9 |
- "github.com/fsouza/go-dockerclient" |
|
| 10 |
- "github.com/spf13/cobra" |
|
| 11 |
- |
|
| 12 |
- "github.com/openshift/origin/pkg/api/latest" |
|
| 13 |
- "github.com/openshift/origin/pkg/client" |
|
| 14 |
- "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
| 15 |
- dh "github.com/openshift/origin/pkg/cmd/util/docker" |
|
| 16 |
- config "github.com/openshift/origin/pkg/config/api" |
|
| 17 |
- appgen "github.com/openshift/origin/pkg/generate/app" |
|
| 18 |
- gen "github.com/openshift/origin/pkg/generate/generator" |
|
| 19 |
- "github.com/openshift/origin/pkg/generate/imageinfo" |
|
| 20 |
- "github.com/openshift/origin/pkg/generate/source" |
|
| 21 |
-) |
|
| 22 |
- |
|
| 23 |
-type Input struct {
|
|
| 24 |
- name, |
|
| 25 |
- sourceDir, |
|
| 26 |
- sourceURL, |
|
| 27 |
- dockerContext, |
|
| 28 |
- builderImage, |
|
| 29 |
- outputImage string |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-func main() {
|
|
| 33 |
- cfg := clientcmd.NewConfig() |
|
| 34 |
- dockerHelper := dh.NewHelper() |
|
| 35 |
- input := Input{}
|
|
| 36 |
- cmd := &cobra.Command{
|
|
| 37 |
- Use: fmt.Sprintf("%s%s", "gen-app", clientcmd.ConfigSyntax),
|
|
| 38 |
- Short: "Generate an application configuration", |
|
| 39 |
- Long: "Generate an application configuration", |
|
| 40 |
- Run: func(c *cobra.Command, args []string) {
|
|
| 41 |
- _, osClient, err := cfg.Clients() |
|
| 42 |
- if err != nil {
|
|
| 43 |
- osClient = nil |
|
| 44 |
- } |
|
| 45 |
- dockerClient, _, err := dockerHelper.GetClient() |
|
| 46 |
- if err != nil {
|
|
| 47 |
- osClient = nil |
|
| 48 |
- } |
|
| 49 |
- GenerateApp(input, osClient, dockerClient) |
|
| 50 |
- }, |
|
| 51 |
- } |
|
| 52 |
- |
|
| 53 |
- flag := cmd.Flags() |
|
| 54 |
- flag.StringVar(&input.name, "name", "", "Set name to use for generated application artifacts") |
|
| 55 |
- flag.StringVar(&input.sourceDir, "source-dir", "", "Set the source directory for the application build") |
|
| 56 |
- flag.StringVar(&input.sourceURL, "source-url", "", "Set the source URL") |
|
| 57 |
- flag.StringVar(&input.dockerContext, "context", "", "Context path for Dockerfile if creating a Docker build") |
|
| 58 |
- flag.StringVar(&input.builderImage, "builder-image", "", "Image to use for STI build") |
|
| 59 |
- flag.StringVar(&input.outputImage, "output-image", "", "Image name to use for output") |
|
| 60 |
- cfg.Bind(flag) |
|
| 61 |
- dockerHelper.InstallFlags(flag) |
|
| 62 |
- if err := cmd.Execute(); err != nil {
|
|
| 63 |
- fmt.Fprintf(os.Stderr, "Error: %s", err) |
|
| 64 |
- os.Exit(1) |
|
| 65 |
- } |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-func GenerateApp(input Input, client client.Interface, dockerClient *docker.Client) {
|
|
| 69 |
- // Get a SourceRef |
|
| 70 |
- var srcRef *appgen.SourceRef |
|
| 71 |
- var err error |
|
| 72 |
- srcRefGen := gen.NewSourceRefGenerator() |
|
| 73 |
- if len(input.sourceURL) > 0 {
|
|
| 74 |
- if srcRef, err = srcRefGen.FromGitURL(input.sourceURL); err != nil {
|
|
| 75 |
- exitWithError(err) |
|
| 76 |
- } |
|
| 77 |
- } else {
|
|
| 78 |
- if len(input.sourceDir) == 0 {
|
|
| 79 |
- if input.sourceDir, err = os.Getwd(); err != nil {
|
|
| 80 |
- exitWithError(err) |
|
| 81 |
- } |
|
| 82 |
- } |
|
| 83 |
- if srcRef, err = srcRefGen.FromDirectory(input.sourceDir); err != nil {
|
|
| 84 |
- exitWithError(err) |
|
| 85 |
- } |
|
| 86 |
- } |
|
| 87 |
- |
|
| 88 |
- // Get a BuildStrategyRef |
|
| 89 |
- var strategyRef *appgen.BuildStrategyRef |
|
| 90 |
- strategyRefGen := gen.NewBuildStrategyRefGenerator(source.DefaultDetectors) |
|
| 91 |
- imageRefGen := gen.NewImageRefGenerator() |
|
| 92 |
- if len(input.dockerContext) > 0 {
|
|
| 93 |
- if strategyRef, err = strategyRefGen.FromSourceRefAndDockerContext(*srcRef, input.dockerContext); err != nil {
|
|
| 94 |
- exitWithError(err) |
|
| 95 |
- } |
|
| 96 |
- } else if len(input.builderImage) > 0 {
|
|
| 97 |
- builderRef, err := imageRefGen.FromName(input.builderImage) |
|
| 98 |
- if err != nil {
|
|
| 99 |
- exitWithError(err) |
|
| 100 |
- } |
|
| 101 |
- if strategyRef, err = strategyRefGen.FromSTIBuilderImage(builderRef); err != nil {
|
|
| 102 |
- exitWithError(err) |
|
| 103 |
- } |
|
| 104 |
- } else {
|
|
| 105 |
- if strategyRef, err = strategyRefGen.FromSourceRef(*srcRef); err != nil {
|
|
| 106 |
- exitWithError(err) |
|
| 107 |
- } |
|
| 108 |
- } |
|
| 109 |
- |
|
| 110 |
- // Get an ImageRef for Output |
|
| 111 |
- outputImage := input.outputImage |
|
| 112 |
- if len(outputImage) == 0 {
|
|
| 113 |
- var ok bool |
|
| 114 |
- if outputImage, ok = srcRef.SuggestName(); !ok {
|
|
| 115 |
- exitWithError(fmt.Errorf("Cannot suggest a name for the output image, please specify one in the command line"))
|
|
| 116 |
- } |
|
| 117 |
- } |
|
| 118 |
- outputRef, err := imageRefGen.FromName(outputImage) |
|
| 119 |
- if err != nil {
|
|
| 120 |
- exitWithError(err) |
|
| 121 |
- } |
|
| 122 |
- |
|
| 123 |
- // Get a BuildRef |
|
| 124 |
- buildRef := appgen.BuildRef{
|
|
| 125 |
- Source: srcRef, |
|
| 126 |
- Strategy: strategyRef, |
|
| 127 |
- Output: outputRef, |
|
| 128 |
- } |
|
| 129 |
- |
|
| 130 |
- // Get a DeploymentConfigRef |
|
| 131 |
- var imageInfoRetriever imageinfo.Retriever |
|
| 132 |
- if client != nil {
|
|
| 133 |
- imageInfoRetriever = imageinfo.NewRetriever( |
|
| 134 |
- client.ImageRepositories(kapi.NamespaceAll), |
|
| 135 |
- client.Images(kapi.NamespaceAll), |
|
| 136 |
- dockerClient) |
|
| 137 |
- } else {
|
|
| 138 |
- imageInfoRetriever = imageinfo.NewRetriever(nil, nil, dockerClient) |
|
| 139 |
- } |
|
| 140 |
- imageInfoGenerator := gen.NewImageInfoGenerator(imageInfoRetriever) |
|
| 141 |
- imageInfos := imageInfoGenerator.FromImageRefs([]appgen.ImageRef{*outputRef})
|
|
| 142 |
- deployRef := appgen.DeploymentConfigRef{
|
|
| 143 |
- Images: imageInfos, |
|
| 144 |
- } |
|
| 145 |
- |
|
| 146 |
- // Generate OpenShift resources |
|
| 147 |
- config := config.Config{}
|
|
| 148 |
- bldcfg, err := buildRef.BuildConfig() |
|
| 149 |
- if err != nil {
|
|
| 150 |
- exitWithError(err) |
|
| 151 |
- } |
|
| 152 |
- imgrepo, err := outputRef.ImageRepository() |
|
| 153 |
- if err != nil {
|
|
| 154 |
- exitWithError(err) |
|
| 155 |
- } |
|
| 156 |
- deploycfg, err := deployRef.DeploymentConfig() |
|
| 157 |
- if err != nil {
|
|
| 158 |
- exitWithError(err) |
|
| 159 |
- } |
|
| 160 |
- addToConfig(&config, bldcfg) |
|
| 161 |
- addToConfig(&config, imgrepo) |
|
| 162 |
- addToConfig(&config, deploycfg) |
|
| 163 |
- |
|
| 164 |
- if strategyRef.Base != nil {
|
|
| 165 |
- baserepo, err := strategyRef.Base.ImageRepository() |
|
| 166 |
- if err == nil {
|
|
| 167 |
- addToConfig(&config, baserepo) |
|
| 168 |
- } |
|
| 169 |
- } |
|
| 170 |
- |
|
| 171 |
- result, err := latest.Codec.Encode(&config) |
|
| 172 |
- if err != nil {
|
|
| 173 |
- exitWithError(err) |
|
| 174 |
- } |
|
| 175 |
- fmt.Println(string(result)) |
|
| 176 |
-} |
|
| 177 |
- |
|
| 178 |
-func exitWithError(err error) {
|
|
| 179 |
- fmt.Fprintf(os.Stderr, "%v", err) |
|
| 180 |
- os.Exit(1) |
|
| 181 |
-} |
|
| 182 |
- |
|
| 183 |
-func addToConfig(cfg *config.Config, object runtime.Object) {
|
|
| 184 |
- json, err := latest.Codec.Encode(object) |
|
| 185 |
- if err != nil {
|
|
| 186 |
- exitWithError(err) |
|
| 187 |
- } |
|
| 188 |
- cfg.Items = append(cfg.Items, runtime.RawExtension{RawJSON: json})
|
|
| 189 |
-} |
| ... | ... |
@@ -11,7 +11,6 @@ import ( |
| 11 | 11 |
"github.com/spf13/pflag" |
| 12 | 12 |
|
| 13 | 13 |
"github.com/openshift/origin/pkg/cmd/cli/cmd" |
| 14 |
- cmdnew "github.com/openshift/origin/pkg/cmd/cli/cmd/new" |
|
| 15 | 14 |
) |
| 16 | 15 |
|
| 17 | 16 |
const longDesc = ` |
| ... | ... |
@@ -47,7 +46,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
|
| 47 | 47 |
clientcmd.DefaultCluster.Server = defaultClusterURL |
| 48 | 48 |
|
| 49 | 49 |
// TODO: there should be two client configs, one for OpenShift, and one for Kubernetes |
| 50 |
- clientConfig := defaultClientConfig(cmds.PersistentFlags()) |
|
| 50 |
+ clientConfig := DefaultClientConfig(cmds.PersistentFlags()) |
|
| 51 | 51 |
f := cmd.NewFactory(clientConfig) |
| 52 | 52 |
f.BindFlags(cmds.PersistentFlags()) |
| 53 | 53 |
out := os.Stdout |
| ... | ... |
@@ -65,6 +64,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
|
| 65 | 65 |
cmds.AddCommand(f.NewCmdProxy(out)) |
| 66 | 66 |
|
| 67 | 67 |
// Origin commands |
| 68 |
+ // cmds.AddCommand(cmd.NewCmdNewApplication(f, out)) |
|
| 68 | 69 |
cmds.AddCommand(cmd.NewCmdProcess(f, out)) |
| 69 | 70 |
|
| 70 | 71 |
// Origin build commands |
| ... | ... |
@@ -82,7 +82,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
|
| 82 | 82 |
func NewCmdKubectl(name string) *cobra.Command {
|
| 83 | 83 |
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
|
| 84 | 84 |
clientcmd.DefaultCluster.Server = defaultClusterURL |
| 85 |
- clientConfig := defaultClientConfig(flags) |
|
| 85 |
+ clientConfig := DefaultClientConfig(flags) |
|
| 86 | 86 |
f := cmd.NewFactory(clientConfig) |
| 87 | 87 |
cmd := f.NewKubectlCommand(os.Stdout) |
| 88 | 88 |
cmd.Use = name |
| ... | ... |
@@ -110,7 +110,7 @@ func applyToCreate(dst *cobra.Command) *cobra.Command {
|
| 110 | 110 |
} |
| 111 | 111 |
|
| 112 | 112 |
// Copy of kubectl/cmd/DefaultClientConfig, using NewNonInteractiveDeferredLoadingClientConfig |
| 113 |
-func defaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
|
|
| 113 |
+func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
|
|
| 114 | 114 |
loadingRules := clientcmd.NewClientConfigLoadingRules() |
| 115 | 115 |
loadingRules.EnvVarPath = os.Getenv(clientcmd.RecommendedConfigPathEnvVar) |
| 116 | 116 |
flags.StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") |
| 117 | 117 |
deleted file mode 100644 |
| ... | ... |
@@ -1,323 +0,0 @@ |
| 1 |
-package new |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "sort" |
|
| 6 |
- "strings" |
|
| 7 |
- |
|
| 8 |
- "github.com/fsouza/go-dockerclient" |
|
| 9 |
- |
|
| 10 |
- image "github.com/openshift/origin/pkg/image/api" |
|
| 11 |
- template "github.com/openshift/origin/pkg/template/api" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-// isComponentReference returns true if the provided string appears to be a reference to a source repository |
|
| 15 |
-// on disk, at a URL, a docker image name (which might be on a Docker registry or an OpenShift image stream), |
|
| 16 |
-// or a template. |
|
| 17 |
-func isComponentReference(s string) bool {
|
|
| 18 |
- if len(s) == 0 {
|
|
| 19 |
- return false |
|
| 20 |
- } |
|
| 21 |
- all := strings.Split(s, "+") |
|
| 22 |
- _, _, _, err := componentWithSource(all[0]) |
|
| 23 |
- return err == nil |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-func componentWithSource(s string) (component, repo string, builder bool, err error) {
|
|
| 27 |
- if strings.Contains(s, "~") {
|
|
| 28 |
- segs := strings.SplitN(s, "~", 2) |
|
| 29 |
- if len(segs) == 2 {
|
|
| 30 |
- builder = true |
|
| 31 |
- switch {
|
|
| 32 |
- case len(segs[0]) == 0: |
|
| 33 |
- err = fmt.Errorf("when using '[image]~[code]' form for %q, you must specify a image name", s)
|
|
| 34 |
- return |
|
| 35 |
- case len(segs[1]) == 0: |
|
| 36 |
- component = segs[0] |
|
| 37 |
- default: |
|
| 38 |
- component = segs[0] |
|
| 39 |
- repo = segs[1] |
|
| 40 |
- } |
|
| 41 |
- } |
|
| 42 |
- } else {
|
|
| 43 |
- component = s |
|
| 44 |
- } |
|
| 45 |
- // TODO: component must be of the form compatible with a pull spec *or* <namespace>/<name> |
|
| 46 |
- if !image.IsPullSpec(component) {
|
|
| 47 |
- return "", "", false, fmt.Errorf("%q is not a valid Docker pull specification", component)
|
|
| 48 |
- } |
|
| 49 |
- return |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-type ComponentReference interface {
|
|
| 53 |
- Input() *ComponentInput |
|
| 54 |
- // Sets Input.Match or returns an error |
|
| 55 |
- Resolve() error |
|
| 56 |
- NeedsSource() bool |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-type ComponentReferences []ComponentReference |
|
| 60 |
- |
|
| 61 |
-func (r ComponentReferences) NeedsSource() (refs ComponentReferences) {
|
|
| 62 |
- for _, ref := range r {
|
|
| 63 |
- if ref.NeedsSource() {
|
|
| 64 |
- refs = append(refs, ref) |
|
| 65 |
- } |
|
| 66 |
- } |
|
| 67 |
- return |
|
| 68 |
-} |
|
| 69 |
- |
|
| 70 |
-type GroupedComponentReferences ComponentReferences |
|
| 71 |
- |
|
| 72 |
-func (m GroupedComponentReferences) Len() int { return len(m) }
|
|
| 73 |
-func (m GroupedComponentReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
|
| 74 |
-func (m GroupedComponentReferences) Less(i, j int) bool {
|
|
| 75 |
- return m[i].Input().Group < m[j].Input().Group |
|
| 76 |
-} |
|
| 77 |
- |
|
| 78 |
-func (r ComponentReferences) Group() (refs []ComponentReferences) {
|
|
| 79 |
- sorted := make(GroupedComponentReferences, len(r)) |
|
| 80 |
- copy(sorted, r) |
|
| 81 |
- sort.Sort(sorted) |
|
| 82 |
- group := -1 |
|
| 83 |
- for _, ref := range sorted {
|
|
| 84 |
- if ref.Input().Group != group {
|
|
| 85 |
- refs = append(refs, ComponentReferences{})
|
|
| 86 |
- } |
|
| 87 |
- group = ref.Input().Group |
|
| 88 |
- refs[len(refs)-1] = append(refs[len(refs)-1], ref) |
|
| 89 |
- } |
|
| 90 |
- return |
|
| 91 |
-} |
|
| 92 |
- |
|
| 93 |
-type ComponentMatch struct {
|
|
| 94 |
- Value string |
|
| 95 |
- Argument string |
|
| 96 |
- Name string |
|
| 97 |
- Description string |
|
| 98 |
- Score float32 |
|
| 99 |
- |
|
| 100 |
- Builder bool |
|
| 101 |
- Image *docker.Image |
|
| 102 |
- ImageStream *image.ImageRepository |
|
| 103 |
- ImageTag string |
|
| 104 |
- Template *template.Template |
|
| 105 |
-} |
|
| 106 |
- |
|
| 107 |
-func (m *ComponentMatch) String() string {
|
|
| 108 |
- return m.Argument |
|
| 109 |
-} |
|
| 110 |
- |
|
| 111 |
-type Resolver interface {
|
|
| 112 |
- // resolvers should return ErrMultipleMatches when more than one result could |
|
| 113 |
- // be construed as a match. Resolvers should set the score to 0.0 if this is a |
|
| 114 |
- // perfect match, and to higher values the less adequate the match is. |
|
| 115 |
- Resolve(value string) (*ComponentMatch, error) |
|
| 116 |
-} |
|
| 117 |
- |
|
| 118 |
-type ScoredComponentMatches []*ComponentMatch |
|
| 119 |
- |
|
| 120 |
-func (m ScoredComponentMatches) Len() int { return len(m) }
|
|
| 121 |
-func (m ScoredComponentMatches) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
|
| 122 |
-func (m ScoredComponentMatches) Less(i, j int) bool { return m[i].Score < m[j].Score }
|
|
| 123 |
- |
|
| 124 |
-type WeightedResolver struct {
|
|
| 125 |
- Resolver |
|
| 126 |
- Weight float32 |
|
| 127 |
-} |
|
| 128 |
- |
|
| 129 |
-// PerfectMatchWeightedResolver returns only matches from resolvers that are identified as exact |
|
| 130 |
-// (weight 0.0), and only matches from those resolvers that qualify as exact (score = 0.0). If no |
|
| 131 |
-// perfect matches exist, an ErrMultipleMatches is returned indicating the remaining candidate(s). |
|
| 132 |
-// Note that this metchod may resolve ErrMultipleMatches with a single match, indicating an error |
|
| 133 |
-// (no perfect match) but with only one candidate. |
|
| 134 |
-type PerfectMatchWeightedResolver []WeightedResolver |
|
| 135 |
- |
|
| 136 |
-func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 137 |
- match, err := WeightedResolvers(r).Resolve(value) |
|
| 138 |
- if err != nil {
|
|
| 139 |
- if multiple, ok := err.(ErrMultipleMatches); ok {
|
|
| 140 |
- sort.Sort(ScoredComponentMatches(multiple.matches)) |
|
| 141 |
- if multiple.matches[0].Score == 0.0 && (len(multiple.matches) == 1 || multiple.matches[1].Score != 0.0) {
|
|
| 142 |
- return multiple.matches[0], nil |
|
| 143 |
- } |
|
| 144 |
- } |
|
| 145 |
- return nil, err |
|
| 146 |
- } |
|
| 147 |
- if match.Score != 0.0 {
|
|
| 148 |
- return nil, ErrMultipleMatches{value, []*ComponentMatch{match}}
|
|
| 149 |
- } |
|
| 150 |
- return match, nil |
|
| 151 |
-} |
|
| 152 |
- |
|
| 153 |
-type WeightedResolvers []WeightedResolver |
|
| 154 |
- |
|
| 155 |
-func (r WeightedResolvers) Resolve(value string) (*ComponentMatch, error) {
|
|
| 156 |
- candidates := []*ComponentMatch{}
|
|
| 157 |
- errs := []error{}
|
|
| 158 |
- for _, resolver := range r {
|
|
| 159 |
- match, err := resolver.Resolve(value) |
|
| 160 |
- if err != nil {
|
|
| 161 |
- if multiple, ok := err.(ErrMultipleMatches); ok {
|
|
| 162 |
- for _, match := range multiple.matches {
|
|
| 163 |
- if resolver.Weight != 0.0 {
|
|
| 164 |
- match.Score = match.Score * resolver.Weight |
|
| 165 |
- } |
|
| 166 |
- candidates = append(candidates, match) |
|
| 167 |
- } |
|
| 168 |
- continue |
|
| 169 |
- } |
|
| 170 |
- if _, ok := err.(ErrNoMatch); ok {
|
|
| 171 |
- continue |
|
| 172 |
- } |
|
| 173 |
- errs = append(errs, err) |
|
| 174 |
- continue |
|
| 175 |
- } |
|
| 176 |
- candidates = append(candidates, match) |
|
| 177 |
- } |
|
| 178 |
- switch len(candidates) {
|
|
| 179 |
- case 0: |
|
| 180 |
- return nil, ErrNoMatch{value: value}
|
|
| 181 |
- case 1: |
|
| 182 |
- return candidates[0], nil |
|
| 183 |
- default: |
|
| 184 |
- return nil, ErrMultipleMatches{value, candidates}
|
|
| 185 |
- } |
|
| 186 |
-} |
|
| 187 |
- |
|
| 188 |
-type ReferenceBuilder struct {
|
|
| 189 |
- refs ComponentReferences |
|
| 190 |
- repos []*SourceRepository |
|
| 191 |
- errs []error |
|
| 192 |
- group int |
|
| 193 |
-} |
|
| 194 |
- |
|
| 195 |
-func (r *ReferenceBuilder) AddImages(inputs []string, fn func(*ComponentInput) ComponentReference) {
|
|
| 196 |
- for _, s := range inputs {
|
|
| 197 |
- for _, s := range strings.Split(s, "+") {
|
|
| 198 |
- input, repo, err := NewComponentInput(s) |
|
| 199 |
- if err != nil {
|
|
| 200 |
- r.errs = append(r.errs, err) |
|
| 201 |
- continue |
|
| 202 |
- } |
|
| 203 |
- input.Group = r.group |
|
| 204 |
- ref := fn(input) |
|
| 205 |
- if len(repo) != 0 {
|
|
| 206 |
- repository, ok := r.AddSourceRepository(repo) |
|
| 207 |
- if !ok {
|
|
| 208 |
- continue |
|
| 209 |
- } |
|
| 210 |
- input.Use(repository) |
|
| 211 |
- repository.UsedBy(ref) |
|
| 212 |
- } |
|
| 213 |
- r.refs = append(r.refs, ref) |
|
| 214 |
- } |
|
| 215 |
- r.group++ |
|
| 216 |
- } |
|
| 217 |
-} |
|
| 218 |
- |
|
| 219 |
-func (r *ReferenceBuilder) AddGroups(inputs []string) {
|
|
| 220 |
- for _, s := range inputs {
|
|
| 221 |
- groups := strings.Split(s, "+") |
|
| 222 |
- if len(groups) == 1 {
|
|
| 223 |
- r.errs = append(r.errs, fmt.Errorf("group %q only contains a single name", s))
|
|
| 224 |
- continue |
|
| 225 |
- } |
|
| 226 |
- to := -1 |
|
| 227 |
- for _, group := range groups {
|
|
| 228 |
- var match ComponentReference |
|
| 229 |
- for _, ref := range r.refs {
|
|
| 230 |
- if group == ref.Input().Value {
|
|
| 231 |
- match = ref |
|
| 232 |
- break |
|
| 233 |
- } |
|
| 234 |
- } |
|
| 235 |
- if match == nil {
|
|
| 236 |
- r.errs = append(r.errs, fmt.Errorf("the name %q from the group definition is not in use, and can't be used", group))
|
|
| 237 |
- break |
|
| 238 |
- } |
|
| 239 |
- if to == -1 {
|
|
| 240 |
- to = match.Input().Group |
|
| 241 |
- } else {
|
|
| 242 |
- match.Input().Group = to |
|
| 243 |
- } |
|
| 244 |
- } |
|
| 245 |
- } |
|
| 246 |
-} |
|
| 247 |
- |
|
| 248 |
-func (r *ReferenceBuilder) AddSourceRepository(input string) (*SourceRepository, bool) {
|
|
| 249 |
- for _, existing := range r.repos {
|
|
| 250 |
- if input == existing.location {
|
|
| 251 |
- return existing, true |
|
| 252 |
- } |
|
| 253 |
- } |
|
| 254 |
- source, err := NewSourceRepository(input) |
|
| 255 |
- if err != nil {
|
|
| 256 |
- r.errs = append(r.errs, err) |
|
| 257 |
- return nil, false |
|
| 258 |
- } |
|
| 259 |
- r.repos = append(r.repos, source) |
|
| 260 |
- return source, true |
|
| 261 |
-} |
|
| 262 |
- |
|
| 263 |
-func (r *ReferenceBuilder) Result() (ComponentReferences, []*SourceRepository, []error) {
|
|
| 264 |
- return r.refs, r.repos, r.errs |
|
| 265 |
-} |
|
| 266 |
- |
|
| 267 |
-func NewComponentInput(input string) (*ComponentInput, string, error) {
|
|
| 268 |
- // check for image using [image]~ (to indicate builder) or [image]~[code] (builder plus code) |
|
| 269 |
- component, repo, builder, err := componentWithSource(input) |
|
| 270 |
- if err != nil {
|
|
| 271 |
- return nil, "", err |
|
| 272 |
- } |
|
| 273 |
- return &ComponentInput{
|
|
| 274 |
- From: input, |
|
| 275 |
- Argument: input, |
|
| 276 |
- Value: component, |
|
| 277 |
- ExpectToBuild: builder, |
|
| 278 |
- }, repo, nil |
|
| 279 |
-} |
|
| 280 |
- |
|
| 281 |
-type ComponentInput struct {
|
|
| 282 |
- Group int |
|
| 283 |
- From string |
|
| 284 |
- Argument string |
|
| 285 |
- Value string |
|
| 286 |
- ExpectToBuild bool |
|
| 287 |
- |
|
| 288 |
- Uses *SourceRepository |
|
| 289 |
- Match *ComponentMatch |
|
| 290 |
- |
|
| 291 |
- Resolver |
|
| 292 |
-} |
|
| 293 |
- |
|
| 294 |
-func (i *ComponentInput) Input() *ComponentInput {
|
|
| 295 |
- return i |
|
| 296 |
-} |
|
| 297 |
- |
|
| 298 |
-func (i *ComponentInput) NeedsSource() bool {
|
|
| 299 |
- return i.ExpectToBuild && i.Uses == nil |
|
| 300 |
-} |
|
| 301 |
- |
|
| 302 |
-func (i *ComponentInput) Resolve() error {
|
|
| 303 |
- if i.Resolver == nil {
|
|
| 304 |
- return ErrNoMatch{value: i.Value, qualifier: "no resolver defined"}
|
|
| 305 |
- } |
|
| 306 |
- match, err := i.Resolver.Resolve(i.Value) |
|
| 307 |
- if err != nil {
|
|
| 308 |
- return err |
|
| 309 |
- } |
|
| 310 |
- i.Value = match.Value |
|
| 311 |
- i.Argument = match.Argument |
|
| 312 |
- i.Match = match |
|
| 313 |
- |
|
| 314 |
- return nil |
|
| 315 |
-} |
|
| 316 |
- |
|
| 317 |
-func (i *ComponentInput) String() string {
|
|
| 318 |
- return i.Value |
|
| 319 |
-} |
|
| 320 |
- |
|
| 321 |
-func (i *ComponentInput) Use(repo *SourceRepository) {
|
|
| 322 |
- i.Uses = repo |
|
| 323 |
-} |
| 324 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,49 +0,0 @@ |
| 1 |
-package new |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "fmt" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-type ErrNoMatch struct {
|
|
| 9 |
- value string |
|
| 10 |
- qualifier string |
|
| 11 |
-} |
|
| 12 |
- |
|
| 13 |
-func (e ErrNoMatch) Error() string {
|
|
| 14 |
- if len(e.qualifier) != 0 {
|
|
| 15 |
- return fmt.Sprintf("no image matched %q: %s", e.value, e.qualifier)
|
|
| 16 |
- } |
|
| 17 |
- return fmt.Sprintf("no image matched %q", e.value)
|
|
| 18 |
-} |
|
| 19 |
- |
|
| 20 |
-func (e ErrNoMatch) UsageError(commandName string) string {
|
|
| 21 |
- return fmt.Sprintf(` |
|
| 22 |
-%[3]s - you can try to search for images or templates that may match this name with: |
|
| 23 |
- |
|
| 24 |
- $ %[2]s -S %[1]q |
|
| 25 |
- |
|
| 26 |
-`, e.value, commandName, e.Error()) |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-type ErrMultipleMatches struct {
|
|
| 30 |
- image string |
|
| 31 |
- matches []*ComponentMatch |
|
| 32 |
-} |
|
| 33 |
- |
|
| 34 |
-func (e ErrMultipleMatches) Error() string {
|
|
| 35 |
- return fmt.Sprintf("multiple images matched %q: %d", e.image, len(e.matches))
|
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-func (e ErrMultipleMatches) UsageError(commandName string) string {
|
|
| 39 |
- buf := &bytes.Buffer{}
|
|
| 40 |
- for _, match := range e.matches {
|
|
| 41 |
- fmt.Fprintf(buf, "* %[1]s (use %[2]s)\n", match.Name, match.Argument) |
|
| 42 |
- fmt.Fprintf(buf, " %s\n\n", match.Description) |
|
| 43 |
- } |
|
| 44 |
- return fmt.Sprintf(` |
|
| 45 |
-The argument %[1]q could apply to the following images or templates: |
|
| 46 |
- |
|
| 47 |
-%[2]s |
|
| 48 |
-`, e.image, buf.String()) |
|
| 49 |
-} |
| 50 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,239 +0,0 @@ |
| 1 |
-package new |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "strings" |
|
| 6 |
- |
|
| 7 |
- "github.com/fsouza/go-dockerclient" |
|
| 8 |
- |
|
| 9 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
|
| 10 |
- "github.com/golang/glog" |
|
| 11 |
- |
|
| 12 |
- "github.com/openshift/origin/pkg/client" |
|
| 13 |
- "github.com/openshift/origin/pkg/dockerregistry" |
|
| 14 |
- "github.com/openshift/origin/pkg/generate/app" |
|
| 15 |
- image "github.com/openshift/origin/pkg/image/api" |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-type dockerClientResolver struct {
|
|
| 19 |
- client *docker.Client |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-func (r dockerClientResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 23 |
- image, err := r.client.InspectImage(value) |
|
| 24 |
- switch {
|
|
| 25 |
- case err == docker.ErrNoSuchImage: |
|
| 26 |
- return nil, ErrNoMatch{value: value}
|
|
| 27 |
- case err != nil: |
|
| 28 |
- return nil, err |
|
| 29 |
- } |
|
| 30 |
- return &ComponentMatch{
|
|
| 31 |
- Value: value, |
|
| 32 |
- Argument: fmt.Sprintf("--docker-image=%q", value),
|
|
| 33 |
- Name: value, |
|
| 34 |
- Description: fmt.Sprintf("Docker image %q by %s\n%s", value, image.Author, image.Comment),
|
|
| 35 |
- Builder: false, |
|
| 36 |
- Score: 0, |
|
| 37 |
- }, nil |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-type dockerRegistryResolver struct {
|
|
| 41 |
- client dockerregistry.Client |
|
| 42 |
-} |
|
| 43 |
- |
|
| 44 |
-func (r dockerRegistryResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 45 |
- registry, namespace, name, tag, err := image.SplitDockerPullSpec(value) |
|
| 46 |
- if err != nil {
|
|
| 47 |
- return nil, err |
|
| 48 |
- } |
|
| 49 |
- connection, err := r.client.Connect(registry) |
|
| 50 |
- if err != nil {
|
|
| 51 |
- if dockerregistry.IsRegistryNotFound(err) {
|
|
| 52 |
- return nil, ErrNoMatch{value: value}
|
|
| 53 |
- } |
|
| 54 |
- return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
|
|
| 55 |
- } |
|
| 56 |
- image, err := connection.ImageByTag(namespace, name, tag) |
|
| 57 |
- if err != nil {
|
|
| 58 |
- if dockerregistry.IsNotFound(err) {
|
|
| 59 |
- return nil, ErrNoMatch{value: value, qualifier: err.Error()}
|
|
| 60 |
- } |
|
| 61 |
- return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
|
|
| 62 |
- } |
|
| 63 |
- if len(tag) == 0 {
|
|
| 64 |
- tag = "latest" |
|
| 65 |
- } |
|
| 66 |
- glog.V(4).Infof("found image: %#v", image)
|
|
| 67 |
- return &ComponentMatch{
|
|
| 68 |
- Value: value, |
|
| 69 |
- Argument: fmt.Sprintf("--docker-image=%q", value),
|
|
| 70 |
- Name: value, |
|
| 71 |
- Description: fmt.Sprintf("Docker image %q (%q)", value, image.ID),
|
|
| 72 |
- Builder: app.IsBuilderImage(image), |
|
| 73 |
- Score: 0, |
|
| 74 |
- |
|
| 75 |
- Image: image, |
|
| 76 |
- ImageTag: tag, |
|
| 77 |
- }, nil |
|
| 78 |
-} |
|
| 79 |
- |
|
| 80 |
-type imageStreamResolver struct {
|
|
| 81 |
- client client.ImageRepositoriesNamespacer |
|
| 82 |
- images client.ImagesNamespacer |
|
| 83 |
- namespaces []string |
|
| 84 |
-} |
|
| 85 |
- |
|
| 86 |
-func (r imageStreamResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 87 |
- registry, namespace, name, tag, err := image.SplitOpenShiftPullSpec(value) |
|
| 88 |
- if err != nil || len(registry) != 0 {
|
|
| 89 |
- return nil, fmt.Errorf("image repositories must be of the form [<namespace>/]<name>[:<tag>]")
|
|
| 90 |
- } |
|
| 91 |
- namespaces := r.namespaces |
|
| 92 |
- if len(namespace) != 0 {
|
|
| 93 |
- namespaces = []string{namespace}
|
|
| 94 |
- } |
|
| 95 |
- for _, namespace := range namespaces {
|
|
| 96 |
- glog.V(4).Infof("checking image stream %s/%s with tag %q", namespace, name, tag)
|
|
| 97 |
- repo, err := r.client.ImageRepositories(namespace).Get(name) |
|
| 98 |
- if err != nil {
|
|
| 99 |
- if errors.IsNotFound(err) {
|
|
| 100 |
- continue |
|
| 101 |
- } |
|
| 102 |
- return nil, err |
|
| 103 |
- } |
|
| 104 |
- searchTag := tag |
|
| 105 |
- // TODO: move to a lookup function on repo, or better yet, have the repo.Status.Tags field automatically infer latest |
|
| 106 |
- if len(searchTag) == 0 {
|
|
| 107 |
- searchTag = "latest" |
|
| 108 |
- } |
|
| 109 |
- id, ok := repo.Tags[searchTag] |
|
| 110 |
- if !ok {
|
|
| 111 |
- if len(tag) == 0 {
|
|
| 112 |
- return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("the default tag %q has not been set", searchTag)}
|
|
| 113 |
- } |
|
| 114 |
- return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q has not been set", tag)}
|
|
| 115 |
- } |
|
| 116 |
- imageData, err := r.images.Images(namespace).Get(id) |
|
| 117 |
- if err != nil {
|
|
| 118 |
- if errors.IsNotFound(err) {
|
|
| 119 |
- return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q is set, but image %q has been removed", tag, id)}
|
|
| 120 |
- } |
|
| 121 |
- return nil, err |
|
| 122 |
- } |
|
| 123 |
- |
|
| 124 |
- spec := image.JoinDockerPullSpec("", namespace, name, tag)
|
|
| 125 |
- return &ComponentMatch{
|
|
| 126 |
- Value: spec, |
|
| 127 |
- Argument: fmt.Sprintf("--image=%q", spec),
|
|
| 128 |
- Name: name, |
|
| 129 |
- Description: fmt.Sprintf("Image stream %s (tag %q) in namespace %s, tracks %q", name, searchTag, namespace, repo.Status.DockerImageRepository),
|
|
| 130 |
- Builder: app.IsBuilderImage(&imageData.DockerImageMetadata), |
|
| 131 |
- Score: 0, |
|
| 132 |
- |
|
| 133 |
- ImageStream: repo, |
|
| 134 |
- Image: &imageData.DockerImageMetadata, |
|
| 135 |
- ImageTag: searchTag, |
|
| 136 |
- }, nil |
|
| 137 |
- } |
|
| 138 |
- return nil, ErrNoMatch{value: value}
|
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-type mockResolver struct{}
|
|
| 142 |
- |
|
| 143 |
-func (mockResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 144 |
- matches, err := mockSearcher{}.Search([]string{value})
|
|
| 145 |
- switch {
|
|
| 146 |
- case err != nil: |
|
| 147 |
- return nil, err |
|
| 148 |
- case len(matches) > 1: |
|
| 149 |
- return nil, ErrMultipleMatches{value, matches}
|
|
| 150 |
- case len(matches) == 0: |
|
| 151 |
- return nil, ErrNoMatch{value: value}
|
|
| 152 |
- default: |
|
| 153 |
- return matches[0], nil |
|
| 154 |
- } |
|
| 155 |
-} |
|
| 156 |
- |
|
| 157 |
-type Searcher interface {
|
|
| 158 |
- Search(terms []string) ([]*ComponentMatch, error) |
|
| 159 |
-} |
|
| 160 |
- |
|
| 161 |
-type mockSearcher struct{}
|
|
| 162 |
- |
|
| 163 |
-func (mockSearcher) Search(terms []string) ([]*ComponentMatch, error) {
|
|
| 164 |
- for _, term := range terms {
|
|
| 165 |
- term = strings.ToLower(term) |
|
| 166 |
- switch term {
|
|
| 167 |
- case "redhat/mysql:5.6": |
|
| 168 |
- return []*ComponentMatch{
|
|
| 169 |
- {
|
|
| 170 |
- Value: term, |
|
| 171 |
- Argument: "redhat/mysql:5.6", |
|
| 172 |
- Name: "MySQL 5.6", |
|
| 173 |
- Description: "The Open Source SQL database", |
|
| 174 |
- }, |
|
| 175 |
- }, nil |
|
| 176 |
- case "mysql", "mysql5", "mysql-5", "mysql-5.x": |
|
| 177 |
- return []*ComponentMatch{
|
|
| 178 |
- {
|
|
| 179 |
- Value: term, |
|
| 180 |
- Argument: "redhat/mysql:5.6", |
|
| 181 |
- Name: "MySQL 5.6", |
|
| 182 |
- Description: "The Open Source SQL database", |
|
| 183 |
- }, |
|
| 184 |
- {
|
|
| 185 |
- Value: term, |
|
| 186 |
- Argument: "mysql", |
|
| 187 |
- Name: "MySQL 5.X", |
|
| 188 |
- Description: "Something out there on the Docker Hub.", |
|
| 189 |
- }, |
|
| 190 |
- }, nil |
|
| 191 |
- case "php", "php-5", "php5", "redhat/php:5", "redhat/php-5": |
|
| 192 |
- return []*ComponentMatch{
|
|
| 193 |
- {
|
|
| 194 |
- Value: term, |
|
| 195 |
- Argument: "redhat/php:5", |
|
| 196 |
- Name: "PHP 5.5", |
|
| 197 |
- Description: "A fast and easy to use scripting language for building websites.", |
|
| 198 |
- Builder: true, |
|
| 199 |
- }, |
|
| 200 |
- }, nil |
|
| 201 |
- case "ruby": |
|
| 202 |
- return []*ComponentMatch{
|
|
| 203 |
- {
|
|
| 204 |
- Value: term, |
|
| 205 |
- Argument: "redhat/ruby:2", |
|
| 206 |
- Name: "Ruby 2.0", |
|
| 207 |
- Description: "A fast and easy to use scripting language for building websites.", |
|
| 208 |
- Builder: true, |
|
| 209 |
- }, |
|
| 210 |
- }, nil |
|
| 211 |
- } |
|
| 212 |
- } |
|
| 213 |
- return []*ComponentMatch{}, nil
|
|
| 214 |
-} |
|
| 215 |
- |
|
| 216 |
-func InputImageFromMatch(match *ComponentMatch) (*app.ImageRef, error) {
|
|
| 217 |
- switch {
|
|
| 218 |
- case match.ImageStream != nil: |
|
| 219 |
- input, err := app.ImageFromRepository(match.ImageStream, match.ImageTag) |
|
| 220 |
- if err != nil {
|
|
| 221 |
- return nil, err |
|
| 222 |
- } |
|
| 223 |
- input.AsImageRepository = true |
|
| 224 |
- input.Info = match.Image |
|
| 225 |
- return input, nil |
|
| 226 |
- |
|
| 227 |
- case match.Image != nil: |
|
| 228 |
- input, err := app.ImageFromName(match.Value, match.ImageTag) |
|
| 229 |
- if err != nil {
|
|
| 230 |
- return nil, err |
|
| 231 |
- } |
|
| 232 |
- input.AsImageRepository = false |
|
| 233 |
- input.Info = match.Image |
|
| 234 |
- return input, nil |
|
| 235 |
- |
|
| 236 |
- default: |
|
| 237 |
- return nil, fmt.Errorf("no image or image stream, can't setup a build")
|
|
| 238 |
- } |
|
| 239 |
-} |
| 240 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,383 +0,0 @@ |
| 1 |
-package new |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io" |
|
| 6 |
- |
|
| 7 |
- "github.com/golang/glog" |
|
| 8 |
- "github.com/spf13/cobra" |
|
| 9 |
- |
|
| 10 |
- kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 11 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" |
|
| 12 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 13 |
- |
|
| 14 |
- "github.com/openshift/origin/pkg/cmd/cli/cmd" |
|
| 15 |
- cmdutil "github.com/openshift/origin/pkg/cmd/util" |
|
| 16 |
- dockerutil "github.com/openshift/origin/pkg/cmd/util/docker" |
|
| 17 |
- "github.com/openshift/origin/pkg/dockerregistry" |
|
| 18 |
- "github.com/openshift/origin/pkg/generate/app" |
|
| 19 |
- "github.com/openshift/origin/pkg/generate/dockerfile" |
|
| 20 |
- "github.com/openshift/origin/pkg/generate/source" |
|
| 21 |
-) |
|
| 22 |
- |
|
| 23 |
-type newAppConfig struct {
|
|
| 24 |
- SourceRepositories util.StringList |
|
| 25 |
- |
|
| 26 |
- Components util.StringList |
|
| 27 |
- ImageStreams util.StringList |
|
| 28 |
- DockerImages util.StringList |
|
| 29 |
- Groups util.StringList |
|
| 30 |
- Environment util.StringList |
|
| 31 |
- |
|
| 32 |
- TypeOfBuild string |
|
| 33 |
- |
|
| 34 |
- localDockerResolver Resolver |
|
| 35 |
- imageStreamResolver Resolver |
|
| 36 |
- |
|
| 37 |
- searcher Searcher |
|
| 38 |
- detector Detector |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-const longNewAppDescription = ` |
|
| 42 |
-Create a new application in OpenShift by specifying source code, templates, and/or images. |
|
| 43 |
- |
|
| 44 |
-Examples: |
|
| 45 |
- $ osc new-app . |
|
| 46 |
- <try to create an application based on the source code in the current directory> |
|
| 47 |
- |
|
| 48 |
- $ osc new-app mysql |
|
| 49 |
- <use the public DockerHub MySQL image to create an app> |
|
| 50 |
- |
|
| 51 |
- $ osc new-app myregistry.com/mycompany/mysql |
|
| 52 |
- <use a MySQL image in a private registry to create an app> |
|
| 53 |
- |
|
| 54 |
- $ osc new-app openshift/ruby-20-centos~git@github.com/mfojtik/sinatra-app-example |
|
| 55 |
- <build an application using the OpenShift Ruby DockerHub image and an example repo>` |
|
| 56 |
- |
|
| 57 |
-func NewCmdNewApplication(f *cmd.Factory, out io.Writer) *cobra.Command {
|
|
| 58 |
- config := newAppConfig{
|
|
| 59 |
- searcher: &mockSearcher{},
|
|
| 60 |
- detector: sourceRepositoryEnumerator{
|
|
| 61 |
- detectors: source.DefaultDetectors, |
|
| 62 |
- tester: dockerfile.NewTester(), |
|
| 63 |
- }, |
|
| 64 |
- } |
|
| 65 |
- helper := dockerutil.NewHelper() |
|
| 66 |
- |
|
| 67 |
- cmd := &cobra.Command{
|
|
| 68 |
- Use: "new-app <components> [--code=<path|url>]", |
|
| 69 |
- Short: "Create a new application", |
|
| 70 |
- Long: longNewAppDescription, |
|
| 71 |
- |
|
| 72 |
- Run: func(c *cobra.Command, args []string) {
|
|
| 73 |
- if dockerclient, _, err := helper.GetClient(); err == nil {
|
|
| 74 |
- config.localDockerResolver = dockerClientResolver{dockerclient}
|
|
| 75 |
- config.localDockerResolver = dockerRegistryResolver{dockerregistry.NewClient()}
|
|
| 76 |
- } |
|
| 77 |
- if osclient, _, err := f.Clients(c); err == nil {
|
|
| 78 |
- config.imageStreamResolver = imageStreamResolver{
|
|
| 79 |
- client: osclient, |
|
| 80 |
- images: osclient, |
|
| 81 |
- namespaces: []string{cmd.GetOriginNamespace(c), "default"},
|
|
| 82 |
- } |
|
| 83 |
- } else {
|
|
| 84 |
- glog.Warningf("error getting client: %v", err)
|
|
| 85 |
- } |
|
| 86 |
- unknown := config.addArguments(args) |
|
| 87 |
- if len(unknown) != 0 {
|
|
| 88 |
- glog.Fatalf("Did not recognize the following arguments: %v", unknown)
|
|
| 89 |
- } |
|
| 90 |
- if err := config.Run(f, out, c.Help); err != nil {
|
|
| 91 |
- if errs, ok := err.(errlist); ok {
|
|
| 92 |
- if len(errs.Errors()) == 1 {
|
|
| 93 |
- err = errs.Errors()[0] |
|
| 94 |
- } |
|
| 95 |
- } |
|
| 96 |
- if usage, ok := err.(UsageError); ok {
|
|
| 97 |
- glog.Fatal(usage.UsageError(c.CommandPath())) |
|
| 98 |
- } |
|
| 99 |
- glog.Fatalf("Error: %v", err)
|
|
| 100 |
- } |
|
| 101 |
- }, |
|
| 102 |
- } |
|
| 103 |
- |
|
| 104 |
- cmd.Flags().Var(&config.SourceRepositories, "code", "Source code to use to build this application.") |
|
| 105 |
- cmd.Flags().VarP(&config.ImageStreams, "image", "i", "Name of an OpenShift image repository to use in the app.") |
|
| 106 |
- cmd.Flags().Var(&config.DockerImages, "docker-image", "Name of a Docker image to include in the app.") |
|
| 107 |
- cmd.Flags().Var(&config.Groups, "group", "Indicate components that should be grouped together as <comp1>+<comp2>.") |
|
| 108 |
- cmd.Flags().VarP(&config.Environment, "env", "e", "Specify key value pairs of environment variables to set into each container.") |
|
| 109 |
- cmd.Flags().StringVar(&config.TypeOfBuild, "build", "", "Specify the type of build to use if you don't want to detect (docker|source)") |
|
| 110 |
- return cmd |
|
| 111 |
-} |
|
| 112 |
- |
|
| 113 |
-type UsageError interface {
|
|
| 114 |
- UsageError(commandName string) string |
|
| 115 |
-} |
|
| 116 |
- |
|
| 117 |
-// TODO: replace with upstream converting [1]error to error |
|
| 118 |
-type errlist interface {
|
|
| 119 |
- Errors() []error |
|
| 120 |
-} |
|
| 121 |
- |
|
| 122 |
-// addArguments converts command line arguments into the appropriate bucket based on what they look like |
|
| 123 |
-func (c *newAppConfig) addArguments(args []string) []string {
|
|
| 124 |
- unknown := []string{}
|
|
| 125 |
- for _, s := range args {
|
|
| 126 |
- switch {
|
|
| 127 |
- case cmdutil.IsEnvironmentArgument(s): |
|
| 128 |
- c.Environment = append(c.Environment, s) |
|
| 129 |
- case isPossibleSourceRepository(s): |
|
| 130 |
- c.SourceRepositories = append(c.SourceRepositories, s) |
|
| 131 |
- case isComponentReference(s): |
|
| 132 |
- c.Components = append(c.Components, s) |
|
| 133 |
- default: |
|
| 134 |
- if len(s) == 0 {
|
|
| 135 |
- break |
|
| 136 |
- } |
|
| 137 |
- unknown = append(unknown, s) |
|
| 138 |
- } |
|
| 139 |
- } |
|
| 140 |
- return unknown |
|
| 141 |
-} |
|
| 142 |
- |
|
| 143 |
-// validate converts all of the arguments on the config into references to objects, or returns an error |
|
| 144 |
-func (c *newAppConfig) validate() (ComponentReferences, []*SourceRepository, cmdutil.Environment, error) {
|
|
| 145 |
- b := &ReferenceBuilder{}
|
|
| 146 |
- for _, s := range c.SourceRepositories {
|
|
| 147 |
- b.AddSourceRepository(s) |
|
| 148 |
- } |
|
| 149 |
- b.AddImages(c.DockerImages, func(input *ComponentInput) ComponentReference {
|
|
| 150 |
- input.Argument = fmt.Sprintf("--docker-image=%q", input.From)
|
|
| 151 |
- input.Resolver = c.localDockerResolver |
|
| 152 |
- return input |
|
| 153 |
- }) |
|
| 154 |
- b.AddImages(c.ImageStreams, func(input *ComponentInput) ComponentReference {
|
|
| 155 |
- input.Argument = fmt.Sprintf("--image=%q", input.From)
|
|
| 156 |
- input.Resolver = c.imageStreamResolver |
|
| 157 |
- return input |
|
| 158 |
- }) |
|
| 159 |
- b.AddImages(c.Components, func(input *ComponentInput) ComponentReference {
|
|
| 160 |
- input.Resolver = PerfectMatchWeightedResolver{
|
|
| 161 |
- WeightedResolver{Resolver: c.imageStreamResolver, Weight: 0.0},
|
|
| 162 |
- WeightedResolver{Resolver: c.localDockerResolver, Weight: 0.0},
|
|
| 163 |
- } |
|
| 164 |
- return input |
|
| 165 |
- }) |
|
| 166 |
- b.AddGroups(c.Groups) |
|
| 167 |
- refs, repos, errs := b.Result() |
|
| 168 |
- if len(c.TypeOfBuild) != 0 && len(repos) == 0 {
|
|
| 169 |
- errs = append(errs, fmt.Errorf("when --build is specified you must provide at least one source code location"))
|
|
| 170 |
- } |
|
| 171 |
- |
|
| 172 |
- env, duplicate, envErrs := cmdutil.ParseEnvironmentArguments(c.Environment) |
|
| 173 |
- for _, s := range duplicate {
|
|
| 174 |
- glog.V(1).Infof("The environment variable %q was overwritten", s)
|
|
| 175 |
- } |
|
| 176 |
- errs = append(errs, envErrs...) |
|
| 177 |
- |
|
| 178 |
- return refs, repos, env, util.SliceToError(errs) |
|
| 179 |
-} |
|
| 180 |
- |
|
| 181 |
-// resolve the references to ensure they are all valid, and identify any images that don't match user input. |
|
| 182 |
-func (c *newAppConfig) resolve(components ComponentReferences) error {
|
|
| 183 |
- errs := []error{}
|
|
| 184 |
- for _, ref := range components {
|
|
| 185 |
- if err := ref.Resolve(); err != nil {
|
|
| 186 |
- errs = append(errs, err) |
|
| 187 |
- continue |
|
| 188 |
- } |
|
| 189 |
- switch input := ref.Input(); {
|
|
| 190 |
- case !input.ExpectToBuild && input.Match.Builder: |
|
| 191 |
- if c.TypeOfBuild != "docker" {
|
|
| 192 |
- glog.Infof("Image %q is a builder, so a repository will be expected unless you also specify --build=docker", input)
|
|
| 193 |
- input.ExpectToBuild = true |
|
| 194 |
- } |
|
| 195 |
- case input.ExpectToBuild && !input.Match.Builder: |
|
| 196 |
- if len(c.TypeOfBuild) == 0 {
|
|
| 197 |
- errs = append(errs, fmt.Errorf("none of the images that match %q can build source code - check whether this is the image you want to use, then use --build=source to build using source or --build=docker to treat this as a Docker base image and set up a layered Docker build", ref))
|
|
| 198 |
- continue |
|
| 199 |
- } |
|
| 200 |
- } |
|
| 201 |
- } |
|
| 202 |
- return util.SliceToError(errs) |
|
| 203 |
-} |
|
| 204 |
- |
|
| 205 |
-// ensureHasSource ensure every builder component has source code associated with it |
|
| 206 |
-func (c *newAppConfig) ensureHasSource(components ComponentReferences, repositories []*SourceRepository) error {
|
|
| 207 |
- requiresSource := components.NeedsSource() |
|
| 208 |
- if len(requiresSource) > 0 {
|
|
| 209 |
- switch {
|
|
| 210 |
- case len(repositories) > 1: |
|
| 211 |
- // TODO: harder problem - need to match repos up |
|
| 212 |
- if len(requiresSource) == 1 {
|
|
| 213 |
- // TODO: print all suggestions |
|
| 214 |
- return fmt.Errorf("there are multiple code locations provided - use '%s~<repo>' to declare which code goes with the image", requiresSource[0])
|
|
| 215 |
- } |
|
| 216 |
- // TODO: indicate which args don't match, and which repos don't match |
|
| 217 |
- return fmt.Errorf("there are multiple code locations provided - use '[image]~[repo]' to declare which code goes with which image")
|
|
| 218 |
- case len(repositories) == 1: |
|
| 219 |
- glog.Infof("Using %q as the source for build", repositories[0])
|
|
| 220 |
- for _, component := range requiresSource {
|
|
| 221 |
- component.Input().Use(repositories[0]) |
|
| 222 |
- repositories[0].UsedBy(component) |
|
| 223 |
- } |
|
| 224 |
- default: |
|
| 225 |
- if len(requiresSource) == 1 {
|
|
| 226 |
- return fmt.Errorf("the image %q will build source code, so you must specify a repository via --code", requiresSource[0])
|
|
| 227 |
- } |
|
| 228 |
- // TODO: array of pointers won't print correctly |
|
| 229 |
- return fmt.Errorf("you must provide at least one source code repository with --code for the images: %v", requiresSource)
|
|
| 230 |
- } |
|
| 231 |
- } |
|
| 232 |
- return nil |
|
| 233 |
-} |
|
| 234 |
- |
|
| 235 |
-// detectSource tries to match each source repository to an image type |
|
| 236 |
-func (c *newAppConfig) detectSource(repositories []*SourceRepository) error {
|
|
| 237 |
- errs := []error{}
|
|
| 238 |
- for _, repo := range repositories {
|
|
| 239 |
- // if the repository is being used by one of the images, we don't need to detect its type (unless we want to double check) |
|
| 240 |
- if repo.InUse() {
|
|
| 241 |
- continue |
|
| 242 |
- } |
|
| 243 |
- path, err := repo.LocalPath() |
|
| 244 |
- if err != nil {
|
|
| 245 |
- errs = append(errs, err) |
|
| 246 |
- continue |
|
| 247 |
- } |
|
| 248 |
- info, err := c.detector.Detect(path) |
|
| 249 |
- if err != nil {
|
|
| 250 |
- errs = append(errs, err) |
|
| 251 |
- continue |
|
| 252 |
- } |
|
| 253 |
- if info.Dockerfile != nil {
|
|
| 254 |
- // TODO: this should be using the reference builder flow, possibly by moving detectSource up before other steps |
|
| 255 |
- /*if from, ok := info.Dockerfile.GetDirective("FROM"); ok {
|
|
| 256 |
- input, _, err := NewComponentInput(from[0]) |
|
| 257 |
- if err != nil {
|
|
| 258 |
- errs = append(errs, err) |
|
| 259 |
- continue |
|
| 260 |
- } |
|
| 261 |
- input. |
|
| 262 |
- }*/ |
|
| 263 |
- repo.BuildWithDocker() |
|
| 264 |
- continue |
|
| 265 |
- } |
|
| 266 |
- |
|
| 267 |
- terms := info.Terms() |
|
| 268 |
- matches, err := c.searcher.Search(terms) |
|
| 269 |
- if err != nil {
|
|
| 270 |
- errs = append(errs, err) |
|
| 271 |
- continue |
|
| 272 |
- } |
|
| 273 |
- if len(matches) == 0 {
|
|
| 274 |
- errs = append(errs, fmt.Errorf("we could not find any images that match the source repo %q (looked for: %v) and this repository does not have a Dockerfile - you'll need to choose a source builder image to continue", repo, terms))
|
|
| 275 |
- continue |
|
| 276 |
- } |
|
| 277 |
- errs = append(errs, fmt.Errorf("found the following possible images to use to build this source repository: %v - to continue, you'll need to specify which image to use with %q", matches, repo))
|
|
| 278 |
- } |
|
| 279 |
- return util.SliceToError(errs) |
|
| 280 |
-} |
|
| 281 |
- |
|
| 282 |
-// buildPipelines converts a set of resolved, valid references into pipelines. |
|
| 283 |
-func (c *newAppConfig) buildPipelines(components ComponentReferences, environment app.Environment) (app.PipelineGroup, error) {
|
|
| 284 |
- pipelines := app.PipelineGroup{}
|
|
| 285 |
- for _, group := range components.Group() {
|
|
| 286 |
- glog.V(2).Infof("found group: %#v", group)
|
|
| 287 |
- common := app.PipelineGroup{}
|
|
| 288 |
- for _, ref := range group {
|
|
| 289 |
- |
|
| 290 |
- var pipeline *app.Pipeline |
|
| 291 |
- if ref.Input().ExpectToBuild {
|
|
| 292 |
- glog.V(2).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses)
|
|
| 293 |
- input, err := InputImageFromMatch(ref.Input().Match) |
|
| 294 |
- if err != nil {
|
|
| 295 |
- return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
|
|
| 296 |
- } |
|
| 297 |
- strategy, source, err := StrategyAndSourceForRepository(ref.Input().Uses) |
|
| 298 |
- if err != nil {
|
|
| 299 |
- return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
|
|
| 300 |
- } |
|
| 301 |
- if pipeline, err = app.NewBuildPipeline(ref.Input().String(), input, strategy, source); err != nil {
|
|
| 302 |
- return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
|
|
| 303 |
- } |
|
| 304 |
- |
|
| 305 |
- } else {
|
|
| 306 |
- glog.V(2).Infof("will include %q", ref)
|
|
| 307 |
- input, err := InputImageFromMatch(ref.Input().Match) |
|
| 308 |
- if err != nil {
|
|
| 309 |
- return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
|
|
| 310 |
- } |
|
| 311 |
- if pipeline, err = app.NewImagePipeline(ref.Input().String(), input); err != nil {
|
|
| 312 |
- return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
|
|
| 313 |
- } |
|
| 314 |
- } |
|
| 315 |
- |
|
| 316 |
- if err := pipeline.NeedsDeployment(environment); err != nil {
|
|
| 317 |
- return nil, fmt.Errorf("can't set up a deployment for %q: %v", ref.Input(), err)
|
|
| 318 |
- } |
|
| 319 |
- common = append(common, pipeline) |
|
| 320 |
- } |
|
| 321 |
- |
|
| 322 |
- if err := common.Reduce(); err != nil {
|
|
| 323 |
- return nil, fmt.Errorf("can't create a pipeline from %s: %v", common, err)
|
|
| 324 |
- } |
|
| 325 |
- pipelines = append(pipelines, common...) |
|
| 326 |
- } |
|
| 327 |
- return pipelines, nil |
|
| 328 |
-} |
|
| 329 |
- |
|
| 330 |
-// Run executes the provided config. |
|
| 331 |
-func (c *newAppConfig) Run(f *cmd.Factory, out io.Writer, helpFn func() error) error {
|
|
| 332 |
- components, repositories, environment, err := c.validate() |
|
| 333 |
- if err != nil {
|
|
| 334 |
- return err |
|
| 335 |
- } |
|
| 336 |
- |
|
| 337 |
- hasSource := len(repositories) != 0 |
|
| 338 |
- hasImages := len(components) != 0 |
|
| 339 |
- if !hasSource && !hasImages {
|
|
| 340 |
- // display help page |
|
| 341 |
- // TODO: return usage error, which should trigger help display |
|
| 342 |
- return helpFn() |
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- if err := c.resolve(components); err != nil {
|
|
| 346 |
- return err |
|
| 347 |
- } |
|
| 348 |
- |
|
| 349 |
- if err := c.ensureHasSource(components, repositories); err != nil {
|
|
| 350 |
- return err |
|
| 351 |
- } |
|
| 352 |
- |
|
| 353 |
- glog.V(4).Infof("Code %v", repositories)
|
|
| 354 |
- glog.V(4).Infof("Images %v", components)
|
|
| 355 |
- |
|
| 356 |
- if err := c.detectSource(repositories); err != nil {
|
|
| 357 |
- return err |
|
| 358 |
- } |
|
| 359 |
- |
|
| 360 |
- pipelines, err := c.buildPipelines(components, app.Environment(environment)) |
|
| 361 |
- if err != nil {
|
|
| 362 |
- return err |
|
| 363 |
- } |
|
| 364 |
- |
|
| 365 |
- objects := app.Objects{}
|
|
| 366 |
- accept := app.NewAcceptFirst() |
|
| 367 |
- for _, p := range pipelines {
|
|
| 368 |
- obj, err := p.Objects(accept) |
|
| 369 |
- if err != nil {
|
|
| 370 |
- return fmt.Errorf("can't setup %q: %v", p.From, err)
|
|
| 371 |
- } |
|
| 372 |
- objects = append(objects, obj...) |
|
| 373 |
- } |
|
| 374 |
- |
|
| 375 |
- objects = app.AddServices(objects) |
|
| 376 |
- |
|
| 377 |
- list := &kapi.List{Items: objects}
|
|
| 378 |
- p, err := kubectl.GetPrinter("yaml", "", "v1beta1", kapi.Scheme, nil)
|
|
| 379 |
- if err != nil {
|
|
| 380 |
- return err |
|
| 381 |
- } |
|
| 382 |
- return p.PrintObj(list, out) |
|
| 383 |
-} |
| 384 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,185 +0,0 @@ |
| 1 |
-package new |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "net/url" |
|
| 6 |
- "os" |
|
| 7 |
- "path/filepath" |
|
| 8 |
- "regexp" |
|
| 9 |
- "strings" |
|
| 10 |
- |
|
| 11 |
- "github.com/openshift/origin/pkg/generate/app" |
|
| 12 |
- "github.com/openshift/origin/pkg/generate/dockerfile" |
|
| 13 |
- "github.com/openshift/origin/pkg/generate/source" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-var ( |
|
| 17 |
- argumentGit = regexp.MustCompile("^(http://|https://|git@|git://).*\\.git(?:#([a-zA-Z0-9]*))?$")
|
|
| 18 |
- argumentGitProtocol = regexp.MustCompile("^git@")
|
|
| 19 |
- argumentPath = regexp.MustCompile("^\\.")
|
|
| 20 |
-) |
|
| 21 |
- |
|
| 22 |
-func isPossibleSourceRepository(s string) bool {
|
|
| 23 |
- return argumentGit.MatchString(s) || argumentGitProtocol.MatchString(s) || argumentPath.MatchString(s) |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// SourceRepository represents an code repository that may be the target of a build. |
|
| 27 |
-type SourceRepository struct {
|
|
| 28 |
- location string |
|
| 29 |
- url url.URL |
|
| 30 |
- |
|
| 31 |
- usedBy []ComponentReference |
|
| 32 |
- buildWithDocker bool |
|
| 33 |
-} |
|
| 34 |
- |
|
| 35 |
-// NewSourceRepository creates a reference to a local or remote source code repository from |
|
| 36 |
-// a URL or path. |
|
| 37 |
-func NewSourceRepository(s string) (*SourceRepository, error) {
|
|
| 38 |
- var location *url.URL |
|
| 39 |
- switch {
|
|
| 40 |
- case strings.HasPrefix(s, "git@"): |
|
| 41 |
- base := "git://" + strings.TrimPrefix(s, "git@") |
|
| 42 |
- url, err := url.Parse(base) |
|
| 43 |
- if err != nil {
|
|
| 44 |
- return nil, err |
|
| 45 |
- } |
|
| 46 |
- location = url |
|
| 47 |
- |
|
| 48 |
- default: |
|
| 49 |
- uri, err := url.Parse(s) |
|
| 50 |
- if err != nil {
|
|
| 51 |
- return nil, err |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- if uri.Scheme == "" {
|
|
| 55 |
- path := s |
|
| 56 |
- ref := "" |
|
| 57 |
- segments := strings.SplitN(path, "#", 2) |
|
| 58 |
- if len(segments) == 2 {
|
|
| 59 |
- path, ref = segments[0], segments[1] |
|
| 60 |
- } |
|
| 61 |
- path, err := filepath.Abs(path) |
|
| 62 |
- if err != nil {
|
|
| 63 |
- return nil, err |
|
| 64 |
- } |
|
| 65 |
- uri = &url.URL{
|
|
| 66 |
- Scheme: "file", |
|
| 67 |
- Path: path, |
|
| 68 |
- Fragment: ref, |
|
| 69 |
- } |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- location = uri |
|
| 73 |
- } |
|
| 74 |
- return &SourceRepository{
|
|
| 75 |
- location: s, |
|
| 76 |
- url: *location, |
|
| 77 |
- }, nil |
|
| 78 |
-} |
|
| 79 |
- |
|
| 80 |
-func (r *SourceRepository) UsedBy(ref ComponentReference) {
|
|
| 81 |
- r.usedBy = append(r.usedBy, ref) |
|
| 82 |
-} |
|
| 83 |
- |
|
| 84 |
-func (r *SourceRepository) Remote() bool {
|
|
| 85 |
- return r.url.Scheme != "file" |
|
| 86 |
-} |
|
| 87 |
- |
|
| 88 |
-func (r *SourceRepository) InUse() bool {
|
|
| 89 |
- return len(r.usedBy) > 0 |
|
| 90 |
-} |
|
| 91 |
- |
|
| 92 |
-func (r *SourceRepository) BuildWithDocker() {
|
|
| 93 |
- r.buildWithDocker = true |
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-func (r *SourceRepository) IsDockerBuild() bool {
|
|
| 97 |
- return r.buildWithDocker |
|
| 98 |
-} |
|
| 99 |
- |
|
| 100 |
-func (r *SourceRepository) String() string {
|
|
| 101 |
- return r.location |
|
| 102 |
-} |
|
| 103 |
- |
|
| 104 |
-func (r *SourceRepository) LocalPath() (string, error) {
|
|
| 105 |
- switch {
|
|
| 106 |
- case r.url.Scheme == "file": |
|
| 107 |
- return r.url.Path, nil |
|
| 108 |
- // TODO: implement other types |
|
| 109 |
- // TODO: lazy cache (predictably?) |
|
| 110 |
- default: |
|
| 111 |
- |
|
| 112 |
- return "", fmt.Errorf("reading local repositories is not implemented: %q", r.location)
|
|
| 113 |
- } |
|
| 114 |
-} |
|
| 115 |
- |
|
| 116 |
-type SourceRepositoryInfo struct {
|
|
| 117 |
- Path string |
|
| 118 |
- Types []SourceLanguageType |
|
| 119 |
- Dockerfile dockerfile.Dockerfile |
|
| 120 |
-} |
|
| 121 |
- |
|
| 122 |
-func (info *SourceRepositoryInfo) Terms() []string {
|
|
| 123 |
- terms := []string{}
|
|
| 124 |
- for i := range info.Types {
|
|
| 125 |
- terms = append(terms, info.Types[i].Platform) |
|
| 126 |
- } |
|
| 127 |
- return terms |
|
| 128 |
-} |
|
| 129 |
- |
|
| 130 |
-type SourceLanguageType struct {
|
|
| 131 |
- Platform string |
|
| 132 |
- Version string |
|
| 133 |
-} |
|
| 134 |
- |
|
| 135 |
-type Detector interface {
|
|
| 136 |
- Detect(dir string) (*SourceRepositoryInfo, error) |
|
| 137 |
-} |
|
| 138 |
- |
|
| 139 |
-type sourceRepositoryEnumerator struct {
|
|
| 140 |
- detectors source.Detectors |
|
| 141 |
- tester dockerfile.Tester |
|
| 142 |
-} |
|
| 143 |
- |
|
| 144 |
-func (e sourceRepositoryEnumerator) Detect(dir string) (*SourceRepositoryInfo, error) {
|
|
| 145 |
- info := &SourceRepositoryInfo{
|
|
| 146 |
- Path: dir, |
|
| 147 |
- } |
|
| 148 |
- for _, d := range e.detectors {
|
|
| 149 |
- if detected, ok := d(dir); ok {
|
|
| 150 |
- info.Types = append(info.Types, SourceLanguageType{
|
|
| 151 |
- Platform: detected.Platform, |
|
| 152 |
- Version: detected.Version, |
|
| 153 |
- }) |
|
| 154 |
- } |
|
| 155 |
- } |
|
| 156 |
- if path, ok, err := e.tester.Has(dir); err == nil && ok {
|
|
| 157 |
- file, err := os.Open(path) |
|
| 158 |
- if err != nil {
|
|
| 159 |
- return nil, err |
|
| 160 |
- } |
|
| 161 |
- defer file.Close() |
|
| 162 |
- dockerfile, err := dockerfile.NewParser().Parse(file) |
|
| 163 |
- if err != nil {
|
|
| 164 |
- return nil, err |
|
| 165 |
- } |
|
| 166 |
- info.Dockerfile = dockerfile |
|
| 167 |
- } |
|
| 168 |
- return info, nil |
|
| 169 |
-} |
|
| 170 |
- |
|
| 171 |
-func StrategyAndSourceForRepository(repo *SourceRepository) (*app.BuildStrategyRef, *app.SourceRef, error) {
|
|
| 172 |
- // TODO: replace with repository origin lookup, then in the future replace with auto push repository to server |
|
| 173 |
- if !repo.Remote() {
|
|
| 174 |
- return nil, nil, fmt.Errorf("the repository %q can't be used, as the CLI does not yet support pushing a local repository from your filesystem to OpenShift", repo)
|
|
| 175 |
- } |
|
| 176 |
- strategy := &app.BuildStrategyRef{
|
|
| 177 |
- IsDockerBuild: repo.IsDockerBuild(), |
|
| 178 |
- DockerContext: "", |
|
| 179 |
- } |
|
| 180 |
- source := &app.SourceRef{
|
|
| 181 |
- URL: &repo.url, |
|
| 182 |
- Ref: repo.url.Fragment, |
|
| 183 |
- } |
|
| 184 |
- return strategy, source, nil |
|
| 185 |
-} |
| 186 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,79 @@ |
| 0 |
+package cmd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors" |
|
| 6 |
+ "github.com/golang/glog" |
|
| 7 |
+ "github.com/spf13/cobra" |
|
| 8 |
+ |
|
| 9 |
+ dockerutil "github.com/openshift/origin/pkg/cmd/util/docker" |
|
| 10 |
+ newcmd "github.com/openshift/origin/pkg/generate/app/cmd" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+type usage interface {
|
|
| 14 |
+ UsageError(commandName string) string |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+const longNewAppDescription = ` |
|
| 18 |
+Create a new application in OpenShift by specifying source code, templates, and/or images. |
|
| 19 |
+ |
|
| 20 |
+Examples: |
|
| 21 |
+ |
|
| 22 |
+ $ osc new-app . |
|
| 23 |
+ <try to create an application based on the source code in the current directory> |
|
| 24 |
+ |
|
| 25 |
+ $ osc new-app mysql |
|
| 26 |
+ <use the public DockerHub MySQL image to create an app> |
|
| 27 |
+ |
|
| 28 |
+ $ osc new-app myregistry.com/mycompany/mysql |
|
| 29 |
+ <use a MySQL image in a private registry to create an app> |
|
| 30 |
+ |
|
| 31 |
+ $ osc new-app openshift/ruby-20-centos~git@github.com/mfojtik/sinatra-app-example |
|
| 32 |
+ <build an application using the OpenShift Ruby DockerHub image and an example repo>` |
|
| 33 |
+ |
|
| 34 |
+func NewCmdNewApplication(f *Factory, out io.Writer) *cobra.Command {
|
|
| 35 |
+ config := newcmd.NewAppConfig() |
|
| 36 |
+ helper := dockerutil.NewHelper() |
|
| 37 |
+ cmd := &cobra.Command{
|
|
| 38 |
+ Use: "new-app <components> [--code=<path|url>]", |
|
| 39 |
+ Short: "Create a new application", |
|
| 40 |
+ Long: longNewAppDescription, |
|
| 41 |
+ |
|
| 42 |
+ Run: func(c *cobra.Command, args []string) {
|
|
| 43 |
+ if dockerClient, _, err := helper.GetClient(); err == nil {
|
|
| 44 |
+ config.SetDockerClient(dockerClient) |
|
| 45 |
+ } |
|
| 46 |
+ if osclient, _, err := f.Clients(c); err == nil {
|
|
| 47 |
+ namespace, err := f.DefaultNamespace(c) |
|
| 48 |
+ checkErr(err) |
|
| 49 |
+ config.SetOpenShiftClient(osclient, namespace) |
|
| 50 |
+ } else {
|
|
| 51 |
+ glog.Warningf("error getting client: %v", err)
|
|
| 52 |
+ } |
|
| 53 |
+ unknown := config.AddArguments(args) |
|
| 54 |
+ if len(unknown) != 0 {
|
|
| 55 |
+ glog.Fatalf("Did not recognize the following arguments: %v", unknown)
|
|
| 56 |
+ } |
|
| 57 |
+ if err := config.Run(out, c.Help); err != nil {
|
|
| 58 |
+ if errs, ok := err.(errors.Aggregate); ok {
|
|
| 59 |
+ if len(errs.Errors()) == 1 {
|
|
| 60 |
+ err = errs.Errors()[0] |
|
| 61 |
+ } |
|
| 62 |
+ } |
|
| 63 |
+ if u, ok := err.(usage); ok {
|
|
| 64 |
+ glog.Fatal(u.UsageError(c.CommandPath())) |
|
| 65 |
+ } |
|
| 66 |
+ glog.Fatalf("Error: %v", err)
|
|
| 67 |
+ } |
|
| 68 |
+ }, |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ cmd.Flags().Var(&config.SourceRepositories, "code", "Source code to use to build this application.") |
|
| 72 |
+ cmd.Flags().VarP(&config.ImageStreams, "image", "i", "Name of an OpenShift image repository to use in the app.") |
|
| 73 |
+ cmd.Flags().Var(&config.DockerImages, "docker-image", "Name of a Docker image to include in the app.") |
|
| 74 |
+ cmd.Flags().Var(&config.Groups, "group", "Indicate components that should be grouped together as <comp1>+<comp2>.") |
|
| 75 |
+ cmd.Flags().VarP(&config.Environment, "env", "e", "Specify key value pairs of environment variables to set into each container.") |
|
| 76 |
+ cmd.Flags().StringVar(&config.TypeOfBuild, "build", "", "Specify the type of build to use if you don't want to detect (docker|source)") |
|
| 77 |
+ return cmd |
|
| 78 |
+} |
| 0 | 79 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,299 @@ |
| 0 |
+package generate |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "os" |
|
| 6 |
+ "strings" |
|
| 7 |
+ |
|
| 8 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 9 |
+ kubecmd "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" |
|
| 10 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors" |
|
| 11 |
+ "github.com/fsouza/go-dockerclient" |
|
| 12 |
+ "github.com/golang/glog" |
|
| 13 |
+ "github.com/spf13/cobra" |
|
| 14 |
+ |
|
| 15 |
+ "github.com/openshift/origin/pkg/api/latest" |
|
| 16 |
+ osclient "github.com/openshift/origin/pkg/client" |
|
| 17 |
+ "github.com/openshift/origin/pkg/cmd/cli" |
|
| 18 |
+ "github.com/openshift/origin/pkg/cmd/cli/cmd" |
|
| 19 |
+ cmdutil "github.com/openshift/origin/pkg/cmd/util" |
|
| 20 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
| 21 |
+ dh "github.com/openshift/origin/pkg/cmd/util/docker" |
|
| 22 |
+ "github.com/openshift/origin/pkg/dockerregistry" |
|
| 23 |
+ genapp "github.com/openshift/origin/pkg/generate/app" |
|
| 24 |
+ gen "github.com/openshift/origin/pkg/generate/generator" |
|
| 25 |
+ "github.com/openshift/origin/pkg/generate/source" |
|
| 26 |
+) |
|
| 27 |
+ |
|
| 28 |
+const longDescription = ` |
|
| 29 |
+Experimental command |
|
| 30 |
+ |
|
| 31 |
+Generate configuration to build and deploy code in OpenShift from a source code |
|
| 32 |
+repository. |
|
| 33 |
+ |
|
| 34 |
+Docker builds - If a Dockerfile is present in the source code repository, then |
|
| 35 |
+a docker build is generated. |
|
| 36 |
+ |
|
| 37 |
+STI builds - If no builder image is specified as an argument, generate will detect |
|
| 38 |
+the type of source repository (JEE, Ruby, NodeJS) and associate a default builder |
|
| 39 |
+to it. |
|
| 40 |
+ |
|
| 41 |
+Services and Exposed Ports - For Docker builds, generate looks for EXPOSE directives |
|
| 42 |
+in the Dockerfile to determine which ports to expose. For STI builds, generate will |
|
| 43 |
+use the exposed ports of the builder image. In either case, if a different set of |
|
| 44 |
+ports needs to be exposed, use the --ports flag to specify them. Services will be |
|
| 45 |
+generated using these ports as well. |
|
| 46 |
+ |
|
| 47 |
+ |
|
| 48 |
+Usage: |
|
| 49 |
+openshift ex generate [source] |
|
| 50 |
+ |
|
| 51 |
+The source parameter may be a directory or a repository URL. |
|
| 52 |
+If not specified, the current directory is used. |
|
| 53 |
+ |
|
| 54 |
+Examples: |
|
| 55 |
+ |
|
| 56 |
+ $ openshift ex generate |
|
| 57 |
+ <finds a git repository in the current directory and builds artifacts based on detection> |
|
| 58 |
+ |
|
| 59 |
+ $ openshift ex generate ./repo/dir |
|
| 60 |
+ <specify the directory for the repository to use> |
|
| 61 |
+ |
|
| 62 |
+ $ openshift ex generate https://github.com/openshift/ruby-hello-world.git |
|
| 63 |
+ <use a remote git repository> |
|
| 64 |
+ |
|
| 65 |
+ $ openshift ex generate --builder-image=openshift/ruby-20-centos |
|
| 66 |
+ <force the application to use the specific builder-image>` |
|
| 67 |
+ |
|
| 68 |
+type params struct {
|
|
| 69 |
+ name, |
|
| 70 |
+ sourceDir, |
|
| 71 |
+ sourceRef, |
|
| 72 |
+ sourceURL, |
|
| 73 |
+ dockerContext, |
|
| 74 |
+ builderImage, |
|
| 75 |
+ ports string |
|
| 76 |
+ env cmdutil.Environment |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+func NewCmdGenerate(name string) *cobra.Command {
|
|
| 80 |
+ cfg := clientcmd.NewConfig() |
|
| 81 |
+ dockerHelper := dh.NewHelper() |
|
| 82 |
+ input := params{}
|
|
| 83 |
+ var factory *cmd.Factory |
|
| 84 |
+ |
|
| 85 |
+ c := &cobra.Command{
|
|
| 86 |
+ Use: fmt.Sprintf("%s%s", name, clientcmd.ConfigSyntax),
|
|
| 87 |
+ Short: "Generates an application configuration from a source repository", |
|
| 88 |
+ Long: longDescription, |
|
| 89 |
+ Run: func(c *cobra.Command, args []string) {
|
|
| 90 |
+ _, osClient, err := cfg.Clients() |
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ osClient = nil |
|
| 93 |
+ } |
|
| 94 |
+ dockerClient, _, err := dockerHelper.GetClient() |
|
| 95 |
+ if err != nil {
|
|
| 96 |
+ osClient = nil |
|
| 97 |
+ } |
|
| 98 |
+ if len(args) == 1 {
|
|
| 99 |
+ if source.IsRemoteRepository(args[0]) {
|
|
| 100 |
+ input.sourceURL = args[0] |
|
| 101 |
+ } else {
|
|
| 102 |
+ input.sourceDir = args[0] |
|
| 103 |
+ } |
|
| 104 |
+ } |
|
| 105 |
+ if len(input.sourceDir) == 0 && len(input.sourceURL) == 0 {
|
|
| 106 |
+ if input.sourceDir, err = os.Getwd(); err != nil {
|
|
| 107 |
+ exitWithError(err) |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 110 |
+ if envParam := kubecmd.GetFlagString(c, "environment"); len(envParam) > 0 {
|
|
| 111 |
+ envVars := strings.Split(envParam, ",") |
|
| 112 |
+ env, _, errs := cmdutil.ParseEnvironmentArguments(envVars) |
|
| 113 |
+ if len(errs) > 0 {
|
|
| 114 |
+ exitWithError(errors.NewAggregate(errs)) |
|
| 115 |
+ } |
|
| 116 |
+ input.env = env |
|
| 117 |
+ } |
|
| 118 |
+ namespace, err := factory.DefaultNamespace(c) |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ namespace = "" |
|
| 121 |
+ } |
|
| 122 |
+ imageResolver := newImageResolver(namespace, osClient, dockerClient) |
|
| 123 |
+ |
|
| 124 |
+ if err = generateApp(input, imageResolver, os.Stdout); err != nil {
|
|
| 125 |
+ exitWithError(err) |
|
| 126 |
+ } |
|
| 127 |
+ }, |
|
| 128 |
+ } |
|
| 129 |
+ clientConfig := cli.DefaultClientConfig(c.PersistentFlags()) |
|
| 130 |
+ factory = cmd.NewFactory(clientConfig) |
|
| 131 |
+ factory.BindFlags(c.PersistentFlags()) |
|
| 132 |
+ |
|
| 133 |
+ flag := c.Flags() |
|
| 134 |
+ flag.StringVar(&input.name, "name", "", "Set name to use for generated application artifacts") |
|
| 135 |
+ flag.StringVar(&input.sourceRef, "ref", "", "Set the name of the repository branch/ref to use") |
|
| 136 |
+ flag.StringVar(&input.sourceURL, "source-url", "", "Set the source URL") |
|
| 137 |
+ flag.StringVar(&input.dockerContext, "docker-context", "", "Context path for Dockerfile if creating a Docker build") |
|
| 138 |
+ flag.StringVar(&input.builderImage, "builder-image", "", "Image to use for STI build") |
|
| 139 |
+ flag.StringVarP(&input.ports, "ports", "p", "", "Comma-separated list of ports to expose on pod deployment") |
|
| 140 |
+ flag.StringP("environment", "e", "", "Comma-separated list of environment variables to add to the deployment. Should be in the form of var1=value1,var2=value2,...")
|
|
| 141 |
+ dockerHelper.InstallFlags(flag) |
|
| 142 |
+ return c |
|
| 143 |
+} |
|
| 144 |
+ |
|
| 145 |
+func newImageResolver(namespace string, osClient osclient.Interface, dockerClient *docker.Client) genapp.Resolver {
|
|
| 146 |
+ resolver := genapp.PerfectMatchWeightedResolver{}
|
|
| 147 |
+ |
|
| 148 |
+ if dockerClient != nil {
|
|
| 149 |
+ localDockerResolver := &genapp.DockerClientResolver{dockerClient}
|
|
| 150 |
+ resolver = append(resolver, genapp.WeightedResolver{localDockerResolver, 0.0})
|
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ if osClient != nil {
|
|
| 154 |
+ namespaces := []string{}
|
|
| 155 |
+ if len(namespace) > 0 {
|
|
| 156 |
+ namespaces = append(namespaces, namespace) |
|
| 157 |
+ } |
|
| 158 |
+ namespaces = append(namespaces, "default") |
|
| 159 |
+ imageStreamResolver := &genapp.ImageStreamResolver{
|
|
| 160 |
+ Client: osClient, |
|
| 161 |
+ Images: osClient, |
|
| 162 |
+ Namespaces: namespaces, |
|
| 163 |
+ } |
|
| 164 |
+ resolver = append(resolver, genapp.WeightedResolver{imageStreamResolver, 0.0})
|
|
| 165 |
+ } |
|
| 166 |
+ |
|
| 167 |
+ dockerRegistryResolver := &genapp.DockerRegistryResolver{dockerregistry.NewClient()}
|
|
| 168 |
+ resolver = append(resolver, genapp.WeightedResolver{dockerRegistryResolver, 0.0})
|
|
| 169 |
+ |
|
| 170 |
+ return resolver |
|
| 171 |
+} |
|
| 172 |
+ |
|
| 173 |
+func generateSourceRef(url string, dir string, ref string, name string) (*genapp.SourceRef, error) {
|
|
| 174 |
+ srcRefGen := gen.NewSourceRefGenerator() |
|
| 175 |
+ var result *genapp.SourceRef |
|
| 176 |
+ var err error |
|
| 177 |
+ if len(url) > 0 {
|
|
| 178 |
+ glog.V(3).Infof("Generating source reference from URL: %s", url)
|
|
| 179 |
+ if result, err = srcRefGen.FromGitURL(url); err != nil {
|
|
| 180 |
+ return nil, err |
|
| 181 |
+ } |
|
| 182 |
+ } else {
|
|
| 183 |
+ glog.V(3).Infof("Generating source reference from directory: %s", dir)
|
|
| 184 |
+ if result, err = srcRefGen.FromDirectory(dir); err != nil {
|
|
| 185 |
+ return nil, err |
|
| 186 |
+ } |
|
| 187 |
+ } |
|
| 188 |
+ if len(ref) > 0 {
|
|
| 189 |
+ result.Ref = ref |
|
| 190 |
+ } |
|
| 191 |
+ if len(name) > 0 {
|
|
| 192 |
+ result.Name = name |
|
| 193 |
+ } |
|
| 194 |
+ return result, nil |
|
| 195 |
+} |
|
| 196 |
+ |
|
| 197 |
+func generateBuildStrategyRef(srcRef *genapp.SourceRef, dockerContext string, builderImage string) (*genapp.BuildStrategyRef, error) {
|
|
| 198 |
+ strategyRefGen := gen.NewBuildStrategyRefGenerator(source.DefaultDetectors) |
|
| 199 |
+ imageRefGen := gen.NewImageRefGenerator() |
|
| 200 |
+ if len(dockerContext) > 0 {
|
|
| 201 |
+ glog.V(3).Infof("Generating build strategy reference using dockerContext: %s", dockerContext)
|
|
| 202 |
+ return strategyRefGen.FromSourceRefAndDockerContext(*srcRef, dockerContext) |
|
| 203 |
+ } else if len(builderImage) > 0 {
|
|
| 204 |
+ glog.V(3).Infof("Generating build strategy reference using builder image: %s", builderImage)
|
|
| 205 |
+ builderRef, err := imageRefGen.FromName(builderImage) |
|
| 206 |
+ if err != nil {
|
|
| 207 |
+ return nil, err |
|
| 208 |
+ } |
|
| 209 |
+ return strategyRefGen.FromSTIBuilderImage(builderRef) |
|
| 210 |
+ } else {
|
|
| 211 |
+ glog.V(3).Infof("Detecting build strategy using source reference: %#v", srcRef)
|
|
| 212 |
+ return strategyRefGen.FromSourceRef(*srcRef) |
|
| 213 |
+ } |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+func generateOutputImageRef(srcRef *genapp.SourceRef, strategyRef *genapp.BuildStrategyRef, ports string) (*genapp.ImageRef, error) {
|
|
| 217 |
+ imageRefGen := gen.NewImageRefGenerator() |
|
| 218 |
+ name, ok := srcRef.SuggestName() |
|
| 219 |
+ if !ok {
|
|
| 220 |
+ return nil, fmt.Errorf("cannot suggest a name for the output image. Please specify one with the --output-image argument")
|
|
| 221 |
+ } |
|
| 222 |
+ if len(ports) > 0 {
|
|
| 223 |
+ portList := strings.Split(ports, ",") |
|
| 224 |
+ return imageRefGen.FromNameAndPorts(name, portList) |
|
| 225 |
+ } |
|
| 226 |
+ if strategyRef.IsDockerBuild {
|
|
| 227 |
+ return imageRefGen.FromDockerfile(name, srcRef.Dir, strategyRef.DockerContext) |
|
| 228 |
+ } else {
|
|
| 229 |
+ return imageRefGen.FromName(name) |
|
| 230 |
+ } |
|
| 231 |
+} |
|
| 232 |
+ |
|
| 233 |
+func generateApp(input params, imageResolver genapp.Resolver, out io.Writer) error {
|
|
| 234 |
+ // Get a SourceRef |
|
| 235 |
+ srcRef, err := generateSourceRef(input.sourceURL, input.sourceDir, input.sourceRef, input.name) |
|
| 236 |
+ if err != nil {
|
|
| 237 |
+ return err |
|
| 238 |
+ } |
|
| 239 |
+ glog.V(2).Infof("Source reference: %#v", srcRef)
|
|
| 240 |
+ |
|
| 241 |
+ // Get a BuildStrategyRef |
|
| 242 |
+ strategyRef, err := generateBuildStrategyRef(srcRef, input.dockerContext, input.builderImage) |
|
| 243 |
+ if err != nil {
|
|
| 244 |
+ return err |
|
| 245 |
+ } |
|
| 246 |
+ glog.V(2).Infof("Generated build strategy reference: %#v", strategyRef)
|
|
| 247 |
+ |
|
| 248 |
+ // Get an ImageRef for Output |
|
| 249 |
+ outputRef, err := generateOutputImageRef(srcRef, strategyRef, input.ports) |
|
| 250 |
+ if err != nil {
|
|
| 251 |
+ return err |
|
| 252 |
+ } |
|
| 253 |
+ glog.V(2).Infof("Generated output image reference: %#v", outputRef)
|
|
| 254 |
+ |
|
| 255 |
+ outputRefCopy := *outputRef |
|
| 256 |
+ baseRef := &outputRefCopy |
|
| 257 |
+ if !strategyRef.IsDockerBuild {
|
|
| 258 |
+ if baseImageMatch, err := imageResolver.Resolve(strategyRef.Base.RepoName()); err == nil {
|
|
| 259 |
+ if inputRef, err := genapp.InputImageFromMatch(baseImageMatch); err == nil {
|
|
| 260 |
+ if len(input.ports) > 0 {
|
|
| 261 |
+ inputRef.Info = baseRef.Info |
|
| 262 |
+ } |
|
| 263 |
+ baseRef = inputRef |
|
| 264 |
+ } |
|
| 265 |
+ } |
|
| 266 |
+ } |
|
| 267 |
+ baseRef.AsImageRepository = false |
|
| 268 |
+ |
|
| 269 |
+ pipeline, err := genapp.NewBuildPipeline(outputRef.Name, baseRef, strategyRef, srcRef) |
|
| 270 |
+ if err != nil {
|
|
| 271 |
+ return err |
|
| 272 |
+ } |
|
| 273 |
+ env := genapp.Environment{}
|
|
| 274 |
+ for k, v := range input.env {
|
|
| 275 |
+ env[k] = v |
|
| 276 |
+ } |
|
| 277 |
+ if err := pipeline.NeedsDeployment(env); err != nil {
|
|
| 278 |
+ return err |
|
| 279 |
+ } |
|
| 280 |
+ |
|
| 281 |
+ objects, err := pipeline.Objects(genapp.NewAcceptFirst()) |
|
| 282 |
+ if err != nil {
|
|
| 283 |
+ return err |
|
| 284 |
+ } |
|
| 285 |
+ objects = genapp.AddServices(objects) |
|
| 286 |
+ list := &kapi.List{Items: objects}
|
|
| 287 |
+ output, err := latest.Codec.Encode(list) |
|
| 288 |
+ if err != nil {
|
|
| 289 |
+ return err |
|
| 290 |
+ } |
|
| 291 |
+ _, err = out.Write(output) |
|
| 292 |
+ return err |
|
| 293 |
+} |
|
| 294 |
+ |
|
| 295 |
+func exitWithError(err error) {
|
|
| 296 |
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
|
| 297 |
+ os.Exit(1) |
|
| 298 |
+} |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
|
| 10 | 10 |
"github.com/openshift/origin/pkg/cmd/cli" |
| 11 | 11 |
"github.com/openshift/origin/pkg/cmd/experimental/config" |
| 12 |
+ "github.com/openshift/origin/pkg/cmd/experimental/generate" |
|
| 12 | 13 |
"github.com/openshift/origin/pkg/cmd/experimental/policy" |
| 13 | 14 |
"github.com/openshift/origin/pkg/cmd/experimental/tokens" |
| 14 | 15 |
"github.com/openshift/origin/pkg/cmd/flagtypes" |
| ... | ... |
@@ -112,6 +113,7 @@ func newExperimentalCommand(parentName, name string) *cobra.Command {
|
| 112 | 112 |
experimental.AddCommand(config.NewCmdConfig(fmt.Sprintf("%s %s", parentName, name), "config"))
|
| 113 | 113 |
experimental.AddCommand(tokens.NewCmdTokens("tokens"))
|
| 114 | 114 |
experimental.AddCommand(policy.NewCommandPolicy("policy"))
|
| 115 |
+ experimental.AddCommand(generate.NewCmdGenerate("generate"))
|
|
| 115 | 116 |
return experimental |
| 116 | 117 |
} |
| 117 | 118 |
|
| ... | ... |
@@ -17,9 +17,9 @@ import ( |
| 17 | 17 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
| 18 | 18 |
"github.com/fsouza/go-dockerclient" |
| 19 | 19 |
|
| 20 |
- build "github.com/openshift/origin/pkg/build/api" |
|
| 21 |
- deploy "github.com/openshift/origin/pkg/deploy/api" |
|
| 22 |
- image "github.com/openshift/origin/pkg/image/api" |
|
| 20 |
+ buildapi "github.com/openshift/origin/pkg/build/api" |
|
| 21 |
+ deployapi "github.com/openshift/origin/pkg/deploy/api" |
|
| 22 |
+ imageapi "github.com/openshift/origin/pkg/image/api" |
|
| 23 | 23 |
) |
| 24 | 24 |
|
| 25 | 25 |
// NameSuggester is an object that can suggest a name for itself |
| ... | ... |
@@ -92,34 +92,38 @@ func nameFromGitURL(url *url.URL) (string, bool) {
|
| 92 | 92 |
|
| 93 | 93 |
// SourceRef is a reference to a build source |
| 94 | 94 |
type SourceRef struct {
|
| 95 |
- URL *url.URL |
|
| 96 |
- Ref string |
|
| 97 |
- Dir string |
|
| 95 |
+ URL *url.URL |
|
| 96 |
+ Ref string |
|
| 97 |
+ Dir string |
|
| 98 |
+ Name string |
|
| 98 | 99 |
} |
| 99 | 100 |
|
| 100 | 101 |
// SuggestName returns a name derived from the source URL |
| 101 | 102 |
func (r *SourceRef) SuggestName() (string, bool) {
|
| 103 |
+ if len(r.Name) > 0 {
|
|
| 104 |
+ return r.Name, true |
|
| 105 |
+ } |
|
| 102 | 106 |
return nameFromGitURL(r.URL) |
| 103 | 107 |
} |
| 104 | 108 |
|
| 105 | 109 |
// BuildSource returns an OpenShift BuildSource from the SourceRef |
| 106 |
-func (r *SourceRef) BuildSource() (*build.BuildSource, []build.BuildTriggerPolicy) {
|
|
| 107 |
- return &build.BuildSource{
|
|
| 108 |
- Type: build.BuildSourceGit, |
|
| 109 |
- Git: &build.GitBuildSource{
|
|
| 110 |
+func (r *SourceRef) BuildSource() (*buildapi.BuildSource, []buildapi.BuildTriggerPolicy) {
|
|
| 111 |
+ return &buildapi.BuildSource{
|
|
| 112 |
+ Type: buildapi.BuildSourceGit, |
|
| 113 |
+ Git: &buildapi.GitBuildSource{
|
|
| 110 | 114 |
URI: r.URL.String(), |
| 111 | 115 |
Ref: r.Ref, |
| 112 | 116 |
}, |
| 113 |
- }, []build.BuildTriggerPolicy{
|
|
| 117 |
+ }, []buildapi.BuildTriggerPolicy{
|
|
| 114 | 118 |
{
|
| 115 |
- Type: build.GithubWebHookType, |
|
| 116 |
- GithubWebHook: &build.WebHookTrigger{
|
|
| 119 |
+ Type: buildapi.GithubWebHookBuildTriggerType, |
|
| 120 |
+ GithubWebHook: &buildapi.WebHookTrigger{
|
|
| 117 | 121 |
Secret: generateSecret(20), |
| 118 | 122 |
}, |
| 119 | 123 |
}, |
| 120 | 124 |
{
|
| 121 |
- Type: build.GenericWebHookType, |
|
| 122 |
- GenericWebHook: &build.WebHookTrigger{
|
|
| 125 |
+ Type: buildapi.GenericWebHookBuildTriggerType, |
|
| 126 |
+ GenericWebHook: &buildapi.WebHookTrigger{
|
|
| 123 | 127 |
Secret: generateSecret(20), |
| 124 | 128 |
}, |
| 125 | 129 |
}, |
| ... | ... |
@@ -134,22 +138,22 @@ type BuildStrategyRef struct {
|
| 134 | 134 |
} |
| 135 | 135 |
|
| 136 | 136 |
// BuildStrategy builds an OpenShift BuildStrategy from a BuildStrategyRef |
| 137 |
-func (s *BuildStrategyRef) BuildStrategy() (*build.BuildStrategy, []build.BuildTriggerPolicy) {
|
|
| 137 |
+func (s *BuildStrategyRef) BuildStrategy() (*buildapi.BuildStrategy, []buildapi.BuildTriggerPolicy) {
|
|
| 138 | 138 |
if s.IsDockerBuild {
|
| 139 |
- strategy := &build.BuildStrategy{
|
|
| 140 |
- Type: build.DockerBuildStrategyType, |
|
| 139 |
+ strategy := &buildapi.BuildStrategy{
|
|
| 140 |
+ Type: buildapi.DockerBuildStrategyType, |
|
| 141 | 141 |
} |
| 142 | 142 |
if len(s.DockerContext) > 0 {
|
| 143 |
- strategy.DockerStrategy = &build.DockerBuildStrategy{
|
|
| 143 |
+ strategy.DockerStrategy = &buildapi.DockerBuildStrategy{
|
|
| 144 | 144 |
ContextDir: s.DockerContext, |
| 145 | 145 |
} |
| 146 | 146 |
} |
| 147 | 147 |
return strategy, s.Base.BuildTriggers() |
| 148 | 148 |
} |
| 149 | 149 |
|
| 150 |
- return &build.BuildStrategy{
|
|
| 151 |
- Type: build.STIBuildStrategyType, |
|
| 152 |
- STIStrategy: &build.STIBuildStrategy{
|
|
| 150 |
+ return &buildapi.BuildStrategy{
|
|
| 151 |
+ Type: buildapi.STIBuildStrategyType, |
|
| 152 |
+ STIStrategy: &buildapi.STIBuildStrategy{
|
|
| 153 | 153 |
Image: s.Base.NameReference(), |
| 154 | 154 |
}, |
| 155 | 155 |
}, nil |
| ... | ... |
@@ -164,14 +168,14 @@ type ImageRef struct {
|
| 164 | 164 |
|
| 165 | 165 |
AsImageRepository bool |
| 166 | 166 |
|
| 167 |
- Repository *image.ImageRepository |
|
| 168 |
- Info *docker.Image |
|
| 167 |
+ Repository *imageapi.ImageRepository |
|
| 168 |
+ Info *imageapi.DockerImage |
|
| 169 | 169 |
} |
| 170 | 170 |
|
| 171 | 171 |
// pullSpec returns the string that can be passed to Docker to fetch this |
| 172 | 172 |
// image. |
| 173 | 173 |
func (r *ImageRef) pullSpec() string {
|
| 174 |
- return image.JoinDockerPullSpec(r.Registry, r.Namespace, r.Name, r.Tag) |
|
| 174 |
+ return imageapi.JoinDockerPullSpec(r.Registry, r.Namespace, r.Name, r.Tag) |
|
| 175 | 175 |
} |
| 176 | 176 |
|
| 177 | 177 |
// NameReference returns the name that other OpenShift objects may refer to this |
| ... | ... |
@@ -203,22 +207,27 @@ func (r *ImageRef) SuggestName() (string, bool) {
|
| 203 | 203 |
return r.Name, true |
| 204 | 204 |
} |
| 205 | 205 |
|
| 206 |
-func (r *ImageRef) BuildOutput() *build.BuildOutput {
|
|
| 206 |
+func (r *ImageRef) BuildOutput() (*buildapi.BuildOutput, error) {
|
|
| 207 | 207 |
if r == nil {
|
| 208 |
- return &build.BuildOutput{}
|
|
| 208 |
+ return &buildapi.BuildOutput{}, nil
|
|
| 209 | 209 |
} |
| 210 |
- return &build.BuildOutput{
|
|
| 211 |
- ImageTag: r.NameReference(), |
|
| 212 |
- Registry: r.Registry, |
|
| 210 |
+ imageRepo, err := r.ImageRepository() |
|
| 211 |
+ if err != nil {
|
|
| 212 |
+ return nil, err |
|
| 213 | 213 |
} |
| 214 |
+ return &buildapi.BuildOutput{
|
|
| 215 |
+ To: &kapi.ObjectReference{
|
|
| 216 |
+ Name: imageRepo.Name, |
|
| 217 |
+ }, |
|
| 218 |
+ }, nil |
|
| 214 | 219 |
} |
| 215 | 220 |
|
| 216 |
-func (r *ImageRef) BuildTriggers() []build.BuildTriggerPolicy {
|
|
| 221 |
+func (r *ImageRef) BuildTriggers() []buildapi.BuildTriggerPolicy {
|
|
| 217 | 222 |
// TODO return triggers when image build triggers are available |
| 218 |
- return []build.BuildTriggerPolicy{}
|
|
| 223 |
+ return []buildapi.BuildTriggerPolicy{}
|
|
| 219 | 224 |
} |
| 220 | 225 |
|
| 221 |
-func (r *ImageRef) ImageRepository() (*image.ImageRepository, error) {
|
|
| 226 |
+func (r *ImageRef) ImageRepository() (*imageapi.ImageRepository, error) {
|
|
| 222 | 227 |
if r.Repository != nil {
|
| 223 | 228 |
return r.Repository, nil |
| 224 | 229 |
} |
| ... | ... |
@@ -228,7 +237,7 @@ func (r *ImageRef) ImageRepository() (*image.ImageRepository, error) {
|
| 228 | 228 |
return nil, fmt.Errorf("unable to suggest an image repository name for %q", r.pullSpec())
|
| 229 | 229 |
} |
| 230 | 230 |
|
| 231 |
- repo := &image.ImageRepository{
|
|
| 231 |
+ repo := &imageapi.ImageRepository{
|
|
| 232 | 232 |
ObjectMeta: kapi.ObjectMeta{
|
| 233 | 233 |
Name: name, |
| 234 | 234 |
}, |
| ... | ... |
@@ -240,23 +249,26 @@ func (r *ImageRef) ImageRepository() (*image.ImageRepository, error) {
|
| 240 | 240 |
return repo, nil |
| 241 | 241 |
} |
| 242 | 242 |
|
| 243 |
-func (r *ImageRef) DeployableContainer() (container *kapi.Container, triggers []deploy.DeploymentTriggerPolicy, err error) {
|
|
| 244 |
- if r.Info == nil {
|
|
| 245 |
- return nil, nil, fmt.Errorf("image info for %q is required to generate a container definition", r.Name)
|
|
| 246 |
- } |
|
| 243 |
+func (r *ImageRef) DeployableContainer() (container *kapi.Container, triggers []deployapi.DeploymentTriggerPolicy, err error) {
|
|
| 247 | 244 |
name, ok := r.SuggestName() |
| 248 | 245 |
if !ok {
|
| 249 | 246 |
return nil, nil, fmt.Errorf("unable to suggest a container name for the image %q", r.pullSpec())
|
| 250 | 247 |
} |
| 251 | 248 |
if r.AsImageRepository {
|
| 252 |
- triggers = []deploy.DeploymentTriggerPolicy{
|
|
| 249 |
+ tag := r.Tag |
|
| 250 |
+ if len(tag) == 0 {
|
|
| 251 |
+ tag = "latest" |
|
| 252 |
+ } |
|
| 253 |
+ triggers = []deployapi.DeploymentTriggerPolicy{
|
|
| 253 | 254 |
{
|
| 254 |
- Type: deploy.DeploymentTriggerOnImageChange, |
|
| 255 |
- ImageChangeParams: &deploy.DeploymentTriggerImageChangeParams{
|
|
| 255 |
+ Type: deployapi.DeploymentTriggerOnImageChange, |
|
| 256 |
+ ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{
|
|
| 256 | 257 |
Automatic: true, |
| 257 | 258 |
ContainerNames: []string{name},
|
| 258 |
- RepositoryName: r.NameReference(), |
|
| 259 |
- Tag: r.Tag, |
|
| 259 |
+ From: kapi.ObjectReference{
|
|
| 260 |
+ Name: name, |
|
| 261 |
+ }, |
|
| 262 |
+ Tag: tag, |
|
| 260 | 263 |
}, |
| 261 | 264 |
}, |
| 262 | 265 |
} |
| ... | ... |
@@ -269,7 +281,8 @@ func (r *ImageRef) DeployableContainer() (container *kapi.Container, triggers [] |
| 269 | 269 |
|
| 270 | 270 |
// If imageInfo present, append ports |
| 271 | 271 |
if r.Info != nil {
|
| 272 |
- for p := range r.Info.Config.ExposedPorts {
|
|
| 272 |
+ for sp := range r.Info.Config.ExposedPorts {
|
|
| 273 |
+ p := docker.Port(sp) |
|
| 273 | 274 |
port, err := strconv.Atoi(p.Port()) |
| 274 | 275 |
if err != nil {
|
| 275 | 276 |
return nil, nil, fmt.Errorf("failed to parse port %q: %v", p.Port(), err)
|
| ... | ... |
@@ -294,30 +307,34 @@ type BuildRef struct {
|
| 294 | 294 |
Output *ImageRef |
| 295 | 295 |
} |
| 296 | 296 |
|
| 297 |
-func (r *BuildRef) BuildConfig() (*build.BuildConfig, error) {
|
|
| 297 |
+func (r *BuildRef) BuildConfig() (*buildapi.BuildConfig, error) {
|
|
| 298 | 298 |
name, ok := NameSuggestions{r.Source, r.Output}.SuggestName()
|
| 299 | 299 |
if !ok {
|
| 300 | 300 |
return nil, fmt.Errorf("unable to suggest a name for this build config from %q", r.Source.URL)
|
| 301 | 301 |
} |
| 302 |
- source := &build.BuildSource{}
|
|
| 303 |
- sourceTriggers := []build.BuildTriggerPolicy{}
|
|
| 302 |
+ source := &buildapi.BuildSource{}
|
|
| 303 |
+ sourceTriggers := []buildapi.BuildTriggerPolicy{}
|
|
| 304 | 304 |
if r.Source != nil {
|
| 305 | 305 |
source, sourceTriggers = r.Source.BuildSource() |
| 306 | 306 |
} |
| 307 |
- strategy := &build.BuildStrategy{}
|
|
| 308 |
- strategyTriggers := []build.BuildTriggerPolicy{}
|
|
| 307 |
+ strategy := &buildapi.BuildStrategy{}
|
|
| 308 |
+ strategyTriggers := []buildapi.BuildTriggerPolicy{}
|
|
| 309 | 309 |
if r.Strategy != nil {
|
| 310 | 310 |
strategy, strategyTriggers = r.Strategy.BuildStrategy() |
| 311 | 311 |
} |
| 312 |
- return &build.BuildConfig{
|
|
| 312 |
+ output, err := r.Output.BuildOutput() |
|
| 313 |
+ if err != nil {
|
|
| 314 |
+ return nil, err |
|
| 315 |
+ } |
|
| 316 |
+ return &buildapi.BuildConfig{
|
|
| 313 | 317 |
ObjectMeta: kapi.ObjectMeta{
|
| 314 | 318 |
Name: name, |
| 315 | 319 |
}, |
| 316 | 320 |
Triggers: append(sourceTriggers, strategyTriggers...), |
| 317 |
- Parameters: build.BuildParameters{
|
|
| 321 |
+ Parameters: buildapi.BuildParameters{
|
|
| 318 | 322 |
Source: *source, |
| 319 | 323 |
Strategy: *strategy, |
| 320 |
- Output: *r.Output.BuildOutput(), |
|
| 324 |
+ Output: *output, |
|
| 321 | 325 |
}, |
| 322 | 326 |
}, nil |
| 323 | 327 |
} |
| ... | ... |
@@ -328,7 +345,7 @@ type DeploymentConfigRef struct {
|
| 328 | 328 |
} |
| 329 | 329 |
|
| 330 | 330 |
// TODO: take a pod template spec as argument |
| 331 |
-func (r *DeploymentConfigRef) DeploymentConfig() (*deploy.DeploymentConfig, error) {
|
|
| 331 |
+func (r *DeploymentConfigRef) DeploymentConfig() (*deployapi.DeploymentConfig, error) {
|
|
| 332 | 332 |
suggestions := NameSuggestions{}
|
| 333 | 333 |
for i := range r.Images {
|
| 334 | 334 |
suggestions = append(suggestions, r.Images[i]) |
| ... | ... |
@@ -342,10 +359,10 @@ func (r *DeploymentConfigRef) DeploymentConfig() (*deploy.DeploymentConfig, erro |
| 342 | 342 |
"deploymentconfig": name, |
| 343 | 343 |
} |
| 344 | 344 |
|
| 345 |
- triggers := []deploy.DeploymentTriggerPolicy{
|
|
| 345 |
+ triggers := []deployapi.DeploymentTriggerPolicy{
|
|
| 346 | 346 |
// By default, always deploy on change |
| 347 | 347 |
{
|
| 348 |
- Type: deploy.DeploymentTriggerOnConfigChange, |
|
| 348 |
+ Type: deployapi.DeploymentTriggerOnConfigChange, |
|
| 349 | 349 |
}, |
| 350 | 350 |
} |
| 351 | 351 |
|
| ... | ... |
@@ -364,11 +381,14 @@ func (r *DeploymentConfigRef) DeploymentConfig() (*deploy.DeploymentConfig, erro |
| 364 | 364 |
template.Containers[i].Env = append(template.Containers[i].Env, r.Env.List()...) |
| 365 | 365 |
} |
| 366 | 366 |
|
| 367 |
- return &deploy.DeploymentConfig{
|
|
| 367 |
+ return &deployapi.DeploymentConfig{
|
|
| 368 | 368 |
ObjectMeta: kapi.ObjectMeta{
|
| 369 | 369 |
Name: name, |
| 370 | 370 |
}, |
| 371 |
- Template: deploy.DeploymentTemplate{
|
|
| 371 |
+ Template: deployapi.DeploymentTemplate{
|
|
| 372 |
+ Strategy: deployapi.DeploymentStrategy{
|
|
| 373 |
+ Type: deployapi.DeploymentStrategyTypeRecreate, |
|
| 374 |
+ }, |
|
| 372 | 375 |
ControllerTemplate: kapi.ReplicationControllerSpec{
|
| 373 | 376 |
Replicas: 1, |
| 374 | 377 |
Selector: selector, |
| ... | ... |
@@ -5,19 +5,17 @@ import ( |
| 5 | 5 |
"net/url" |
| 6 | 6 |
"testing" |
| 7 | 7 |
|
| 8 |
- "github.com/fsouza/go-dockerclient" |
|
| 9 |
- |
|
| 10 | 8 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
| 11 | 9 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
| 12 | 10 |
|
| 13 | 11 |
"github.com/openshift/origin/pkg/api/latest" |
| 14 | 12 |
build "github.com/openshift/origin/pkg/build/api" |
| 15 |
- config "github.com/openshift/origin/pkg/config/api" |
|
| 13 |
+ imageapi "github.com/openshift/origin/pkg/image/api" |
|
| 16 | 14 |
) |
| 17 | 15 |
|
| 18 |
-func testImageInfo() *docker.Image {
|
|
| 19 |
- return &docker.Image{
|
|
| 20 |
- Config: &docker.Config{},
|
|
| 16 |
+func testImageInfo() *imageapi.DockerImage {
|
|
| 17 |
+ return &imageapi.DockerImage{
|
|
| 18 |
+ Config: imageapi.DockerConfig{},
|
|
| 21 | 19 |
} |
| 22 | 20 |
} |
| 23 | 21 |
|
| ... | ... |
@@ -75,7 +73,7 @@ func TestSimpleBuildConfig(t *testing.T) {
|
| 75 | 75 |
if err != nil {
|
| 76 | 76 |
t.Fatalf("unexpected error: %v", err)
|
| 77 | 77 |
} |
| 78 |
- if config.Name != "origin" || config.Parameters.Output.ImageTag != "myregistry/openshift/origin" || config.Parameters.Output.Registry != "myregistry" {
|
|
| 78 |
+ if config.Name != "origin" || config.Parameters.Output.To.Name != "origin" {
|
|
| 79 | 79 |
t.Errorf("unexpected name: %#v", config)
|
| 80 | 80 |
} |
| 81 | 81 |
} |
| ... | ... |
@@ -121,11 +119,8 @@ func ExampleGenerateSimpleDockerApp() {
|
| 121 | 121 |
buildConfig, |
| 122 | 122 |
deployConfig, |
| 123 | 123 |
} |
| 124 |
- rawItems := []runtime.RawExtension{}
|
|
| 125 |
- kapi.Scheme.Convert(items, &rawItems) |
|
| 126 |
- |
|
| 127 |
- out := &config.Config{
|
|
| 128 |
- Items: rawItems, |
|
| 124 |
+ out := &kapi.List{
|
|
| 125 |
+ Items: items, |
|
| 129 | 126 |
} |
| 130 | 127 |
|
| 131 | 128 |
data, err := latest.Codec.Encode(out) |
| ... | ... |
@@ -1,13 +1,14 @@ |
| 1 | 1 |
package app |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "github.com/fsouza/go-dockerclient" |
|
| 5 | 4 |
"strings" |
| 5 |
+ |
|
| 6 |
+ imageapi "github.com/openshift/origin/pkg/image/api" |
|
| 6 | 7 |
) |
| 7 | 8 |
|
| 8 | 9 |
var stiEnvironmentNames = []string{"STI_LOCATION", "STI_SCRIPTS_URL", "STI_BUILDER"}
|
| 9 | 10 |
|
| 10 |
-func IsBuilderImage(image *docker.Image) bool {
|
|
| 11 |
+func IsBuilderImage(image *imageapi.DockerImage) bool {
|
|
| 11 | 12 |
for _, env := range image.Config.Env {
|
| 12 | 13 |
for _, name := range stiEnvironmentNames {
|
| 13 | 14 |
if strings.HasPrefix(env, name+"=") {
|
| 14 | 15 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,391 @@ |
| 0 |
+package cmd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "strings" |
|
| 6 |
+ |
|
| 7 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 8 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" |
|
| 9 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 10 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors" |
|
| 11 |
+ "github.com/fsouza/go-dockerclient" |
|
| 12 |
+ "github.com/golang/glog" |
|
| 13 |
+ |
|
| 14 |
+ "github.com/openshift/origin/pkg/client" |
|
| 15 |
+ cmdutil "github.com/openshift/origin/pkg/cmd/util" |
|
| 16 |
+ "github.com/openshift/origin/pkg/dockerregistry" |
|
| 17 |
+ "github.com/openshift/origin/pkg/generate/app" |
|
| 18 |
+ "github.com/openshift/origin/pkg/generate/dockerfile" |
|
| 19 |
+ "github.com/openshift/origin/pkg/generate/source" |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 22 |
+type AppConfig struct {
|
|
| 23 |
+ SourceRepositories util.StringList |
|
| 24 |
+ |
|
| 25 |
+ Components util.StringList |
|
| 26 |
+ ImageStreams util.StringList |
|
| 27 |
+ DockerImages util.StringList |
|
| 28 |
+ Groups util.StringList |
|
| 29 |
+ Environment util.StringList |
|
| 30 |
+ |
|
| 31 |
+ TypeOfBuild string |
|
| 32 |
+ |
|
| 33 |
+ localDockerResolver app.Resolver |
|
| 34 |
+ dockerRegistryResolver app.Resolver |
|
| 35 |
+ imageStreamResolver app.Resolver |
|
| 36 |
+ |
|
| 37 |
+ searcher app.Searcher |
|
| 38 |
+ detector app.Detector |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+type UsageError interface {
|
|
| 42 |
+ UsageError(commandName string) string |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+// TODO: replace with upstream converting [1]error to error |
|
| 46 |
+type errlist interface {
|
|
| 47 |
+ Errors() []error |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func NewAppConfig() *AppConfig {
|
|
| 51 |
+ return &AppConfig{
|
|
| 52 |
+ searcher: &mockSearcher{},
|
|
| 53 |
+ detector: app.SourceRepositoryEnumerator{
|
|
| 54 |
+ Detectors: source.DefaultDetectors, |
|
| 55 |
+ Tester: dockerfile.NewTester(), |
|
| 56 |
+ }, |
|
| 57 |
+ dockerRegistryResolver: app.DockerRegistryResolver{dockerregistry.NewClient()},
|
|
| 58 |
+ } |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func (c *AppConfig) SetDockerClient(dockerclient *docker.Client) {
|
|
| 62 |
+ c.localDockerResolver = app.DockerClientResolver{dockerclient}
|
|
| 63 |
+} |
|
| 64 |
+ |
|
| 65 |
+func (c *AppConfig) SetOpenShiftClient(osclient client.Interface, originNamespace string) {
|
|
| 66 |
+ c.imageStreamResolver = app.ImageStreamResolver{
|
|
| 67 |
+ Client: osclient, |
|
| 68 |
+ Images: osclient, |
|
| 69 |
+ Namespaces: []string{originNamespace, "default"},
|
|
| 70 |
+ } |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+// addArguments converts command line arguments into the appropriate bucket based on what they look like |
|
| 74 |
+func (c *AppConfig) AddArguments(args []string) []string {
|
|
| 75 |
+ unknown := []string{}
|
|
| 76 |
+ for _, s := range args {
|
|
| 77 |
+ switch {
|
|
| 78 |
+ case cmdutil.IsEnvironmentArgument(s): |
|
| 79 |
+ c.Environment = append(c.Environment, s) |
|
| 80 |
+ case app.IsPossibleSourceRepository(s): |
|
| 81 |
+ c.SourceRepositories = append(c.SourceRepositories, s) |
|
| 82 |
+ case app.IsComponentReference(s): |
|
| 83 |
+ c.Components = append(c.Components, s) |
|
| 84 |
+ default: |
|
| 85 |
+ if len(s) == 0 {
|
|
| 86 |
+ break |
|
| 87 |
+ } |
|
| 88 |
+ unknown = append(unknown, s) |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+ return unknown |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+// validate converts all of the arguments on the config into references to objects, or returns an error |
|
| 95 |
+func (c *AppConfig) validate() (app.ComponentReferences, []*app.SourceRepository, cmdutil.Environment, error) {
|
|
| 96 |
+ b := &app.ReferenceBuilder{}
|
|
| 97 |
+ for _, s := range c.SourceRepositories {
|
|
| 98 |
+ b.AddSourceRepository(s) |
|
| 99 |
+ } |
|
| 100 |
+ b.AddImages(c.DockerImages, func(input *app.ComponentInput) app.ComponentReference {
|
|
| 101 |
+ input.Argument = fmt.Sprintf("--docker-image=%q", input.From)
|
|
| 102 |
+ input.Resolver = c.dockerRegistryResolver |
|
| 103 |
+ return input |
|
| 104 |
+ }) |
|
| 105 |
+ b.AddImages(c.ImageStreams, func(input *app.ComponentInput) app.ComponentReference {
|
|
| 106 |
+ input.Argument = fmt.Sprintf("--image=%q", input.From)
|
|
| 107 |
+ input.Resolver = c.imageStreamResolver |
|
| 108 |
+ return input |
|
| 109 |
+ }) |
|
| 110 |
+ b.AddImages(c.Components, func(input *app.ComponentInput) app.ComponentReference {
|
|
| 111 |
+ input.Resolver = app.PerfectMatchWeightedResolver{
|
|
| 112 |
+ app.WeightedResolver{Resolver: c.imageStreamResolver, Weight: 0.0},
|
|
| 113 |
+ app.WeightedResolver{Resolver: c.dockerRegistryResolver, Weight: 0.0},
|
|
| 114 |
+ app.WeightedResolver{Resolver: c.localDockerResolver, Weight: 0.0},
|
|
| 115 |
+ } |
|
| 116 |
+ return input |
|
| 117 |
+ }) |
|
| 118 |
+ b.AddGroups(c.Groups) |
|
| 119 |
+ refs, repos, errs := b.Result() |
|
| 120 |
+ if len(c.TypeOfBuild) != 0 && len(repos) == 0 {
|
|
| 121 |
+ errs = append(errs, fmt.Errorf("when --build is specified you must provide at least one source code location"))
|
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ env, duplicate, envErrs := cmdutil.ParseEnvironmentArguments(c.Environment) |
|
| 125 |
+ for _, s := range duplicate {
|
|
| 126 |
+ glog.V(1).Infof("The environment variable %q was overwritten", s)
|
|
| 127 |
+ } |
|
| 128 |
+ errs = append(errs, envErrs...) |
|
| 129 |
+ |
|
| 130 |
+ return refs, repos, env, errors.NewAggregate(errs) |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+// resolve the references to ensure they are all valid, and identify any images that don't match user input. |
|
| 134 |
+func (c *AppConfig) resolve(components app.ComponentReferences) error {
|
|
| 135 |
+ errs := []error{}
|
|
| 136 |
+ for _, ref := range components {
|
|
| 137 |
+ if err := ref.Resolve(); err != nil {
|
|
| 138 |
+ errs = append(errs, err) |
|
| 139 |
+ continue |
|
| 140 |
+ } |
|
| 141 |
+ switch input := ref.Input(); {
|
|
| 142 |
+ case !input.ExpectToBuild && input.Match.Builder: |
|
| 143 |
+ if c.TypeOfBuild != "docker" {
|
|
| 144 |
+ glog.Infof("Image %q is a builder, so a repository will be expected unless you also specify --build=docker", input)
|
|
| 145 |
+ input.ExpectToBuild = true |
|
| 146 |
+ } |
|
| 147 |
+ case input.ExpectToBuild && !input.Match.Builder: |
|
| 148 |
+ if len(c.TypeOfBuild) == 0 {
|
|
| 149 |
+ errs = append(errs, fmt.Errorf("none of the images that match %q can build source code - check whether this is the image you want to use, then use --build=source to build using source or --build=docker to treat this as a Docker base image and set up a layered Docker build", ref))
|
|
| 150 |
+ continue |
|
| 151 |
+ } |
|
| 152 |
+ } |
|
| 153 |
+ } |
|
| 154 |
+ return errors.NewAggregate(errs) |
|
| 155 |
+} |
|
| 156 |
+ |
|
| 157 |
+// ensureHasSource ensure every builder component has source code associated with it |
|
| 158 |
+func (c *AppConfig) ensureHasSource(components app.ComponentReferences, repositories []*app.SourceRepository) error {
|
|
| 159 |
+ requiresSource := components.NeedsSource() |
|
| 160 |
+ if len(requiresSource) > 0 {
|
|
| 161 |
+ switch {
|
|
| 162 |
+ case len(repositories) > 1: |
|
| 163 |
+ // TODO: harder problem - need to match repos up |
|
| 164 |
+ if len(requiresSource) == 1 {
|
|
| 165 |
+ // TODO: print all suggestions |
|
| 166 |
+ return fmt.Errorf("there are multiple code locations provided - use '%s~<repo>' to declare which code goes with the image", requiresSource[0])
|
|
| 167 |
+ } |
|
| 168 |
+ // TODO: indicate which args don't match, and which repos don't match |
|
| 169 |
+ return fmt.Errorf("there are multiple code locations provided - use '[image]~[repo]' to declare which code goes with which image")
|
|
| 170 |
+ case len(repositories) == 1: |
|
| 171 |
+ glog.Infof("Using %q as the source for build", repositories[0])
|
|
| 172 |
+ for _, component := range requiresSource {
|
|
| 173 |
+ component.Input().Use(repositories[0]) |
|
| 174 |
+ repositories[0].UsedBy(component) |
|
| 175 |
+ } |
|
| 176 |
+ default: |
|
| 177 |
+ if len(requiresSource) == 1 {
|
|
| 178 |
+ return fmt.Errorf("the image %q will build source code, so you must specify a repository via --code", requiresSource[0])
|
|
| 179 |
+ } |
|
| 180 |
+ // TODO: array of pointers won't print correctly |
|
| 181 |
+ return fmt.Errorf("you must provide at least one source code repository with --code for the images: %v", requiresSource)
|
|
| 182 |
+ } |
|
| 183 |
+ } |
|
| 184 |
+ return nil |
|
| 185 |
+} |
|
| 186 |
+ |
|
| 187 |
+// detectSource tries to match each source repository to an image type |
|
| 188 |
+func (c *AppConfig) detectSource(repositories []*app.SourceRepository) error {
|
|
| 189 |
+ errs := []error{}
|
|
| 190 |
+ for _, repo := range repositories {
|
|
| 191 |
+ // if the repository is being used by one of the images, we don't need to detect its type (unless we want to double check) |
|
| 192 |
+ if repo.InUse() {
|
|
| 193 |
+ continue |
|
| 194 |
+ } |
|
| 195 |
+ path, err := repo.LocalPath() |
|
| 196 |
+ if err != nil {
|
|
| 197 |
+ errs = append(errs, err) |
|
| 198 |
+ continue |
|
| 199 |
+ } |
|
| 200 |
+ info, err := c.detector.Detect(path) |
|
| 201 |
+ if err != nil {
|
|
| 202 |
+ errs = append(errs, err) |
|
| 203 |
+ continue |
|
| 204 |
+ } |
|
| 205 |
+ if info.Dockerfile != nil {
|
|
| 206 |
+ // TODO: this should be using the reference builder flow, possibly by moving detectSource up before other steps |
|
| 207 |
+ /*if from, ok := info.Dockerfile.GetDirective("FROM"); ok {
|
|
| 208 |
+ input, _, err := NewComponentInput(from[0]) |
|
| 209 |
+ if err != nil {
|
|
| 210 |
+ errs = append(errs, err) |
|
| 211 |
+ continue |
|
| 212 |
+ } |
|
| 213 |
+ input. |
|
| 214 |
+ }*/ |
|
| 215 |
+ repo.BuildWithDocker() |
|
| 216 |
+ continue |
|
| 217 |
+ } |
|
| 218 |
+ |
|
| 219 |
+ terms := info.Terms() |
|
| 220 |
+ matches, err := c.searcher.Search(terms) |
|
| 221 |
+ if err != nil {
|
|
| 222 |
+ errs = append(errs, err) |
|
| 223 |
+ continue |
|
| 224 |
+ } |
|
| 225 |
+ if len(matches) == 0 {
|
|
| 226 |
+ errs = append(errs, fmt.Errorf("we could not find any images that match the source repo %q (looked for: %v) and this repository does not have a Dockerfile - you'll need to choose a source builder image to continue", repo, terms))
|
|
| 227 |
+ continue |
|
| 228 |
+ } |
|
| 229 |
+ errs = append(errs, fmt.Errorf("found the following possible images to use to build this source repository: %v - to continue, you'll need to specify which image to use with %q", matches, repo))
|
|
| 230 |
+ } |
|
| 231 |
+ return errors.NewAggregate(errs) |
|
| 232 |
+} |
|
| 233 |
+ |
|
| 234 |
+// buildPipelines converts a set of resolved, valid references into pipelines. |
|
| 235 |
+func (c *AppConfig) buildPipelines(components app.ComponentReferences, environment app.Environment) (app.PipelineGroup, error) {
|
|
| 236 |
+ pipelines := app.PipelineGroup{}
|
|
| 237 |
+ for _, group := range components.Group() {
|
|
| 238 |
+ glog.V(2).Infof("found group: %#v", group)
|
|
| 239 |
+ common := app.PipelineGroup{}
|
|
| 240 |
+ for _, ref := range group {
|
|
| 241 |
+ |
|
| 242 |
+ var pipeline *app.Pipeline |
|
| 243 |
+ if ref.Input().ExpectToBuild {
|
|
| 244 |
+ glog.V(2).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses)
|
|
| 245 |
+ input, err := app.InputImageFromMatch(ref.Input().Match) |
|
| 246 |
+ if err != nil {
|
|
| 247 |
+ return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
|
|
| 248 |
+ } |
|
| 249 |
+ strategy, source, err := app.StrategyAndSourceForRepository(ref.Input().Uses) |
|
| 250 |
+ if err != nil {
|
|
| 251 |
+ return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
|
|
| 252 |
+ } |
|
| 253 |
+ if pipeline, err = app.NewBuildPipeline(ref.Input().String(), input, strategy, source); err != nil {
|
|
| 254 |
+ return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
|
|
| 255 |
+ } |
|
| 256 |
+ |
|
| 257 |
+ } else {
|
|
| 258 |
+ glog.V(2).Infof("will include %q", ref)
|
|
| 259 |
+ input, err := app.InputImageFromMatch(ref.Input().Match) |
|
| 260 |
+ if err != nil {
|
|
| 261 |
+ return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
|
|
| 262 |
+ } |
|
| 263 |
+ if pipeline, err = app.NewImagePipeline(ref.Input().String(), input); err != nil {
|
|
| 264 |
+ return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
|
|
| 265 |
+ } |
|
| 266 |
+ } |
|
| 267 |
+ |
|
| 268 |
+ if err := pipeline.NeedsDeployment(environment); err != nil {
|
|
| 269 |
+ return nil, fmt.Errorf("can't set up a deployment for %q: %v", ref.Input(), err)
|
|
| 270 |
+ } |
|
| 271 |
+ common = append(common, pipeline) |
|
| 272 |
+ } |
|
| 273 |
+ |
|
| 274 |
+ if err := common.Reduce(); err != nil {
|
|
| 275 |
+ return nil, fmt.Errorf("can't create a pipeline from %s: %v", common, err)
|
|
| 276 |
+ } |
|
| 277 |
+ pipelines = append(pipelines, common...) |
|
| 278 |
+ } |
|
| 279 |
+ return pipelines, nil |
|
| 280 |
+} |
|
| 281 |
+ |
|
| 282 |
+// Run executes the provided config. |
|
| 283 |
+func (c *AppConfig) Run(out io.Writer, helpFn func() error) error {
|
|
| 284 |
+ components, repositories, environment, err := c.validate() |
|
| 285 |
+ if err != nil {
|
|
| 286 |
+ return err |
|
| 287 |
+ } |
|
| 288 |
+ |
|
| 289 |
+ hasSource := len(repositories) != 0 |
|
| 290 |
+ hasImages := len(components) != 0 |
|
| 291 |
+ if !hasSource && !hasImages {
|
|
| 292 |
+ // display help page |
|
| 293 |
+ // TODO: return usage error, which should trigger help display |
|
| 294 |
+ return helpFn() |
|
| 295 |
+ } |
|
| 296 |
+ |
|
| 297 |
+ if err := c.resolve(components); err != nil {
|
|
| 298 |
+ return err |
|
| 299 |
+ } |
|
| 300 |
+ |
|
| 301 |
+ if err := c.ensureHasSource(components, repositories); err != nil {
|
|
| 302 |
+ return err |
|
| 303 |
+ } |
|
| 304 |
+ |
|
| 305 |
+ glog.V(4).Infof("Code %v", repositories)
|
|
| 306 |
+ glog.V(4).Infof("Images %v", components)
|
|
| 307 |
+ |
|
| 308 |
+ if err := c.detectSource(repositories); err != nil {
|
|
| 309 |
+ return err |
|
| 310 |
+ } |
|
| 311 |
+ |
|
| 312 |
+ pipelines, err := c.buildPipelines(components, app.Environment(environment)) |
|
| 313 |
+ if err != nil {
|
|
| 314 |
+ return err |
|
| 315 |
+ } |
|
| 316 |
+ |
|
| 317 |
+ objects := app.Objects{}
|
|
| 318 |
+ accept := app.NewAcceptFirst() |
|
| 319 |
+ for _, p := range pipelines {
|
|
| 320 |
+ obj, err := p.Objects(accept) |
|
| 321 |
+ if err != nil {
|
|
| 322 |
+ return fmt.Errorf("can't setup %q: %v", p.From, err)
|
|
| 323 |
+ } |
|
| 324 |
+ objects = append(objects, obj...) |
|
| 325 |
+ } |
|
| 326 |
+ |
|
| 327 |
+ objects = app.AddServices(objects) |
|
| 328 |
+ |
|
| 329 |
+ list := &kapi.List{Items: objects}
|
|
| 330 |
+ p, _, err := kubectl.GetPrinter("yaml", "")
|
|
| 331 |
+ if err != nil {
|
|
| 332 |
+ return err |
|
| 333 |
+ } |
|
| 334 |
+ return p.PrintObj(list, out) |
|
| 335 |
+} |
|
| 336 |
+ |
|
| 337 |
+type mockSearcher struct{}
|
|
| 338 |
+ |
|
| 339 |
+func (mockSearcher) Search(terms []string) ([]*app.ComponentMatch, error) {
|
|
| 340 |
+ for _, term := range terms {
|
|
| 341 |
+ term = strings.ToLower(term) |
|
| 342 |
+ switch term {
|
|
| 343 |
+ case "redhat/mysql:5.6": |
|
| 344 |
+ return []*app.ComponentMatch{
|
|
| 345 |
+ {
|
|
| 346 |
+ Value: term, |
|
| 347 |
+ Argument: "redhat/mysql:5.6", |
|
| 348 |
+ Name: "MySQL 5.6", |
|
| 349 |
+ Description: "The Open Source SQL database", |
|
| 350 |
+ }, |
|
| 351 |
+ }, nil |
|
| 352 |
+ case "mysql", "mysql5", "mysql-5", "mysql-5.x": |
|
| 353 |
+ return []*app.ComponentMatch{
|
|
| 354 |
+ {
|
|
| 355 |
+ Value: term, |
|
| 356 |
+ Argument: "redhat/mysql:5.6", |
|
| 357 |
+ Name: "MySQL 5.6", |
|
| 358 |
+ Description: "The Open Source SQL database", |
|
| 359 |
+ }, |
|
| 360 |
+ {
|
|
| 361 |
+ Value: term, |
|
| 362 |
+ Argument: "mysql", |
|
| 363 |
+ Name: "MySQL 5.X", |
|
| 364 |
+ Description: "Something out there on the Docker Hub.", |
|
| 365 |
+ }, |
|
| 366 |
+ }, nil |
|
| 367 |
+ case "php", "php-5", "php5", "redhat/php:5", "redhat/php-5": |
|
| 368 |
+ return []*app.ComponentMatch{
|
|
| 369 |
+ {
|
|
| 370 |
+ Value: term, |
|
| 371 |
+ Argument: "redhat/php:5", |
|
| 372 |
+ Name: "PHP 5.5", |
|
| 373 |
+ Description: "A fast and easy to use scripting language for building websites.", |
|
| 374 |
+ Builder: true, |
|
| 375 |
+ }, |
|
| 376 |
+ }, nil |
|
| 377 |
+ case "ruby": |
|
| 378 |
+ return []*app.ComponentMatch{
|
|
| 379 |
+ {
|
|
| 380 |
+ Value: term, |
|
| 381 |
+ Argument: "redhat/ruby:2", |
|
| 382 |
+ Name: "Ruby 2.0", |
|
| 383 |
+ Description: "A fast and easy to use scripting language for building websites.", |
|
| 384 |
+ Builder: true, |
|
| 385 |
+ }, |
|
| 386 |
+ }, nil |
|
| 387 |
+ } |
|
| 388 |
+ } |
|
| 389 |
+ return []*app.ComponentMatch{}, nil
|
|
| 390 |
+} |
| 0 | 391 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,151 @@ |
| 0 |
+package cmd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestAddArguments(t *testing.T) {
|
|
| 10 |
+ tests := map[string]struct {
|
|
| 11 |
+ args []string |
|
| 12 |
+ env util.StringList |
|
| 13 |
+ repos util.StringList |
|
| 14 |
+ components util.StringList |
|
| 15 |
+ unknown []string |
|
| 16 |
+ }{
|
|
| 17 |
+ "components": {
|
|
| 18 |
+ args: []string{"one", "two+three", "four~five"},
|
|
| 19 |
+ components: util.StringList{"one", "two+three", "four~five"},
|
|
| 20 |
+ unknown: []string{},
|
|
| 21 |
+ }, |
|
| 22 |
+ "source": {
|
|
| 23 |
+ args: []string{".", "./test/one/two/three", "/var/go/src/test", "git://server/repo.git"},
|
|
| 24 |
+ repos: util.StringList{".", "./test/one/two/three", "/var/go/src/test", "git://server/repo.git"},
|
|
| 25 |
+ unknown: []string{},
|
|
| 26 |
+ }, |
|
| 27 |
+ "env": {
|
|
| 28 |
+ args: []string{"first=one", "second=two", "third=three"},
|
|
| 29 |
+ env: util.StringList{"first=one", "second=two", "third=three"},
|
|
| 30 |
+ unknown: []string{},
|
|
| 31 |
+ }, |
|
| 32 |
+ "mix 1": {
|
|
| 33 |
+ args: []string{"git://server/repo.git", "mysql+ruby~git@test.server/repo.git", "env1=test"},
|
|
| 34 |
+ repos: util.StringList{"git://server/repo.git"},
|
|
| 35 |
+ components: util.StringList{"mysql+ruby~git@test.server/repo.git"},
|
|
| 36 |
+ env: util.StringList{"env1=test"},
|
|
| 37 |
+ unknown: []string{},
|
|
| 38 |
+ }, |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ for n, c := range tests {
|
|
| 42 |
+ a := AppConfig{}
|
|
| 43 |
+ unknown := a.AddArguments(c.args) |
|
| 44 |
+ if !reflect.DeepEqual(a.Environment, c.env) {
|
|
| 45 |
+ t.Errorf("%s: Different env variables. Expected: %v, Actual: %v", n, c.env, a.Environment)
|
|
| 46 |
+ } |
|
| 47 |
+ if !reflect.DeepEqual(a.SourceRepositories, c.repos) {
|
|
| 48 |
+ t.Errorf("%s: Different source repos. Expected: %v, Actual: %v", n, c.repos, a.SourceRepositories)
|
|
| 49 |
+ } |
|
| 50 |
+ if !reflect.DeepEqual(a.Components, c.components) {
|
|
| 51 |
+ t.Errorf("%s: Different components. Expected: %v, Actual: %v", n, c.components, a.Components)
|
|
| 52 |
+ } |
|
| 53 |
+ if !reflect.DeepEqual(unknown, c.unknown) {
|
|
| 54 |
+ t.Errorf("%s: Different unknown result. Expected: %v, Actual: %v", n, c.unknown, unknown)
|
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+func TestValidate(t *testing.T) {
|
|
| 61 |
+ tests := map[string]struct {
|
|
| 62 |
+ cfg AppConfig |
|
| 63 |
+ componentValues []string |
|
| 64 |
+ sourceRepoLocations []string |
|
| 65 |
+ env map[string]string |
|
| 66 |
+ }{
|
|
| 67 |
+ "components": {
|
|
| 68 |
+ cfg: AppConfig{
|
|
| 69 |
+ Components: util.StringList{"one", "two", "three/four"},
|
|
| 70 |
+ }, |
|
| 71 |
+ componentValues: []string{"one", "two", "three/four"},
|
|
| 72 |
+ sourceRepoLocations: []string{},
|
|
| 73 |
+ env: map[string]string{},
|
|
| 74 |
+ }, |
|
| 75 |
+ "sourcerepos": {
|
|
| 76 |
+ cfg: AppConfig{
|
|
| 77 |
+ SourceRepositories: []string{".", "/test/var/src", "https://server/repo.git"},
|
|
| 78 |
+ }, |
|
| 79 |
+ componentValues: []string{},
|
|
| 80 |
+ sourceRepoLocations: []string{".", "/test/var/src", "https://server/repo.git"},
|
|
| 81 |
+ env: map[string]string{},
|
|
| 82 |
+ }, |
|
| 83 |
+ "envs": {
|
|
| 84 |
+ cfg: AppConfig{
|
|
| 85 |
+ Environment: util.StringList{"one=first", "two=second", "three=third"},
|
|
| 86 |
+ }, |
|
| 87 |
+ componentValues: []string{},
|
|
| 88 |
+ sourceRepoLocations: []string{},
|
|
| 89 |
+ env: map[string]string{"one": "first", "two": "second", "three": "third"},
|
|
| 90 |
+ }, |
|
| 91 |
+ "component+source": {
|
|
| 92 |
+ cfg: AppConfig{
|
|
| 93 |
+ Components: util.StringList{"one~https://server/repo.git"},
|
|
| 94 |
+ }, |
|
| 95 |
+ componentValues: []string{"one"},
|
|
| 96 |
+ sourceRepoLocations: []string{"https://server/repo.git"},
|
|
| 97 |
+ env: map[string]string{},
|
|
| 98 |
+ }, |
|
| 99 |
+ "components+source": {
|
|
| 100 |
+ cfg: AppConfig{
|
|
| 101 |
+ Components: util.StringList{"mysql+ruby~git://github.com/namespace/repo.git"},
|
|
| 102 |
+ }, |
|
| 103 |
+ componentValues: []string{"mysql", "ruby"},
|
|
| 104 |
+ sourceRepoLocations: []string{"git://github.com/namespace/repo.git"},
|
|
| 105 |
+ env: map[string]string{},
|
|
| 106 |
+ }, |
|
| 107 |
+ "components+env": {
|
|
| 108 |
+ cfg: AppConfig{
|
|
| 109 |
+ Components: util.StringList{"mysql+php"},
|
|
| 110 |
+ Environment: util.StringList{"one=first", "two=second"},
|
|
| 111 |
+ }, |
|
| 112 |
+ componentValues: []string{"mysql", "php"},
|
|
| 113 |
+ sourceRepoLocations: []string{},
|
|
| 114 |
+ env: map[string]string{
|
|
| 115 |
+ "one": "first", |
|
| 116 |
+ "two": "second", |
|
| 117 |
+ }, |
|
| 118 |
+ }, |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ for n, c := range tests {
|
|
| 122 |
+ cr, repos, env, err := c.cfg.validate() |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ t.Errorf("%s: Unexpected error: %v", n, err)
|
|
| 125 |
+ } |
|
| 126 |
+ compValues := []string{}
|
|
| 127 |
+ for _, r := range cr {
|
|
| 128 |
+ compValues = append(compValues, r.Input().Value) |
|
| 129 |
+ } |
|
| 130 |
+ if !reflect.DeepEqual(c.componentValues, compValues) {
|
|
| 131 |
+ t.Errorf("%s: Component values don't match. Expected: %v, Got: %v", n, c.componentValues, compValues)
|
|
| 132 |
+ } |
|
| 133 |
+ repoLocations := []string{}
|
|
| 134 |
+ for _, r := range repos {
|
|
| 135 |
+ repoLocations = append(repoLocations, r.String()) |
|
| 136 |
+ } |
|
| 137 |
+ if !reflect.DeepEqual(c.sourceRepoLocations, repoLocations) {
|
|
| 138 |
+ t.Errorf("%s: Repository locations don't match. Expected: %v, Got: %v", n, c.sourceRepoLocations, repoLocations)
|
|
| 139 |
+ } |
|
| 140 |
+ if len(env) != len(c.env) {
|
|
| 141 |
+ t.Errorf("%s: Environment variables don't match. Expected: %v, Got: %v", n, c.env, env)
|
|
| 142 |
+ } |
|
| 143 |
+ for e, v := range env {
|
|
| 144 |
+ if c.env[e] != v {
|
|
| 145 |
+ t.Errorf("%s: Environment variables don't match. Expected: %v, Got: %v", n, c.env, env)
|
|
| 146 |
+ break |
|
| 147 |
+ } |
|
| 148 |
+ } |
|
| 149 |
+ } |
|
| 150 |
+} |
| 0 | 151 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,321 @@ |
| 0 |
+package app |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "sort" |
|
| 5 |
+ "strings" |
|
| 6 |
+ |
|
| 7 |
+ imageapi "github.com/openshift/origin/pkg/image/api" |
|
| 8 |
+ templateapi "github.com/openshift/origin/pkg/template/api" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// isComponentReference returns true if the provided string appears to be a reference to a source repository |
|
| 12 |
+// on disk, at a URL, a docker image name (which might be on a Docker registry or an OpenShift image stream), |
|
| 13 |
+// or a template. |
|
| 14 |
+func IsComponentReference(s string) bool {
|
|
| 15 |
+ if len(s) == 0 {
|
|
| 16 |
+ return false |
|
| 17 |
+ } |
|
| 18 |
+ all := strings.Split(s, "+") |
|
| 19 |
+ _, _, _, err := componentWithSource(all[0]) |
|
| 20 |
+ return err == nil |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func componentWithSource(s string) (component, repo string, builder bool, err error) {
|
|
| 24 |
+ if strings.Contains(s, "~") {
|
|
| 25 |
+ segs := strings.SplitN(s, "~", 2) |
|
| 26 |
+ if len(segs) == 2 {
|
|
| 27 |
+ builder = true |
|
| 28 |
+ switch {
|
|
| 29 |
+ case len(segs[0]) == 0: |
|
| 30 |
+ err = fmt.Errorf("when using '[image]~[code]' form for %q, you must specify a image name", s)
|
|
| 31 |
+ return |
|
| 32 |
+ case len(segs[1]) == 0: |
|
| 33 |
+ component = segs[0] |
|
| 34 |
+ default: |
|
| 35 |
+ component = segs[0] |
|
| 36 |
+ repo = segs[1] |
|
| 37 |
+ } |
|
| 38 |
+ } |
|
| 39 |
+ } else {
|
|
| 40 |
+ component = s |
|
| 41 |
+ } |
|
| 42 |
+ // TODO: component must be of the form compatible with a pull spec *or* <namespace>/<name> |
|
| 43 |
+ if !imageapi.IsPullSpec(component) {
|
|
| 44 |
+ return "", "", false, fmt.Errorf("%q is not a valid Docker pull specification", component)
|
|
| 45 |
+ } |
|
| 46 |
+ return |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+type ComponentReference interface {
|
|
| 50 |
+ Input() *ComponentInput |
|
| 51 |
+ // Sets Input.Match or returns an error |
|
| 52 |
+ Resolve() error |
|
| 53 |
+ NeedsSource() bool |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 56 |
+type ComponentReferences []ComponentReference |
|
| 57 |
+ |
|
| 58 |
+func (r ComponentReferences) NeedsSource() (refs ComponentReferences) {
|
|
| 59 |
+ for _, ref := range r {
|
|
| 60 |
+ if ref.NeedsSource() {
|
|
| 61 |
+ refs = append(refs, ref) |
|
| 62 |
+ } |
|
| 63 |
+ } |
|
| 64 |
+ return |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+type GroupedComponentReferences ComponentReferences |
|
| 68 |
+ |
|
| 69 |
+func (m GroupedComponentReferences) Len() int { return len(m) }
|
|
| 70 |
+func (m GroupedComponentReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
|
| 71 |
+func (m GroupedComponentReferences) Less(i, j int) bool {
|
|
| 72 |
+ return m[i].Input().Group < m[j].Input().Group |
|
| 73 |
+} |
|
| 74 |
+ |
|
| 75 |
+func (r ComponentReferences) Group() (refs []ComponentReferences) {
|
|
| 76 |
+ sorted := make(GroupedComponentReferences, len(r)) |
|
| 77 |
+ copy(sorted, r) |
|
| 78 |
+ sort.Sort(sorted) |
|
| 79 |
+ group := -1 |
|
| 80 |
+ for _, ref := range sorted {
|
|
| 81 |
+ if ref.Input().Group != group {
|
|
| 82 |
+ refs = append(refs, ComponentReferences{})
|
|
| 83 |
+ } |
|
| 84 |
+ group = ref.Input().Group |
|
| 85 |
+ refs[len(refs)-1] = append(refs[len(refs)-1], ref) |
|
| 86 |
+ } |
|
| 87 |
+ return |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+type ComponentMatch struct {
|
|
| 91 |
+ Value string |
|
| 92 |
+ Argument string |
|
| 93 |
+ Name string |
|
| 94 |
+ Description string |
|
| 95 |
+ Score float32 |
|
| 96 |
+ |
|
| 97 |
+ Builder bool |
|
| 98 |
+ Image *imageapi.DockerImage |
|
| 99 |
+ ImageStream *imageapi.ImageRepository |
|
| 100 |
+ ImageTag string |
|
| 101 |
+ Template *templateapi.Template |
|
| 102 |
+} |
|
| 103 |
+ |
|
| 104 |
+func (m *ComponentMatch) String() string {
|
|
| 105 |
+ return m.Argument |
|
| 106 |
+} |
|
| 107 |
+ |
|
| 108 |
+type Resolver interface {
|
|
| 109 |
+ // resolvers should return ErrMultipleMatches when more than one result could |
|
| 110 |
+ // be construed as a match. Resolvers should set the score to 0.0 if this is a |
|
| 111 |
+ // perfect match, and to higher values the less adequate the match is. |
|
| 112 |
+ Resolve(value string) (*ComponentMatch, error) |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 115 |
+type ScoredComponentMatches []*ComponentMatch |
|
| 116 |
+ |
|
| 117 |
+func (m ScoredComponentMatches) Len() int { return len(m) }
|
|
| 118 |
+func (m ScoredComponentMatches) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
|
| 119 |
+func (m ScoredComponentMatches) Less(i, j int) bool { return m[i].Score < m[j].Score }
|
|
| 120 |
+ |
|
| 121 |
+type WeightedResolver struct {
|
|
| 122 |
+ Resolver |
|
| 123 |
+ Weight float32 |
|
| 124 |
+} |
|
| 125 |
+ |
|
| 126 |
+// PerfectMatchWeightedResolver returns only matches from resolvers that are identified as exact |
|
| 127 |
+// (weight 0.0), and only matches from those resolvers that qualify as exact (score = 0.0). If no |
|
| 128 |
+// perfect matches exist, an ErrMultipleMatches is returned indicating the remaining candidate(s). |
|
| 129 |
+// Note that this metchod may resolve ErrMultipleMatches with a single match, indicating an error |
|
| 130 |
+// (no perfect match) but with only one candidate. |
|
| 131 |
+type PerfectMatchWeightedResolver []WeightedResolver |
|
| 132 |
+ |
|
| 133 |
+func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 134 |
+ match, err := WeightedResolvers(r).Resolve(value) |
|
| 135 |
+ if err != nil {
|
|
| 136 |
+ if multiple, ok := err.(ErrMultipleMatches); ok {
|
|
| 137 |
+ sort.Sort(ScoredComponentMatches(multiple.matches)) |
|
| 138 |
+ if multiple.matches[0].Score == 0.0 && (len(multiple.matches) == 1 || multiple.matches[1].Score != 0.0) {
|
|
| 139 |
+ return multiple.matches[0], nil |
|
| 140 |
+ } |
|
| 141 |
+ } |
|
| 142 |
+ return nil, err |
|
| 143 |
+ } |
|
| 144 |
+ if match.Score != 0.0 {
|
|
| 145 |
+ return nil, ErrMultipleMatches{value, []*ComponentMatch{match}}
|
|
| 146 |
+ } |
|
| 147 |
+ return match, nil |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+type WeightedResolvers []WeightedResolver |
|
| 151 |
+ |
|
| 152 |
+func (r WeightedResolvers) Resolve(value string) (*ComponentMatch, error) {
|
|
| 153 |
+ candidates := []*ComponentMatch{}
|
|
| 154 |
+ errs := []error{}
|
|
| 155 |
+ for _, resolver := range r {
|
|
| 156 |
+ match, err := resolver.Resolve(value) |
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ if multiple, ok := err.(ErrMultipleMatches); ok {
|
|
| 159 |
+ for _, match := range multiple.matches {
|
|
| 160 |
+ if resolver.Weight != 0.0 {
|
|
| 161 |
+ match.Score = match.Score * resolver.Weight |
|
| 162 |
+ } |
|
| 163 |
+ candidates = append(candidates, match) |
|
| 164 |
+ } |
|
| 165 |
+ continue |
|
| 166 |
+ } |
|
| 167 |
+ if _, ok := err.(ErrNoMatch); ok {
|
|
| 168 |
+ continue |
|
| 169 |
+ } |
|
| 170 |
+ errs = append(errs, err) |
|
| 171 |
+ continue |
|
| 172 |
+ } |
|
| 173 |
+ candidates = append(candidates, match) |
|
| 174 |
+ } |
|
| 175 |
+ switch len(candidates) {
|
|
| 176 |
+ case 0: |
|
| 177 |
+ return nil, ErrNoMatch{value: value}
|
|
| 178 |
+ case 1: |
|
| 179 |
+ return candidates[0], nil |
|
| 180 |
+ default: |
|
| 181 |
+ return nil, ErrMultipleMatches{value, candidates}
|
|
| 182 |
+ } |
|
| 183 |
+} |
|
| 184 |
+ |
|
| 185 |
+type ReferenceBuilder struct {
|
|
| 186 |
+ refs ComponentReferences |
|
| 187 |
+ repos []*SourceRepository |
|
| 188 |
+ errs []error |
|
| 189 |
+ group int |
|
| 190 |
+} |
|
| 191 |
+ |
|
| 192 |
+func (r *ReferenceBuilder) AddImages(inputs []string, fn func(*ComponentInput) ComponentReference) {
|
|
| 193 |
+ for _, s := range inputs {
|
|
| 194 |
+ for _, s := range strings.Split(s, "+") {
|
|
| 195 |
+ input, repo, err := NewComponentInput(s) |
|
| 196 |
+ if err != nil {
|
|
| 197 |
+ r.errs = append(r.errs, err) |
|
| 198 |
+ continue |
|
| 199 |
+ } |
|
| 200 |
+ input.Group = r.group |
|
| 201 |
+ ref := fn(input) |
|
| 202 |
+ if len(repo) != 0 {
|
|
| 203 |
+ repository, ok := r.AddSourceRepository(repo) |
|
| 204 |
+ if !ok {
|
|
| 205 |
+ continue |
|
| 206 |
+ } |
|
| 207 |
+ input.Use(repository) |
|
| 208 |
+ repository.UsedBy(ref) |
|
| 209 |
+ } |
|
| 210 |
+ r.refs = append(r.refs, ref) |
|
| 211 |
+ } |
|
| 212 |
+ r.group++ |
|
| 213 |
+ } |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+func (r *ReferenceBuilder) AddGroups(inputs []string) {
|
|
| 217 |
+ for _, s := range inputs {
|
|
| 218 |
+ groups := strings.Split(s, "+") |
|
| 219 |
+ if len(groups) == 1 {
|
|
| 220 |
+ r.errs = append(r.errs, fmt.Errorf("group %q only contains a single name", s))
|
|
| 221 |
+ continue |
|
| 222 |
+ } |
|
| 223 |
+ to := -1 |
|
| 224 |
+ for _, group := range groups {
|
|
| 225 |
+ var match ComponentReference |
|
| 226 |
+ for _, ref := range r.refs {
|
|
| 227 |
+ if group == ref.Input().Value {
|
|
| 228 |
+ match = ref |
|
| 229 |
+ break |
|
| 230 |
+ } |
|
| 231 |
+ } |
|
| 232 |
+ if match == nil {
|
|
| 233 |
+ r.errs = append(r.errs, fmt.Errorf("the name %q from the group definition is not in use, and can't be used", group))
|
|
| 234 |
+ break |
|
| 235 |
+ } |
|
| 236 |
+ if to == -1 {
|
|
| 237 |
+ to = match.Input().Group |
|
| 238 |
+ } else {
|
|
| 239 |
+ match.Input().Group = to |
|
| 240 |
+ } |
|
| 241 |
+ } |
|
| 242 |
+ } |
|
| 243 |
+} |
|
| 244 |
+ |
|
| 245 |
+func (r *ReferenceBuilder) AddSourceRepository(input string) (*SourceRepository, bool) {
|
|
| 246 |
+ for _, existing := range r.repos {
|
|
| 247 |
+ if input == existing.location {
|
|
| 248 |
+ return existing, true |
|
| 249 |
+ } |
|
| 250 |
+ } |
|
| 251 |
+ source, err := NewSourceRepository(input) |
|
| 252 |
+ if err != nil {
|
|
| 253 |
+ r.errs = append(r.errs, err) |
|
| 254 |
+ return nil, false |
|
| 255 |
+ } |
|
| 256 |
+ r.repos = append(r.repos, source) |
|
| 257 |
+ return source, true |
|
| 258 |
+} |
|
| 259 |
+ |
|
| 260 |
+func (r *ReferenceBuilder) Result() (ComponentReferences, []*SourceRepository, []error) {
|
|
| 261 |
+ return r.refs, r.repos, r.errs |
|
| 262 |
+} |
|
| 263 |
+ |
|
| 264 |
+func NewComponentInput(input string) (*ComponentInput, string, error) {
|
|
| 265 |
+ // check for image using [image]~ (to indicate builder) or [image]~[code] (builder plus code) |
|
| 266 |
+ component, repo, builder, err := componentWithSource(input) |
|
| 267 |
+ if err != nil {
|
|
| 268 |
+ return nil, "", err |
|
| 269 |
+ } |
|
| 270 |
+ return &ComponentInput{
|
|
| 271 |
+ From: input, |
|
| 272 |
+ Argument: input, |
|
| 273 |
+ Value: component, |
|
| 274 |
+ ExpectToBuild: builder, |
|
| 275 |
+ }, repo, nil |
|
| 276 |
+} |
|
| 277 |
+ |
|
| 278 |
+type ComponentInput struct {
|
|
| 279 |
+ Group int |
|
| 280 |
+ From string |
|
| 281 |
+ Argument string |
|
| 282 |
+ Value string |
|
| 283 |
+ ExpectToBuild bool |
|
| 284 |
+ |
|
| 285 |
+ Uses *SourceRepository |
|
| 286 |
+ Match *ComponentMatch |
|
| 287 |
+ |
|
| 288 |
+ Resolver |
|
| 289 |
+} |
|
| 290 |
+ |
|
| 291 |
+func (i *ComponentInput) Input() *ComponentInput {
|
|
| 292 |
+ return i |
|
| 293 |
+} |
|
| 294 |
+ |
|
| 295 |
+func (i *ComponentInput) NeedsSource() bool {
|
|
| 296 |
+ return i.ExpectToBuild && i.Uses == nil |
|
| 297 |
+} |
|
| 298 |
+ |
|
| 299 |
+func (i *ComponentInput) Resolve() error {
|
|
| 300 |
+ if i.Resolver == nil {
|
|
| 301 |
+ return ErrNoMatch{value: i.Value, qualifier: "no resolver defined"}
|
|
| 302 |
+ } |
|
| 303 |
+ match, err := i.Resolver.Resolve(i.Value) |
|
| 304 |
+ if err != nil {
|
|
| 305 |
+ return err |
|
| 306 |
+ } |
|
| 307 |
+ i.Value = match.Value |
|
| 308 |
+ i.Argument = match.Argument |
|
| 309 |
+ i.Match = match |
|
| 310 |
+ |
|
| 311 |
+ return nil |
|
| 312 |
+} |
|
| 313 |
+ |
|
| 314 |
+func (i *ComponentInput) String() string {
|
|
| 315 |
+ return i.Value |
|
| 316 |
+} |
|
| 317 |
+ |
|
| 318 |
+func (i *ComponentInput) Use(repo *SourceRepository) {
|
|
| 319 |
+ i.Uses = repo |
|
| 320 |
+} |
| 0 | 321 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,49 @@ |
| 0 |
+package app |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+type ErrNoMatch struct {
|
|
| 8 |
+ value string |
|
| 9 |
+ qualifier string |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+func (e ErrNoMatch) Error() string {
|
|
| 13 |
+ if len(e.qualifier) != 0 {
|
|
| 14 |
+ return fmt.Sprintf("no image matched %q: %s", e.value, e.qualifier)
|
|
| 15 |
+ } |
|
| 16 |
+ return fmt.Sprintf("no image matched %q", e.value)
|
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+func (e ErrNoMatch) UsageError(commandName string) string {
|
|
| 20 |
+ return fmt.Sprintf(` |
|
| 21 |
+%[3]s - you can try to search for images or templates that may match this name with: |
|
| 22 |
+ |
|
| 23 |
+ $ %[2]s -S %[1]q |
|
| 24 |
+ |
|
| 25 |
+`, e.value, commandName, e.Error()) |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+type ErrMultipleMatches struct {
|
|
| 29 |
+ image string |
|
| 30 |
+ matches []*ComponentMatch |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func (e ErrMultipleMatches) Error() string {
|
|
| 34 |
+ return fmt.Sprintf("multiple images matched %q: %d", e.image, len(e.matches))
|
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func (e ErrMultipleMatches) UsageError(commandName string) string {
|
|
| 38 |
+ buf := &bytes.Buffer{}
|
|
| 39 |
+ for _, match := range e.matches {
|
|
| 40 |
+ fmt.Fprintf(buf, "* %[1]s (use %[2]s)\n", match.Name, match.Argument) |
|
| 41 |
+ fmt.Fprintf(buf, " %s\n\n", match.Description) |
|
| 42 |
+ } |
|
| 43 |
+ return fmt.Sprintf(` |
|
| 44 |
+The argument %[1]q could apply to the following images or templates: |
|
| 45 |
+ |
|
| 46 |
+%[2]s |
|
| 47 |
+`, e.image, buf.String()) |
|
| 48 |
+} |
| 0 | 49 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,170 @@ |
| 0 |
+package app |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/fsouza/go-dockerclient" |
|
| 6 |
+ |
|
| 7 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 8 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
|
| 9 |
+ "github.com/golang/glog" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/openshift/origin/pkg/client" |
|
| 12 |
+ "github.com/openshift/origin/pkg/dockerregistry" |
|
| 13 |
+ imageapi "github.com/openshift/origin/pkg/image/api" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+type DockerClientResolver struct {
|
|
| 17 |
+ Client *docker.Client |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func (r DockerClientResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 21 |
+ image, err := r.Client.InspectImage(value) |
|
| 22 |
+ switch {
|
|
| 23 |
+ case err == docker.ErrNoSuchImage: |
|
| 24 |
+ return nil, ErrNoMatch{value: value}
|
|
| 25 |
+ case err != nil: |
|
| 26 |
+ return nil, err |
|
| 27 |
+ } |
|
| 28 |
+ return &ComponentMatch{
|
|
| 29 |
+ Value: value, |
|
| 30 |
+ Argument: fmt.Sprintf("--docker-image=%q", value),
|
|
| 31 |
+ Name: value, |
|
| 32 |
+ Description: fmt.Sprintf("Docker image %q by %s\n%s", value, image.Author, image.Comment),
|
|
| 33 |
+ Builder: false, |
|
| 34 |
+ Score: 0, |
|
| 35 |
+ }, nil |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+type DockerRegistryResolver struct {
|
|
| 39 |
+ Client dockerregistry.Client |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+func (r DockerRegistryResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 43 |
+ registry, namespace, name, tag, err := imageapi.SplitDockerPullSpec(value) |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ return nil, err |
|
| 46 |
+ } |
|
| 47 |
+ connection, err := r.Client.Connect(registry) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ if dockerregistry.IsRegistryNotFound(err) {
|
|
| 50 |
+ return nil, ErrNoMatch{value: value}
|
|
| 51 |
+ } |
|
| 52 |
+ return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
|
|
| 53 |
+ } |
|
| 54 |
+ image, err := connection.ImageByTag(namespace, name, tag) |
|
| 55 |
+ if err != nil {
|
|
| 56 |
+ if dockerregistry.IsNotFound(err) {
|
|
| 57 |
+ return nil, ErrNoMatch{value: value, qualifier: err.Error()}
|
|
| 58 |
+ } |
|
| 59 |
+ return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
|
|
| 60 |
+ } |
|
| 61 |
+ if len(tag) == 0 {
|
|
| 62 |
+ tag = "latest" |
|
| 63 |
+ } |
|
| 64 |
+ glog.V(4).Infof("found image: %#v", image)
|
|
| 65 |
+ dockerImage := &imageapi.DockerImage{}
|
|
| 66 |
+ if err = kapi.Scheme.Convert(image, dockerImage); err != nil {
|
|
| 67 |
+ return nil, err |
|
| 68 |
+ } |
|
| 69 |
+ return &ComponentMatch{
|
|
| 70 |
+ Value: value, |
|
| 71 |
+ Argument: fmt.Sprintf("--docker-image=%q", value),
|
|
| 72 |
+ Name: value, |
|
| 73 |
+ Description: fmt.Sprintf("Docker image %q (%q)", value, image.ID),
|
|
| 74 |
+ Builder: IsBuilderImage(dockerImage), |
|
| 75 |
+ Score: 0, |
|
| 76 |
+ Image: dockerImage, |
|
| 77 |
+ ImageTag: tag, |
|
| 78 |
+ }, nil |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+type ImageStreamResolver struct {
|
|
| 82 |
+ Client client.ImageRepositoriesNamespacer |
|
| 83 |
+ Images client.ImagesNamespacer |
|
| 84 |
+ Namespaces []string |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func (r ImageStreamResolver) Resolve(value string) (*ComponentMatch, error) {
|
|
| 88 |
+ registry, namespace, name, tag, err := imageapi.SplitOpenShiftPullSpec(value) |
|
| 89 |
+ if err != nil || len(registry) != 0 {
|
|
| 90 |
+ return nil, fmt.Errorf("image repositories must be of the form [<namespace>/]<name>[:<tag>]")
|
|
| 91 |
+ } |
|
| 92 |
+ namespaces := r.Namespaces |
|
| 93 |
+ if len(namespace) != 0 {
|
|
| 94 |
+ namespaces = []string{namespace}
|
|
| 95 |
+ } |
|
| 96 |
+ for _, namespace := range namespaces {
|
|
| 97 |
+ glog.V(4).Infof("checking image stream %s/%s with tag %q", namespace, name, tag)
|
|
| 98 |
+ repo, err := r.Client.ImageRepositories(namespace).Get(name) |
|
| 99 |
+ if err != nil {
|
|
| 100 |
+ if errors.IsNotFound(err) {
|
|
| 101 |
+ continue |
|
| 102 |
+ } |
|
| 103 |
+ return nil, err |
|
| 104 |
+ } |
|
| 105 |
+ searchTag := tag |
|
| 106 |
+ // TODO: move to a lookup function on repo, or better yet, have the repo.Status.Tags field automatically infer latest |
|
| 107 |
+ if len(searchTag) == 0 {
|
|
| 108 |
+ searchTag = "latest" |
|
| 109 |
+ } |
|
| 110 |
+ id, ok := repo.Tags[searchTag] |
|
| 111 |
+ if !ok {
|
|
| 112 |
+ if len(tag) == 0 {
|
|
| 113 |
+ return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("the default tag %q has not been set", searchTag)}
|
|
| 114 |
+ } |
|
| 115 |
+ return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q has not been set", tag)}
|
|
| 116 |
+ } |
|
| 117 |
+ imageData, err := r.Images.Images(namespace).Get(id) |
|
| 118 |
+ if err != nil {
|
|
| 119 |
+ if errors.IsNotFound(err) {
|
|
| 120 |
+ return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q is set, but image %q has been removed", tag, id)}
|
|
| 121 |
+ } |
|
| 122 |
+ return nil, err |
|
| 123 |
+ } |
|
| 124 |
+ |
|
| 125 |
+ spec := imageapi.JoinDockerPullSpec("", namespace, name, tag)
|
|
| 126 |
+ return &ComponentMatch{
|
|
| 127 |
+ Value: spec, |
|
| 128 |
+ Argument: fmt.Sprintf("--image=%q", spec),
|
|
| 129 |
+ Name: name, |
|
| 130 |
+ Description: fmt.Sprintf("Image stream %s (tag %q) in namespace %s, tracks %q", name, searchTag, namespace, repo.Status.DockerImageRepository),
|
|
| 131 |
+ Builder: IsBuilderImage(&imageData.DockerImageMetadata), |
|
| 132 |
+ Score: 0, |
|
| 133 |
+ |
|
| 134 |
+ ImageStream: repo, |
|
| 135 |
+ Image: &imageData.DockerImageMetadata, |
|
| 136 |
+ ImageTag: searchTag, |
|
| 137 |
+ }, nil |
|
| 138 |
+ } |
|
| 139 |
+ return nil, ErrNoMatch{value: value}
|
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+type Searcher interface {
|
|
| 143 |
+ Search(terms []string) ([]*ComponentMatch, error) |
|
| 144 |
+} |
|
| 145 |
+ |
|
| 146 |
+func InputImageFromMatch(match *ComponentMatch) (*ImageRef, error) {
|
|
| 147 |
+ switch {
|
|
| 148 |
+ case match.ImageStream != nil: |
|
| 149 |
+ input, err := ImageFromRepository(match.ImageStream, match.ImageTag) |
|
| 150 |
+ if err != nil {
|
|
| 151 |
+ return nil, err |
|
| 152 |
+ } |
|
| 153 |
+ input.AsImageRepository = true |
|
| 154 |
+ input.Info = match.Image |
|
| 155 |
+ return input, nil |
|
| 156 |
+ |
|
| 157 |
+ case match.Image != nil: |
|
| 158 |
+ input, err := ImageFromName(match.Value, match.ImageTag) |
|
| 159 |
+ if err != nil {
|
|
| 160 |
+ return nil, err |
|
| 161 |
+ } |
|
| 162 |
+ input.AsImageRepository = false |
|
| 163 |
+ input.Info = match.Image |
|
| 164 |
+ return input, nil |
|
| 165 |
+ |
|
| 166 |
+ default: |
|
| 167 |
+ return nil, fmt.Errorf("no image or image stream, can't setup a build")
|
|
| 168 |
+ } |
|
| 169 |
+} |
| ... | ... |
@@ -3,10 +3,12 @@ package app |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"math/rand" |
| 6 |
+ "regexp" |
|
| 6 | 7 |
"strings" |
| 7 | 8 |
|
| 8 | 9 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
| 9 | 10 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
| 11 |
+ kutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 10 | 12 |
|
| 11 | 13 |
deploy "github.com/openshift/origin/pkg/deploy/api" |
| 12 | 14 |
) |
| ... | ... |
@@ -44,9 +46,11 @@ func NewBuildPipeline(from string, input *ImageRef, strategy *BuildStrategyRef, |
| 44 | 44 |
Tag: "latest", |
| 45 | 45 |
|
| 46 | 46 |
AsImageRepository: true, |
| 47 |
+ } |
|
| 48 |
+ if input != nil {
|
|
| 47 | 49 |
// TODO: assumes that build doesn't change the image metadata. In the future |
| 48 | 50 |
// we could get away with deferred generation possibly. |
| 49 |
- Info: input.Info, |
|
| 51 |
+ output.Info = input.Info |
|
| 50 | 52 |
} |
| 51 | 53 |
|
| 52 | 54 |
build := &BuildRef{
|
| ... | ... |
@@ -136,25 +140,44 @@ func (g PipelineGroup) String() string {
|
| 136 | 136 |
return strings.Join(s, "+") |
| 137 | 137 |
} |
| 138 | 138 |
|
| 139 |
+const MaxServiceNameLen = 24 |
|
| 140 |
+ |
|
| 141 |
+var InvalidServiceChars = regexp.MustCompile("[^-a-z0-9]")
|
|
| 142 |
+ |
|
| 143 |
+func makeValidServiceName(name string) string {
|
|
| 144 |
+ name = strings.ToLower(name) |
|
| 145 |
+ name = InvalidServiceChars.ReplaceAllString(name, "") |
|
| 146 |
+ if len(name) == 0 {
|
|
| 147 |
+ return fmt.Sprintf("svc-%d", rand.Intn(100000))
|
|
| 148 |
+ } |
|
| 149 |
+ if len(name) > MaxServiceNameLen-5 {
|
|
| 150 |
+ name = name[:MaxServiceNameLen-5] |
|
| 151 |
+ } |
|
| 152 |
+ name = fmt.Sprintf("%s-%d", name, rand.Intn(9999))
|
|
| 153 |
+ if strings.HasPrefix(name, "-") {
|
|
| 154 |
+ name = "0" + name[1:] |
|
| 155 |
+ } |
|
| 156 |
+ return name |
|
| 157 |
+} |
|
| 158 |
+ |
|
| 139 | 159 |
func AddServices(objects Objects) Objects {
|
| 140 | 160 |
svcs := []runtime.Object{}
|
| 141 | 161 |
for _, o := range objects {
|
| 142 | 162 |
switch t := o.(type) {
|
| 143 | 163 |
case *deploy.DeploymentConfig: |
| 144 |
- // TODO: expose all ports, or try to find the one that matches a given protocol |
|
| 145 | 164 |
for _, container := range t.Template.ControllerTemplate.Template.Spec.Containers {
|
| 146 | 165 |
for _, port := range container.Ports {
|
| 147 | 166 |
p := port.ContainerPort |
| 148 | 167 |
svcs = append(svcs, &kapi.Service{
|
| 149 | 168 |
ObjectMeta: kapi.ObjectMeta{
|
| 150 |
- Name: t.Name, |
|
| 169 |
+ Name: makeValidServiceName(t.Name), |
|
| 151 | 170 |
}, |
| 152 | 171 |
Spec: kapi.ServiceSpec{
|
| 153 |
- Port: p, |
|
| 154 |
- Selector: t.Template.ControllerTemplate.Selector, |
|
| 172 |
+ ContainerPort: kutil.NewIntOrStringFromInt(p), |
|
| 173 |
+ Port: p, |
|
| 174 |
+ Selector: t.Template.ControllerTemplate.Selector, |
|
| 155 | 175 |
}, |
| 156 | 176 |
}) |
| 157 |
- break |
|
| 158 | 177 |
} |
| 159 | 178 |
break |
| 160 | 179 |
} |
| 161 | 180 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,184 @@ |
| 0 |
+package app |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "regexp" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/openshift/origin/pkg/generate/dockerfile" |
|
| 11 |
+ "github.com/openshift/origin/pkg/generate/source" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+var ( |
|
| 15 |
+ argumentGit = regexp.MustCompile("^(http://|https://|git@|git://).*\\.git(?:#([a-zA-Z0-9]*))?$")
|
|
| 16 |
+ argumentGitProtocol = regexp.MustCompile("^git@")
|
|
| 17 |
+ argumentPath = regexp.MustCompile("^\\.|^\\/[^/]+")
|
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+func IsPossibleSourceRepository(s string) bool {
|
|
| 21 |
+ return argumentGit.MatchString(s) || argumentGitProtocol.MatchString(s) || argumentPath.MatchString(s) |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+// SourceRepository represents an code repository that may be the target of a build. |
|
| 25 |
+type SourceRepository struct {
|
|
| 26 |
+ location string |
|
| 27 |
+ url url.URL |
|
| 28 |
+ |
|
| 29 |
+ usedBy []ComponentReference |
|
| 30 |
+ buildWithDocker bool |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+// NewSourceRepository creates a reference to a local or remote source code repository from |
|
| 34 |
+// a URL or path. |
|
| 35 |
+func NewSourceRepository(s string) (*SourceRepository, error) {
|
|
| 36 |
+ var location *url.URL |
|
| 37 |
+ switch {
|
|
| 38 |
+ case strings.HasPrefix(s, "git@"): |
|
| 39 |
+ base := "git://" + strings.TrimPrefix(s, "git@") |
|
| 40 |
+ url, err := url.Parse(base) |
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ return nil, err |
|
| 43 |
+ } |
|
| 44 |
+ location = url |
|
| 45 |
+ |
|
| 46 |
+ default: |
|
| 47 |
+ uri, err := url.Parse(s) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return nil, err |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ if uri.Scheme == "" {
|
|
| 53 |
+ path := s |
|
| 54 |
+ ref := "" |
|
| 55 |
+ segments := strings.SplitN(path, "#", 2) |
|
| 56 |
+ if len(segments) == 2 {
|
|
| 57 |
+ path, ref = segments[0], segments[1] |
|
| 58 |
+ } |
|
| 59 |
+ path, err := filepath.Abs(path) |
|
| 60 |
+ if err != nil {
|
|
| 61 |
+ return nil, err |
|
| 62 |
+ } |
|
| 63 |
+ uri = &url.URL{
|
|
| 64 |
+ Scheme: "file", |
|
| 65 |
+ Path: path, |
|
| 66 |
+ Fragment: ref, |
|
| 67 |
+ } |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ location = uri |
|
| 71 |
+ } |
|
| 72 |
+ return &SourceRepository{
|
|
| 73 |
+ location: s, |
|
| 74 |
+ url: *location, |
|
| 75 |
+ }, nil |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+func (r *SourceRepository) UsedBy(ref ComponentReference) {
|
|
| 79 |
+ r.usedBy = append(r.usedBy, ref) |
|
| 80 |
+} |
|
| 81 |
+ |
|
| 82 |
+func (r *SourceRepository) Remote() bool {
|
|
| 83 |
+ return r.url.Scheme != "file" |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func (r *SourceRepository) InUse() bool {
|
|
| 87 |
+ return len(r.usedBy) > 0 |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+func (r *SourceRepository) BuildWithDocker() {
|
|
| 91 |
+ r.buildWithDocker = true |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+func (r *SourceRepository) IsDockerBuild() bool {
|
|
| 95 |
+ return r.buildWithDocker |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+func (r *SourceRepository) String() string {
|
|
| 99 |
+ return r.location |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func (r *SourceRepository) LocalPath() (string, error) {
|
|
| 103 |
+ switch {
|
|
| 104 |
+ case r.url.Scheme == "file": |
|
| 105 |
+ return r.url.Path, nil |
|
| 106 |
+ // TODO: implement other types |
|
| 107 |
+ // TODO: lazy cache (predictably?) |
|
| 108 |
+ default: |
|
| 109 |
+ |
|
| 110 |
+ return "", fmt.Errorf("reading local repositories is not implemented: %q", r.location)
|
|
| 111 |
+ } |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+type SourceRepositoryInfo struct {
|
|
| 115 |
+ Path string |
|
| 116 |
+ Types []SourceLanguageType |
|
| 117 |
+ Dockerfile dockerfile.Dockerfile |
|
| 118 |
+} |
|
| 119 |
+ |
|
| 120 |
+func (info *SourceRepositoryInfo) Terms() []string {
|
|
| 121 |
+ terms := []string{}
|
|
| 122 |
+ for i := range info.Types {
|
|
| 123 |
+ terms = append(terms, info.Types[i].Platform) |
|
| 124 |
+ } |
|
| 125 |
+ return terms |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+type SourceLanguageType struct {
|
|
| 129 |
+ Platform string |
|
| 130 |
+ Version string |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+type Detector interface {
|
|
| 134 |
+ Detect(dir string) (*SourceRepositoryInfo, error) |
|
| 135 |
+} |
|
| 136 |
+ |
|
| 137 |
+type SourceRepositoryEnumerator struct {
|
|
| 138 |
+ Detectors source.Detectors |
|
| 139 |
+ Tester dockerfile.Tester |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+func (e SourceRepositoryEnumerator) Detect(dir string) (*SourceRepositoryInfo, error) {
|
|
| 143 |
+ info := &SourceRepositoryInfo{
|
|
| 144 |
+ Path: dir, |
|
| 145 |
+ } |
|
| 146 |
+ for _, d := range e.Detectors {
|
|
| 147 |
+ if detected, ok := d(dir); ok {
|
|
| 148 |
+ info.Types = append(info.Types, SourceLanguageType{
|
|
| 149 |
+ Platform: detected.Platform, |
|
| 150 |
+ Version: detected.Version, |
|
| 151 |
+ }) |
|
| 152 |
+ } |
|
| 153 |
+ } |
|
| 154 |
+ if path, ok, err := e.Tester.Has(dir); err == nil && ok {
|
|
| 155 |
+ file, err := os.Open(path) |
|
| 156 |
+ if err != nil {
|
|
| 157 |
+ return nil, err |
|
| 158 |
+ } |
|
| 159 |
+ defer file.Close() |
|
| 160 |
+ dockerfile, err := dockerfile.NewParser().Parse(file) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ return nil, err |
|
| 163 |
+ } |
|
| 164 |
+ info.Dockerfile = dockerfile |
|
| 165 |
+ } |
|
| 166 |
+ return info, nil |
|
| 167 |
+} |
|
| 168 |
+ |
|
| 169 |
+func StrategyAndSourceForRepository(repo *SourceRepository) (*BuildStrategyRef, *SourceRef, error) {
|
|
| 170 |
+ // TODO: replace with repository origin lookup, then in the future replace with auto push repository to server |
|
| 171 |
+ if !repo.Remote() {
|
|
| 172 |
+ return nil, nil, fmt.Errorf("the repository %q can't be used, as the CLI does not yet support pushing a local repository from your filesystem to OpenShift", repo)
|
|
| 173 |
+ } |
|
| 174 |
+ strategy := &BuildStrategyRef{
|
|
| 175 |
+ IsDockerBuild: repo.IsDockerBuild(), |
|
| 176 |
+ DockerContext: "", |
|
| 177 |
+ } |
|
| 178 |
+ source := &SourceRef{
|
|
| 179 |
+ URL: &repo.url, |
|
| 180 |
+ Ref: repo.url.Fragment, |
|
| 181 |
+ } |
|
| 182 |
+ return strategy, source, nil |
|
| 183 |
+} |
| ... | ... |
@@ -46,6 +46,8 @@ func (_ *parser) Parse(input io.Reader) (Dockerfile, error) {
|
| 46 | 46 |
return d, nil |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
+// GetDirective returns a list of lines that begin with the given directive |
|
| 50 |
+// and a flag that is true if the directive was found in the Dockerfile |
|
| 49 | 51 |
func (d dockerfile) GetDirective(s string) ([]string, bool) {
|
| 50 | 52 |
values := []string{}
|
| 51 | 53 |
s = strings.ToLower(s) |
| ... | ... |
@@ -20,19 +20,19 @@ const ( |
| 20 | 20 |
func (e GenerationError) Error() string {
|
| 21 | 21 |
switch e {
|
| 22 | 22 |
case NoGit: |
| 23 |
- return "Git was not detected in your system. It is needed for build config generation." |
|
| 23 |
+ return "git was not detected in your system. It is needed for build config generation." |
|
| 24 | 24 |
case SourceDirAndURL: |
| 25 |
- return "A source directory and a source URL were specified. Please only specify one." |
|
| 25 |
+ return "a source directory and a source URL were specified. Please only specify one." |
|
| 26 | 26 |
case InvalidSourceDir: |
| 27 |
- return "The source directory is not readable or is invalid." |
|
| 27 |
+ return "the source directory is not readable or is invalid." |
|
| 28 | 28 |
case CouldNotDetect: |
| 29 |
- return "Could not detect a build type from the source." |
|
| 29 |
+ return "could not detect a build type from the source." |
|
| 30 | 30 |
case NoBuilderFound: |
| 31 |
- return "Could not find a builder to match the STI source repository." |
|
| 31 |
+ return "could not find a builder to match the STI source repository." |
|
| 32 | 32 |
case InvalidDockerfile: |
| 33 |
- return "Invalid Dockerfile. Does not contain a FROM clause." |
|
| 33 |
+ return "invalid Dockerfile. Does not contain a FROM clause." |
|
| 34 | 34 |
case ImageNotFound: |
| 35 |
- return "Image data could not be found." |
|
| 35 |
+ return "image data could not be found." |
|
| 36 | 36 |
} |
| 37 | 37 |
return "" |
| 38 | 38 |
} |
| ... | ... |
@@ -47,13 +47,13 @@ func NewMultipleDockerfilesErr(paths []string) error {
|
| 47 | 47 |
type multipleDockerFilesError []string |
| 48 | 48 |
|
| 49 | 49 |
func (e multipleDockerFilesError) Error() string {
|
| 50 |
- result := "Error: Multiple Dockerfile(s) found.\nSpecify one of the following flags:\n" |
|
| 50 |
+ result := "multiple Dockerfile(s) found.\nSpecify one of the following flags:\n" |
|
| 51 | 51 |
for _, f := range e {
|
| 52 | 52 |
dir := filepath.Dir(f) |
| 53 | 53 |
if dir == "" {
|
| 54 | 54 |
dir = "." |
| 55 | 55 |
} |
| 56 |
- result += "--context=\"" + dir + "\"" |
|
| 56 |
+ result += "--docker-context=\"" + dir + "\"" |
|
| 57 | 57 |
result += "\n" |
| 58 | 58 |
} |
| 59 | 59 |
return result |
| 60 | 60 |
deleted file mode 100644 |
| ... | ... |
@@ -1,44 +0,0 @@ |
| 1 |
-package generator |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/openshift/origin/pkg/generate/app" |
|
| 5 |
- "github.com/openshift/origin/pkg/generate/imageinfo" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-// Generators for ImageInfo |
|
| 9 |
-// - ImageRef -> ImageInfo |
|
| 10 |
- |
|
| 11 |
-// NewImageInfoGenerator creates a new ImageInfoGenerator |
|
| 12 |
-func NewImageInfoGenerator(retriever imageinfo.Retriever) *ImageInfoGenerator {
|
|
| 13 |
- return &ImageInfoGenerator{
|
|
| 14 |
- retriever: retriever, |
|
| 15 |
- } |
|
| 16 |
-} |
|
| 17 |
- |
|
| 18 |
-// ImageInfoGenerator generates ImageInfo objects from ImageRef |
|
| 19 |
-type ImageInfoGenerator struct {
|
|
| 20 |
- retriever imageinfo.Retriever |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-// FromImageRef generates an ImageInfo from an ImageRef |
|
| 24 |
-func (g *ImageInfoGenerator) FromImageRef(imageRef app.ImageRef) *app.ImageRef {
|
|
| 25 |
- info, err := g.retriever.Retrieve(imageRef.NameReference()) |
|
| 26 |
- if err != nil {
|
|
| 27 |
- // If image info could not be retrieved, return a simple image info |
|
| 28 |
- // without the additional image metadata |
|
| 29 |
- return &imageRef |
|
| 30 |
- } |
|
| 31 |
- imageRef.Info = info |
|
| 32 |
- return &imageRef |
|
| 33 |
-} |
|
| 34 |
- |
|
| 35 |
-// FromImageRefs generates an array of ImageInfo from an array of ImageRef |
|
| 36 |
-func (g *ImageInfoGenerator) FromImageRefs(imageRefs []app.ImageRef) []*app.ImageRef {
|
|
| 37 |
- result := []*app.ImageRef{}
|
|
| 38 |
- for _, ir := range imageRefs {
|
|
| 39 |
- info := g.FromImageRef(ir) |
|
| 40 |
- result = append(result, info) |
|
| 41 |
- } |
|
| 42 |
- return result |
|
| 43 |
- |
|
| 44 |
-} |
| 45 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,50 +0,0 @@ |
| 1 |
-package generator |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
- |
|
| 6 |
- "github.com/fsouza/go-dockerclient" |
|
| 7 |
- |
|
| 8 |
- "github.com/openshift/origin/pkg/generate/app" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-func TestFromImageRef(t *testing.T) {
|
|
| 12 |
- img := &docker.Image{
|
|
| 13 |
- ID: "test/image", |
|
| 14 |
- } |
|
| 15 |
- g := NewImageInfoGenerator(&fakeRetriever{img})
|
|
| 16 |
- |
|
| 17 |
- imageRef := app.ImageRef{Namespace: "test", Name: "image"}
|
|
| 18 |
- imageInfo := g.FromImageRef(imageRef) |
|
| 19 |
- if imageInfo.Info != img {
|
|
| 20 |
- t.Errorf("Unexpected image info returned.")
|
|
| 21 |
- } |
|
| 22 |
-} |
|
| 23 |
- |
|
| 24 |
-func TestFromImageRefs(t *testing.T) {
|
|
| 25 |
- img := &docker.Image{
|
|
| 26 |
- ID: "test/image", |
|
| 27 |
- } |
|
| 28 |
- g := NewImageInfoGenerator(&fakeRetriever{img})
|
|
| 29 |
- |
|
| 30 |
- imageRefs := []app.ImageRef{
|
|
| 31 |
- {Namespace: "test", Name: "image1"},
|
|
| 32 |
- {Namespace: "test", Name: "image2"},
|
|
| 33 |
- {Namespace: "test", Name: "image3"},
|
|
| 34 |
- } |
|
| 35 |
- imageInfos := g.FromImageRefs(imageRefs) |
|
| 36 |
- if len(imageInfos) != 3 {
|
|
| 37 |
- t.Errorf("Unexpected number of imagerefs returned")
|
|
| 38 |
- } |
|
| 39 |
- if imageInfos[0].Info != img {
|
|
| 40 |
- t.Errorf("Unexpected image info returned.")
|
|
| 41 |
- } |
|
| 42 |
-} |
|
| 43 |
- |
|
| 44 |
-type fakeRetriever struct {
|
|
| 45 |
- image *docker.Image |
|
| 46 |
-} |
|
| 47 |
- |
|
| 48 |
-func (r *fakeRetriever) Retrieve(name string) (*docker.Image, error) {
|
|
| 49 |
- return r.image, nil |
|
| 50 |
-} |
| ... | ... |
@@ -2,9 +2,13 @@ package generator |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "strings" |
|
| 5 | 8 |
|
| 6 | 9 |
"github.com/openshift/origin/pkg/generate/app" |
| 7 |
- image "github.com/openshift/origin/pkg/image/api" |
|
| 10 |
+ "github.com/openshift/origin/pkg/generate/dockerfile" |
|
| 11 |
+ imageapi "github.com/openshift/origin/pkg/image/api" |
|
| 8 | 12 |
) |
| 9 | 13 |
|
| 10 | 14 |
// Generators for ImageRef |
| ... | ... |
@@ -14,38 +18,89 @@ import ( |
| 14 | 14 |
// ImageRefGenerator generates ImageRefs |
| 15 | 15 |
type ImageRefGenerator interface {
|
| 16 | 16 |
FromName(name string) (*app.ImageRef, error) |
| 17 |
- FromRepository(repo *image.ImageRepository, tag string) (*app.ImageRef, error) |
|
| 17 |
+ FromNameAndPorts(name string, ports []string) (*app.ImageRef, error) |
|
| 18 |
+ FromRepository(repo *imageapi.ImageRepository, tag string) (*app.ImageRef, error) |
|
| 19 |
+ FromDockerfile(name string, dir string, context string) (*app.ImageRef, error) |
|
| 18 | 20 |
} |
| 19 | 21 |
|
| 20 |
-type imageRefGenerator struct{}
|
|
| 22 |
+type imageRefGenerator struct {
|
|
| 23 |
+ dockerParser dockerfile.Parser |
|
| 24 |
+} |
|
| 21 | 25 |
|
| 22 | 26 |
// NewImageRefGenerator creates a new ImageRefGenerator |
| 23 | 27 |
func NewImageRefGenerator() ImageRefGenerator {
|
| 24 |
- return &imageRefGenerator{}
|
|
| 28 |
+ return &imageRefGenerator{
|
|
| 29 |
+ dockerParser: dockerfile.NewParser(), |
|
| 30 |
+ } |
|
| 25 | 31 |
} |
| 26 | 32 |
|
| 27 | 33 |
// FromName generates an ImageRef from a given name |
| 28 | 34 |
func (g *imageRefGenerator) FromName(name string) (*app.ImageRef, error) {
|
| 29 |
- registry, namespace, name, tag, err := image.SplitDockerPullSpec(name) |
|
| 35 |
+ registry, namespace, name, tag, err := imageapi.SplitDockerPullSpec(name) |
|
| 30 | 36 |
if err != nil {
|
| 31 | 37 |
return nil, err |
| 32 | 38 |
} |
| 33 | 39 |
return &app.ImageRef{
|
| 34 |
- Registry: registry, |
|
| 35 |
- Namespace: namespace, |
|
| 36 |
- Name: name, |
|
| 37 |
- Tag: tag, |
|
| 40 |
+ Registry: registry, |
|
| 41 |
+ Namespace: namespace, |
|
| 42 |
+ Name: name, |
|
| 43 |
+ Tag: tag, |
|
| 44 |
+ AsImageRepository: true, |
|
| 38 | 45 |
}, nil |
| 39 | 46 |
} |
| 40 | 47 |
|
| 48 |
+func (g *imageRefGenerator) FromNameAndPorts(name string, ports []string) (*app.ImageRef, error) {
|
|
| 49 |
+ present := struct{}{}
|
|
| 50 |
+ imageRef, err := g.FromName(name) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return nil, err |
|
| 53 |
+ } |
|
| 54 |
+ exposedPorts := map[string]struct{}{}
|
|
| 55 |
+ |
|
| 56 |
+ for _, p := range ports {
|
|
| 57 |
+ exposedPorts[p] = present |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ imageRef.Info = &imageapi.DockerImage{
|
|
| 61 |
+ Config: imageapi.DockerConfig{
|
|
| 62 |
+ ExposedPorts: exposedPorts, |
|
| 63 |
+ }, |
|
| 64 |
+ } |
|
| 65 |
+ return imageRef, nil |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+func (g *imageRefGenerator) FromDockerfile(name string, dir string, context string) (*app.ImageRef, error) {
|
|
| 69 |
+ // Look for Dockerfile in repository |
|
| 70 |
+ file, err := os.Open(filepath.Join(dir, context, "Dockerfile")) |
|
| 71 |
+ if err != nil {
|
|
| 72 |
+ return nil, err |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ dockerFile, err := g.dockerParser.Parse(file) |
|
| 76 |
+ if err != nil {
|
|
| 77 |
+ return nil, err |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ expose, ok := dockerFile.GetDirective("EXPOSE")
|
|
| 81 |
+ if !ok {
|
|
| 82 |
+ return nil, err |
|
| 83 |
+ } |
|
| 84 |
+ ports := []string{}
|
|
| 85 |
+ for _, e := range expose {
|
|
| 86 |
+ ps := strings.Split(e, " ") |
|
| 87 |
+ ports = append(ports, ps...) |
|
| 88 |
+ } |
|
| 89 |
+ return g.FromNameAndPorts(name, ports) |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 41 | 92 |
// FromRepository generates an ImageRef from an OpenShift ImageRepository |
| 42 |
-func (g *imageRefGenerator) FromRepository(repo *image.ImageRepository, tag string) (*app.ImageRef, error) {
|
|
| 93 |
+func (g *imageRefGenerator) FromRepository(repo *imageapi.ImageRepository, tag string) (*app.ImageRef, error) {
|
|
| 43 | 94 |
pullSpec := repo.Status.DockerImageRepository |
| 44 | 95 |
if len(pullSpec) == 0 {
|
| 45 | 96 |
// need to know the default OpenShift registry |
| 46 | 97 |
return nil, fmt.Errorf("the repository does not resolve to a pullable Docker repository")
|
| 47 | 98 |
} |
| 48 |
- registry, namespace, name, repoTag, err := image.SplitDockerPullSpec(pullSpec) |
|
| 99 |
+ registry, namespace, name, repoTag, err := imageapi.SplitDockerPullSpec(pullSpec) |
|
| 49 | 100 |
if err != nil {
|
| 50 | 101 |
return nil, err |
| 51 | 102 |
} |
| 3 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,73 +0,0 @@ |
| 1 |
-package imageinfo |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 5 |
- "github.com/fsouza/go-dockerclient" |
|
| 6 |
- |
|
| 7 |
- "github.com/openshift/origin/pkg/generate/errors" |
|
| 8 |
- image "github.com/openshift/origin/pkg/image/api" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-type Retriever interface {
|
|
| 12 |
- Retrieve(name string) (*docker.Image, error) |
|
| 13 |
-} |
|
| 14 |
- |
|
| 15 |
-type retriever struct {
|
|
| 16 |
- streamLister imageStreamLister |
|
| 17 |
- imageGetter imageGetter |
|
| 18 |
- dockerClient dockerClient |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-type dockerClient interface {
|
|
| 22 |
- InspectImage(name string) (*docker.Image, error) |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-type imageStreamLister interface {
|
|
| 26 |
- List(label, field labels.Selector) (*image.ImageRepositoryList, error) |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-type imageGetter interface {
|
|
| 30 |
- Get(name string) (*image.Image, error) |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-func NewRetriever(streamLister imageStreamLister, imageGetter imageGetter, dockerClient dockerClient) Retriever {
|
|
| 34 |
- return &retriever{
|
|
| 35 |
- streamLister: streamLister, |
|
| 36 |
- imageGetter: imageGetter, |
|
| 37 |
- dockerClient: dockerClient, |
|
| 38 |
- } |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-func (r *retriever) Retrieve(name string) (*docker.Image, error) {
|
|
| 42 |
- // TODO: implement a way to discover images on the server via REST client |
|
| 43 |
- // Try finding the image on the openshift server first |
|
| 44 |
- if r.streamLister != nil {
|
|
| 45 |
- streamList, err := r.streamLister.List(labels.Everything(), labels.Everything()) |
|
| 46 |
- if err == nil {
|
|
| 47 |
- for _, imageStream := range streamList.Items {
|
|
| 48 |
- _, ns, nm, _, err := image.SplitDockerPullSpec(imageStream.DockerImageRepository) |
|
| 49 |
- if err != nil {
|
|
| 50 |
- continue |
|
| 51 |
- } |
|
| 52 |
- if name == ns+"/"+nm {
|
|
| 53 |
- for _, imageName := range imageStream.Tags {
|
|
| 54 |
- img, err := r.imageGetter.Get(imageName) |
|
| 55 |
- if err != nil {
|
|
| 56 |
- continue |
|
| 57 |
- } |
|
| 58 |
- return &img.DockerImageMetadata, nil |
|
| 59 |
- } |
|
| 60 |
- } |
|
| 61 |
- } |
|
| 62 |
- } |
|
| 63 |
- } |
|
| 64 |
- |
|
| 65 |
- // TODO: Use the Docker registry API to retrieve image information if possible |
|
| 66 |
- |
|
| 67 |
- // If that doesn't work try Docker if present |
|
| 68 |
- if r.dockerClient != nil {
|
|
| 69 |
- return r.dockerClient.InspectImage(name) |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- return nil, errors.ImageNotFound |
|
| 73 |
-} |
| 74 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,81 +0,0 @@ |
| 1 |
-package imageinfo |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "reflect" |
|
| 6 |
- "testing" |
|
| 7 |
- |
|
| 8 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 9 |
- "github.com/fsouza/go-dockerclient" |
|
| 10 |
- |
|
| 11 |
- image "github.com/openshift/origin/pkg/image/api" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-func TestRetrieveFromOpenShift(t *testing.T) {
|
|
| 15 |
- imageMetadata := docker.Image{
|
|
| 16 |
- ID: "image2", |
|
| 17 |
- Size: 2048, |
|
| 18 |
- } |
|
| 19 |
- client := fakeOSClient{
|
|
| 20 |
- repositories: []image.ImageRepository{
|
|
| 21 |
- {
|
|
| 22 |
- DockerImageRepository: "test/repository1", |
|
| 23 |
- }, |
|
| 24 |
- {
|
|
| 25 |
- DockerImageRepository: "test/repository2", |
|
| 26 |
- Tags: map[string]string{
|
|
| 27 |
- "a_test_image_tag": "image1", |
|
| 28 |
- "a_second_tag": "image2", |
|
| 29 |
- }, |
|
| 30 |
- }, |
|
| 31 |
- }, |
|
| 32 |
- images: map[string]image.Image{
|
|
| 33 |
- "image2": {
|
|
| 34 |
- DockerImageMetadata: imageMetadata, |
|
| 35 |
- }, |
|
| 36 |
- }, |
|
| 37 |
- } |
|
| 38 |
- r := NewRetriever(&client, &client, &fakeDockerClient{})
|
|
| 39 |
- result, err := r.Retrieve("test/repository2")
|
|
| 40 |
- if err != nil {
|
|
| 41 |
- t.Errorf("Unexpected error: %v", err)
|
|
| 42 |
- } |
|
| 43 |
- if !reflect.DeepEqual(*result, imageMetadata) {
|
|
| 44 |
- t.Errorf("Unexpected result: %#v", result)
|
|
| 45 |
- } |
|
| 46 |
-} |
|
| 47 |
- |
|
| 48 |
-func TestRetrieveFromLocalDocker(t *testing.T) {
|
|
| 49 |
- |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-type fakeOSClient struct {
|
|
| 53 |
- repositories []image.ImageRepository |
|
| 54 |
- images map[string]image.Image |
|
| 55 |
-} |
|
| 56 |
- |
|
| 57 |
-func (c *fakeOSClient) List(label, field labels.Selector) (*image.ImageRepositoryList, error) {
|
|
| 58 |
- return &image.ImageRepositoryList{
|
|
| 59 |
- Items: c.repositories, |
|
| 60 |
- }, nil |
|
| 61 |
-} |
|
| 62 |
- |
|
| 63 |
-func (c *fakeOSClient) Get(name string) (*image.Image, error) {
|
|
| 64 |
- img, ok := c.images[name] |
|
| 65 |
- if ok {
|
|
| 66 |
- return &img, nil |
|
| 67 |
- } |
|
| 68 |
- return nil, fmt.Errorf("Not found")
|
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-type fakeDockerClient struct {
|
|
| 72 |
- images map[string]docker.Image |
|
| 73 |
-} |
|
| 74 |
- |
|
| 75 |
-func (d *fakeDockerClient) InspectImage(name string) (*docker.Image, error) {
|
|
| 76 |
- img, ok := d.images[name] |
|
| 77 |
- if ok {
|
|
| 78 |
- return &img, nil |
|
| 79 |
- } |
|
| 80 |
- return nil, fmt.Errorf("Not found")
|
|
| 81 |
-} |
| ... | ... |
@@ -5,19 +5,21 @@ import ( |
| 5 | 5 |
"path/filepath" |
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 |
+// Info is detected platform information from a source directory |
|
| 8 | 9 |
type Info struct {
|
| 9 | 10 |
Platform string |
| 10 | 11 |
Version string |
| 11 | 12 |
} |
| 12 | 13 |
|
| 13 |
-type DetectSource interface {
|
|
| 14 |
- DetectSource(dir string) (*Info, bool) |
|
| 15 |
-} |
|
| 16 |
- |
|
| 14 |
+// DetectorFunc is a function that returns source Info from a given directory. |
|
| 15 |
+// It returns true if it was able to detect the code in the given directory. |
|
| 17 | 16 |
type DetectorFunc func(dir string) (*Info, bool) |
| 18 | 17 |
|
| 18 |
+// Detectors is a set of DetectorFunc that is used to detect the |
|
| 19 |
+// language/platform for a given source directory |
|
| 19 | 20 |
type Detectors []DetectorFunc |
| 20 | 21 |
|
| 22 |
+// DefafultDetectors is a default set of Detector functions |
|
| 21 | 23 |
var DefaultDetectors = Detectors{
|
| 22 | 24 |
DetectRuby, |
| 23 | 25 |
DetectJava, |
| ... | ... |
@@ -28,6 +30,8 @@ type sourceDetector struct {
|
| 28 | 28 |
detectors []DetectorFunc |
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 |
+// DetectSource returns source information from a given directory using |
|
| 32 |
+// a set of Detectors |
|
| 31 | 33 |
func (s Detectors) DetectSource(dir string) (*Info, bool) {
|
| 32 | 34 |
for _, d := range s {
|
| 33 | 35 |
if info, found := d(dir); found {
|
| ... | ... |
@@ -37,8 +41,9 @@ func (s Detectors) DetectSource(dir string) (*Info, bool) {
|
| 37 | 37 |
return nil, false |
| 38 | 38 |
} |
| 39 | 39 |
|
| 40 |
+// DetectRuby detects whether the source code in the given repository is Ruby |
|
| 40 | 41 |
func DetectRuby(dir string) (*Info, bool) {
|
| 41 |
- if filesPresent(dir, []string{"Gemfile", "Rakefile"}) {
|
|
| 42 |
+ if filesPresent(dir, []string{"Gemfile", "Rakefile", "config.ru"}) {
|
|
| 42 | 43 |
return &Info{
|
| 43 | 44 |
Platform: "Ruby", |
| 44 | 45 |
}, true |
| ... | ... |
@@ -46,6 +51,7 @@ func DetectRuby(dir string) (*Info, bool) {
|
| 46 | 46 |
return nil, false |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
+// DetectJava detects whether the source code in the given repository is Java |
|
| 49 | 50 |
func DetectJava(dir string) (*Info, bool) {
|
| 50 | 51 |
if filesPresent(dir, []string{"pom.xml"}) {
|
| 51 | 52 |
return &Info{
|
| ... | ... |
@@ -55,8 +61,9 @@ func DetectJava(dir string) (*Info, bool) {
|
| 55 | 55 |
return nil, false |
| 56 | 56 |
} |
| 57 | 57 |
|
| 58 |
+// DetectNodeJS detects whether the source code in the given repository is NodeJS |
|
| 58 | 59 |
func DetectNodeJS(dir string) (*Info, bool) {
|
| 59 |
- if filesPresent(dir, []string{"config.json"}) {
|
|
| 60 |
+ if filesPresent(dir, []string{"config.json", "package.json"}) {
|
|
| 60 | 61 |
return &Info{
|
| 61 | 62 |
Platform: "NodeJS", |
| 62 | 63 |
}, true |
| ... | ... |
@@ -1,9 +1,46 @@ |
| 1 | 1 |
package source |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "strings" |
|
| 4 | 5 |
"testing" |
| 5 | 6 |
) |
| 6 | 7 |
|
| 7 | 8 |
func TestDetectSource(t *testing.T) {
|
| 9 |
+ d := Detectors{fake1, fake2}
|
|
| 10 |
+ i, ok := d.DetectSource("test_fake1")
|
|
| 11 |
+ if !ok {
|
|
| 12 |
+ t.Errorf("Unable to detect source for test_fake1")
|
|
| 13 |
+ } |
|
| 14 |
+ if i.Platform != "fake1" {
|
|
| 15 |
+ t.Errorf("Invalid platform for test_fake1")
|
|
| 16 |
+ } |
|
| 17 |
+ if i, ok = d.DetectSource("test_fake3"); ok {
|
|
| 18 |
+ t.Errorf("Detected source for invalid dir: test_fake3")
|
|
| 19 |
+ } |
|
| 20 |
+ i, ok = d.DetectSource("test_fake2")
|
|
| 21 |
+ if !ok {
|
|
| 22 |
+ t.Errorf("Unable to detect source for test_fake2")
|
|
| 23 |
+ } |
|
| 24 |
+ if i.Platform != "fake2" {
|
|
| 25 |
+ t.Errorf("Invalid platform for test_fake2")
|
|
| 26 |
+ } |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func fake1(dir string) (*Info, bool) {
|
|
| 30 |
+ if strings.Contains(dir, "fake1") {
|
|
| 31 |
+ return &Info{
|
|
| 32 |
+ Platform: "fake1", |
|
| 33 |
+ }, true |
|
| 34 |
+ } |
|
| 35 |
+ return nil, false |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+func fake2(dir string) (*Info, bool) {
|
|
| 39 |
+ if strings.Contains(dir, "fake2") {
|
|
| 40 |
+ return &Info{
|
|
| 41 |
+ Platform: "fake2", |
|
| 42 |
+ }, true |
|
| 43 |
+ } |
|
| 44 |
+ return nil, false |
|
| 8 | 45 |
|
| 9 | 46 |
} |
| 10 | 47 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+package source |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "regexp" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+var ( |
|
| 7 |
+ argumentGit = regexp.MustCompile("^(http://|https://|git@|git://).*\\.git(?:#([a-zA-Z0-9]*))?$")
|
|
| 8 |
+ argumentGitProtocol = regexp.MustCompile("^git@")
|
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func IsRemoteRepository(s string) bool {
|
|
| 12 |
+ return argumentGit.MatchString(s) || argumentGitProtocol.MatchString(s) |
|
| 13 |
+} |