|
|
@@ -98,25 +98,62 @@ func apply(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []str
|
|
|
worker.RegisterDriver("porter.deploy", NewPorterDriver)
|
|
|
worker.SetDefaultDriver("porter.deploy")
|
|
|
|
|
|
- deplNamespace := os.Getenv("PORTER_NAMESPACE")
|
|
|
+ if hasDeploymentHookEnvVars() {
|
|
|
+ deplNamespace := os.Getenv("PORTER_NAMESPACE")
|
|
|
|
|
|
- if deplNamespace == "" {
|
|
|
- return fmt.Errorf("namespace must be set by PORTER_NAMESPACE")
|
|
|
- }
|
|
|
+ if deplNamespace == "" {
|
|
|
+ return fmt.Errorf("namespace must be set by PORTER_NAMESPACE")
|
|
|
+ }
|
|
|
|
|
|
- deploymentHook, err := NewDeploymentHook(client, resGroup, deplNamespace)
|
|
|
+ deploymentHook, err := NewDeploymentHook(client, resGroup, deplNamespace)
|
|
|
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ worker.RegisterHook("deployment", deploymentHook)
|
|
|
}
|
|
|
|
|
|
- worker.RegisterHook("deployment", deploymentHook)
|
|
|
+ cloneEnvGroupHook := NewCloneEnvGroupHook(client, resGroup)
|
|
|
+ worker.RegisterHook("cloneenvgroup", cloneEnvGroupHook)
|
|
|
|
|
|
return worker.Apply(resGroup, &switchboardTypes.ApplyOpts{
|
|
|
BasePath: basePath,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+func hasDeploymentHookEnvVars() bool {
|
|
|
+ if ghIDStr := os.Getenv("PORTER_GIT_INSTALLATION_ID"); ghIDStr == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if prIDStr := os.Getenv("PORTER_PULL_REQUEST_ID"); prIDStr == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if branchName := os.Getenv("PORTER_BRANCH_NAME"); branchName == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if actionIDStr := os.Getenv("PORTER_ACTION_ID"); actionIDStr == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if repoName := os.Getenv("PORTER_REPO_NAME"); repoName == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if repoOwner := os.Getenv("PORTER_REPO_OWNER"); repoOwner == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if prName := os.Getenv("PORTER_PR_NAME"); prName == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
type Source struct {
|
|
|
Name string
|
|
|
Repo string
|
|
|
@@ -135,6 +172,8 @@ type ApplicationConfig struct {
|
|
|
WaitForJob bool
|
|
|
|
|
|
Build struct {
|
|
|
+ ForceBuild bool
|
|
|
+ ForcePush bool
|
|
|
Method string
|
|
|
Context string
|
|
|
Dockerfile string
|
|
|
@@ -143,6 +182,8 @@ type ApplicationConfig struct {
|
|
|
Buildpacks []string
|
|
|
}
|
|
|
|
|
|
+ EnvGroups []types.EnvGroupMeta
|
|
|
+
|
|
|
Values map[string]interface{}
|
|
|
}
|
|
|
|
|
|
@@ -161,18 +202,24 @@ func NewPorterDriver(resource *models.Resource, opts *drivers.SharedDriverOpts)
|
|
|
output: make(map[string]interface{}),
|
|
|
}
|
|
|
|
|
|
- err := driver.getSource(resource.Source)
|
|
|
+ source := &Source{}
|
|
|
|
|
|
+ err := getSource(resource.Source, source)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
- err = driver.getTarget(resource.Target)
|
|
|
+ driver.source = source
|
|
|
+
|
|
|
+ target := &Target{}
|
|
|
|
|
|
+ err = getTarget(resource.Target, target)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
+ driver.target = target
|
|
|
+
|
|
|
return driver, nil
|
|
|
}
|
|
|
|
|
|
@@ -310,6 +357,7 @@ func (d *Driver) applyApplication(resource *models.Resource, client *api.Client,
|
|
|
LocalDockerfile: appConfig.Build.Dockerfile,
|
|
|
OverrideTag: tag,
|
|
|
Method: deploy.DeployBuildType(method),
|
|
|
+ EnvGroups: appConfig.EnvGroups,
|
|
|
}
|
|
|
|
|
|
if shouldCreate {
|
|
|
@@ -405,7 +453,7 @@ func (d *Driver) createApplication(resource *models.Resource, client *api.Client
|
|
|
if appConf.Build.Method == "registry" {
|
|
|
subdomain, err = createAgent.CreateFromRegistry(appConf.Build.Image, appConf.Values)
|
|
|
} else {
|
|
|
- subdomain, err = createAgent.CreateFromDocker(appConf.Values, sharedOpts.OverrideTag, buildConfig)
|
|
|
+ subdomain, err = createAgent.CreateFromDocker(appConf.Values, sharedOpts.OverrideTag, buildConfig, appConf.Build.ForceBuild)
|
|
|
}
|
|
|
|
|
|
if err != nil {
|
|
|
@@ -453,13 +501,13 @@ func (d *Driver) updateApplication(resource *models.Resource, client *api.Client
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- err = updateAgent.Build(buildConfig)
|
|
|
+ err = updateAgent.Build(buildConfig, appConf.Build.ForceBuild)
|
|
|
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
- err = updateAgent.Push()
|
|
|
+ err = updateAgent.Push(appConf.Build.ForcePush)
|
|
|
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
@@ -497,94 +545,90 @@ func (d *Driver) Output() (map[string]interface{}, error) {
|
|
|
return d.output, nil
|
|
|
}
|
|
|
|
|
|
-func (d *Driver) getSource(genericSource map[string]interface{}) error {
|
|
|
- d.source = &Source{}
|
|
|
-
|
|
|
+func getSource(input map[string]interface{}, output *Source) error {
|
|
|
// first read from env vars
|
|
|
- d.source.Name = os.Getenv("PORTER_SOURCE_NAME")
|
|
|
- d.source.Repo = os.Getenv("PORTER_SOURCE_REPO")
|
|
|
- d.source.Version = os.Getenv("PORTER_SOURCE_VERSION")
|
|
|
+ output.Name = os.Getenv("PORTER_SOURCE_NAME")
|
|
|
+ output.Repo = os.Getenv("PORTER_SOURCE_REPO")
|
|
|
+ output.Version = os.Getenv("PORTER_SOURCE_VERSION")
|
|
|
|
|
|
// next, check for values in the YAML file
|
|
|
- if d.source.Name == "" {
|
|
|
- if name, ok := genericSource["name"]; ok {
|
|
|
+ if output.Name == "" {
|
|
|
+ if name, ok := input["name"]; ok {
|
|
|
nameVal, ok := name.(string)
|
|
|
if !ok {
|
|
|
return fmt.Errorf("invalid name provided")
|
|
|
}
|
|
|
- d.source.Name = nameVal
|
|
|
+ output.Name = nameVal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if d.source.Name == "" {
|
|
|
+ if output.Name == "" {
|
|
|
return fmt.Errorf("source name required")
|
|
|
}
|
|
|
|
|
|
- if d.source.Repo == "" {
|
|
|
- if repo, ok := genericSource["repo"]; ok {
|
|
|
+ if output.Repo == "" {
|
|
|
+ if repo, ok := input["repo"]; ok {
|
|
|
repoVal, ok := repo.(string)
|
|
|
if !ok {
|
|
|
return fmt.Errorf("invalid repo provided")
|
|
|
}
|
|
|
- d.source.Repo = repoVal
|
|
|
+ output.Repo = repoVal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if d.source.Version == "" {
|
|
|
- if version, ok := genericSource["version"]; ok {
|
|
|
+ if output.Version == "" {
|
|
|
+ if version, ok := input["version"]; ok {
|
|
|
versionVal, ok := version.(string)
|
|
|
if !ok {
|
|
|
return fmt.Errorf("invalid version provided")
|
|
|
}
|
|
|
- d.source.Version = versionVal
|
|
|
+ output.Version = versionVal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// lastly, just put in the defaults
|
|
|
- if d.source.Version == "" {
|
|
|
- d.source.Version = "latest"
|
|
|
+ if output.Version == "" {
|
|
|
+ output.Version = "latest"
|
|
|
}
|
|
|
|
|
|
- d.source.IsApplication = d.source.Repo == "https://charts.getporter.dev"
|
|
|
+ output.IsApplication = output.Repo == "https://charts.getporter.dev"
|
|
|
|
|
|
- if d.source.Repo == "" {
|
|
|
- d.source.Repo = "https://charts.getporter.dev"
|
|
|
+ if output.Repo == "" {
|
|
|
+ output.Repo = "https://charts.getporter.dev"
|
|
|
|
|
|
- values, err := existsInRepo(d.source.Name, d.source.Version, d.source.Repo)
|
|
|
+ values, err := existsInRepo(output.Name, output.Version, output.Repo)
|
|
|
|
|
|
if err == nil {
|
|
|
// found in "https://charts.getporter.dev"
|
|
|
- d.source.SourceValues = values
|
|
|
- d.source.IsApplication = true
|
|
|
+ output.SourceValues = values
|
|
|
+ output.IsApplication = true
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- d.source.Repo = "https://chart-addons.getporter.dev"
|
|
|
+ output.Repo = "https://chart-addons.getporter.dev"
|
|
|
|
|
|
- values, err = existsInRepo(d.source.Name, d.source.Version, d.source.Repo)
|
|
|
+ values, err = existsInRepo(output.Name, output.Version, output.Repo)
|
|
|
|
|
|
if err == nil {
|
|
|
// found in https://chart-addons.getporter.dev
|
|
|
- d.source.SourceValues = values
|
|
|
+ output.SourceValues = values
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
return fmt.Errorf("source does not exist in any repo")
|
|
|
}
|
|
|
|
|
|
- return fmt.Errorf("source '%s' does not exist in repo '%s'", d.source.Name, d.source.Repo)
|
|
|
+ return fmt.Errorf("source '%s' does not exist in repo '%s'", output.Name, output.Repo)
|
|
|
}
|
|
|
|
|
|
-func (d *Driver) getTarget(genericTarget map[string]interface{}) error {
|
|
|
- d.target = &Target{}
|
|
|
-
|
|
|
+func getTarget(input map[string]interface{}, output *Target) error {
|
|
|
// first read from env vars
|
|
|
if projectEnv := os.Getenv("PORTER_PROJECT"); projectEnv != "" {
|
|
|
project, err := strconv.Atoi(projectEnv)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- d.target.Project = uint(project)
|
|
|
+ output.Project = uint(project)
|
|
|
}
|
|
|
|
|
|
if clusterEnv := os.Getenv("PORTER_CLUSTER"); clusterEnv != "" {
|
|
|
@@ -592,51 +636,51 @@ func (d *Driver) getTarget(genericTarget map[string]interface{}) error {
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- d.target.Cluster = uint(cluster)
|
|
|
+ output.Cluster = uint(cluster)
|
|
|
}
|
|
|
|
|
|
- d.target.Namespace = os.Getenv("PORTER_NAMESPACE")
|
|
|
+ output.Namespace = os.Getenv("PORTER_NAMESPACE")
|
|
|
|
|
|
// next, check for values in the YAML file
|
|
|
- if d.target.Project == 0 {
|
|
|
- if project, ok := genericTarget["project"]; ok {
|
|
|
+ if output.Project == 0 {
|
|
|
+ if project, ok := input["project"]; ok {
|
|
|
projectVal, ok := project.(uint)
|
|
|
if !ok {
|
|
|
return fmt.Errorf("project value must be an integer")
|
|
|
}
|
|
|
- d.target.Project = projectVal
|
|
|
+ output.Project = projectVal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if d.target.Cluster == 0 {
|
|
|
- if cluster, ok := genericTarget["cluster"]; ok {
|
|
|
+ if output.Cluster == 0 {
|
|
|
+ if cluster, ok := input["cluster"]; ok {
|
|
|
clusterVal, ok := cluster.(uint)
|
|
|
if !ok {
|
|
|
return fmt.Errorf("cluster value must be an integer")
|
|
|
}
|
|
|
- d.target.Cluster = clusterVal
|
|
|
+ output.Cluster = clusterVal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if d.target.Namespace == "" {
|
|
|
- if namespace, ok := genericTarget["namespace"]; ok {
|
|
|
+ if output.Namespace == "" {
|
|
|
+ if namespace, ok := input["namespace"]; ok {
|
|
|
namespaceVal, ok := namespace.(string)
|
|
|
if !ok {
|
|
|
return fmt.Errorf("invalid namespace provided")
|
|
|
}
|
|
|
- d.target.Namespace = namespaceVal
|
|
|
+ output.Namespace = namespaceVal
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// lastly, just put in the defaults
|
|
|
- if d.target.Project == 0 {
|
|
|
- d.target.Project = config.Project
|
|
|
+ if output.Project == 0 {
|
|
|
+ output.Project = config.Project
|
|
|
}
|
|
|
- if d.target.Cluster == 0 {
|
|
|
- d.target.Cluster = config.Cluster
|
|
|
+ if output.Cluster == 0 {
|
|
|
+ output.Cluster = config.Cluster
|
|
|
}
|
|
|
- if d.target.Namespace == "" {
|
|
|
- d.target.Namespace = "default"
|
|
|
+ if output.Namespace == "" {
|
|
|
+ output.Namespace = "default"
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
@@ -699,30 +743,24 @@ func NewDeploymentHook(client *api.Client, resourceGroup *switchboardTypes.Resou
|
|
|
namespace: namespace,
|
|
|
}
|
|
|
|
|
|
- if ghIDStr := os.Getenv("PORTER_GIT_INSTALLATION_ID"); ghIDStr != "" {
|
|
|
- ghID, err := strconv.Atoi(ghIDStr)
|
|
|
+ ghIDStr := os.Getenv("PORTER_GIT_INSTALLATION_ID")
|
|
|
+ ghID, err := strconv.Atoi(ghIDStr)
|
|
|
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- res.gitInstallationID = uint(ghID)
|
|
|
- } else if ghIDStr == "" {
|
|
|
- return nil, fmt.Errorf("Git installation ID must be defined, set by PORTER_GIT_INSTALLATION_ID")
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- if prIDStr := os.Getenv("PORTER_PULL_REQUEST_ID"); prIDStr != "" {
|
|
|
- prID, err := strconv.Atoi(prIDStr)
|
|
|
+ res.gitInstallationID = uint(ghID)
|
|
|
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+ prIDStr := os.Getenv("PORTER_PULL_REQUEST_ID")
|
|
|
+ prID, err := strconv.Atoi(prIDStr)
|
|
|
|
|
|
- res.prID = uint(prID)
|
|
|
- } else if prIDStr == "" {
|
|
|
- return nil, fmt.Errorf("Pull request ID must be defined, set by PORTER_PULL_REQUEST_ID")
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
+ res.prID = uint(prID)
|
|
|
+
|
|
|
res.projectID = config.Project
|
|
|
|
|
|
if res.projectID == 0 {
|
|
|
@@ -735,41 +773,26 @@ func NewDeploymentHook(client *api.Client, resourceGroup *switchboardTypes.Resou
|
|
|
return nil, fmt.Errorf("cluster id must be set")
|
|
|
}
|
|
|
|
|
|
- if branchName := os.Getenv("PORTER_BRANCH_NAME"); branchName != "" {
|
|
|
- res.branch = branchName
|
|
|
- } else if branchName == "" {
|
|
|
- return nil, fmt.Errorf("Branch name must be defined, set by PORTER_BRANCH_NAME")
|
|
|
- }
|
|
|
+ branchName := os.Getenv("PORTER_BRANCH_NAME")
|
|
|
+ res.branch = branchName
|
|
|
|
|
|
- if actionIDStr := os.Getenv("PORTER_ACTION_ID"); actionIDStr != "" {
|
|
|
- actionID, err := strconv.Atoi(actionIDStr)
|
|
|
+ actionIDStr := os.Getenv("PORTER_ACTION_ID")
|
|
|
+ actionID, err := strconv.Atoi(actionIDStr)
|
|
|
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- res.actionID = uint(actionID)
|
|
|
- } else if actionIDStr == "" {
|
|
|
- return nil, fmt.Errorf("Action Run ID must be defined, set by PORTER_ACTION_ID")
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- if repoName := os.Getenv("PORTER_REPO_NAME"); repoName != "" {
|
|
|
- res.repoName = repoName
|
|
|
- } else if repoName == "" {
|
|
|
- return nil, fmt.Errorf("Repo name must be defined, set by PORTER_REPO_NAME")
|
|
|
- }
|
|
|
+ res.actionID = uint(actionID)
|
|
|
|
|
|
- if repoOwner := os.Getenv("PORTER_REPO_OWNER"); repoOwner != "" {
|
|
|
- res.repoOwner = repoOwner
|
|
|
- } else if repoOwner == "" {
|
|
|
- return nil, fmt.Errorf("Repo owner must be defined, set by PORTER_REPO_OWNER")
|
|
|
- }
|
|
|
+ repoName := os.Getenv("PORTER_REPO_NAME")
|
|
|
+ res.repoName = repoName
|
|
|
|
|
|
- if prName := os.Getenv("PORTER_PR_NAME"); prName != "" {
|
|
|
- res.prName = prName
|
|
|
- } else if prName == "" {
|
|
|
- return nil, fmt.Errorf("PR Name must be supplied, set by PORTER_PR_NAME")
|
|
|
- }
|
|
|
+ repoOwner := os.Getenv("PORTER_REPO_OWNER")
|
|
|
+ res.repoOwner = repoOwner
|
|
|
+
|
|
|
+ prName := os.Getenv("PORTER_PR_NAME")
|
|
|
+ res.prName = prName
|
|
|
|
|
|
commit, err := git.LastCommit()
|
|
|
|
|
|
@@ -914,3 +937,91 @@ func (t *DeploymentHook) OnError(err error) {
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+type CloneEnvGroupHook struct {
|
|
|
+ client *api.Client
|
|
|
+ resGroup *switchboardTypes.ResourceGroup
|
|
|
+}
|
|
|
+
|
|
|
+func NewCloneEnvGroupHook(client *api.Client, resourceGroup *switchboardTypes.ResourceGroup) *CloneEnvGroupHook {
|
|
|
+ return &CloneEnvGroupHook{
|
|
|
+ client: client,
|
|
|
+ resGroup: resourceGroup,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (t *CloneEnvGroupHook) PreApply() error {
|
|
|
+ for _, res := range t.resGroup.Resources {
|
|
|
+ config := &ApplicationConfig{}
|
|
|
+
|
|
|
+ err := mapstructure.Decode(res.Config, &config)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if config != nil && len(config.EnvGroups) > 0 {
|
|
|
+ target := &Target{}
|
|
|
+
|
|
|
+ err = getTarget(res.Target, target)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, group := range config.EnvGroups {
|
|
|
+ if group.Name == "" {
|
|
|
+ return fmt.Errorf("env group name cannot be empty")
|
|
|
+ }
|
|
|
+
|
|
|
+ _, err := t.client.GetEnvGroup(
|
|
|
+ context.Background(),
|
|
|
+ target.Project,
|
|
|
+ target.Cluster,
|
|
|
+ target.Namespace,
|
|
|
+ &types.GetEnvGroupRequest{
|
|
|
+ Name: group.Name,
|
|
|
+ Version: group.Version,
|
|
|
+ },
|
|
|
+ )
|
|
|
+
|
|
|
+ if err != nil && err.Error() == "env group not found" {
|
|
|
+ if group.Namespace == "" {
|
|
|
+ return fmt.Errorf("env group namespace cannot be empty")
|
|
|
+ }
|
|
|
+
|
|
|
+ color.New(color.FgBlue, color.Bold).
|
|
|
+ Printf("Env group '%s' does not exist in the target namespace '%s'\n", group.Name, target.Namespace)
|
|
|
+ color.New(color.FgBlue, color.Bold).
|
|
|
+ Printf("Cloning env group '%s' from namespace '%s' to target namespace '%s'\n",
|
|
|
+ group.Name, group.Namespace, target.Namespace)
|
|
|
+
|
|
|
+ _, err = t.client.CloneEnvGroup(
|
|
|
+ context.Background(), target.Project, target.Cluster, group.Namespace,
|
|
|
+ &types.CloneEnvGroupRequest{
|
|
|
+ Name: group.Name,
|
|
|
+ Namespace: target.Namespace,
|
|
|
+ },
|
|
|
+ )
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ } else if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (t *CloneEnvGroupHook) DataQueries() map[string]interface{} {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (t *CloneEnvGroupHook) PostApply(populatedData map[string]interface{}) error {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (t *CloneEnvGroupHook) OnError(err error) {}
|