Skip to content

Commit

Permalink
feat: initial work on extensions
Browse files Browse the repository at this point in the history
* Add an extension CRD which allows extensions to be added to a team
* Add extensions to PipelineActivity CRD which allows extensions to be
  defined on a per app basis
* Add an `jx upgrade extensions` command which installs extensions
  defined in a (intermediate) yaml file (tooling will be created to 
  create this). 
* `jx upgrade extensions` will additionally execute any `install` type
  extensions
* Add a `jx step pre extend` command which adds needed extensions to
  the pipeline
* Add a `jx step post run` command which executes any post extensions
* Add a `jx step collect —provider=github` command which will collect 
  any files specified by a pattern and add them to the GitHub Pages
  for the project.
* Add an attachment section to the PipelineActivity CRD which contains a
  list URLs. The `jx step collect` command will add any collected files
  to the list of attachments
  • Loading branch information
pmuir committed Oct 2, 2018
1 parent 4529976 commit de4d3ea
Show file tree
Hide file tree
Showing 63 changed files with 2,540 additions and 2,643 deletions.
563 changes: 505 additions & 58 deletions Gopkg.lock

Large diffs are not rendered by default.

Empty file added jenkins-x-extensions.yaml
Empty file.
2 changes: 2 additions & 0 deletions pkg/apis/jenkins.io/v1/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&EnvironmentList{},
&EnvironmentRoleBinding{},
&EnvironmentRoleBindingList{},
&Extension{},
&ExtensionList{},
&GitService{},
&GitServiceList{},
&PipelineActivity{},
Expand Down
166 changes: 166 additions & 0 deletions pkg/apis/jenkins.io/v1/types_extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package v1

import (
"bytes"
"fmt"
"github.com/jenkins-x/jx/pkg/log"
"github.com/jenkins-x/jx/pkg/util"
"github.com/pkg/errors"
"github.com/stoewer/go-strcase"
"io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=true

// Extension represents an extension available to this Jenkins X install
type Extension struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

Spec ExtensionDetails `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ExtensionList is a list of Extension resources
type ExtensionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`

Items []Extension `json:"items"`
}

// ExtensionDetails containers details of a user
type ExtensionDetails struct {
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
Description string `json:"description,omitempty" protobuf:"bytes,2,opt,name=description"`
Version string `json:"version,omitempty" protobuf:"bytes,3,opt,name=version"`
Script string `json:"script,omitempty" protobuf:"bytes,4,opt,name=script"`
When []ExtensionWhen `json:"when,omitempty" protobuf:"bytes,5,opt,name=when"`
Given ExtensionGiven `json:"given,omitempty" protobuf:"bytes,6,opt,name=given"`
Type ExtensionType `json:"type,omitempty" protobuf:"bytes,7,opt,name=type"`
Parameters []ExtensionParameter `json:"parameters,omitempty" protobuf:"bytes,8,opt,name=parameters"`

// TODO Pre ExtensionCondition `json:"pre,omitempty" protobuf:"bytes,4,opt,name=pre"`
}

type ExtensionWhen string

const (
// Executed before a pipeline starts
ExtensionWhenPre ExtensionWhen = "Pre"
// Executed after a pipeline completes
ExtensionWhenPost ExtensionWhen = "Post"
// Executed when an extension installs
ExtensionWhenInstall ExtensionWhen = "OnInstall"
// Executed when an extension upgrades
ExtensionWhenUpgrade ExtensionWhen = "OnUpgrade"
)

type ExtensionGiven string

const (
ExtensionConditionAlways ExtensionGiven = "Always"
ExtensionConditionFailure ExtensionGiven = "Failure"
ExtensionConditionSuccess ExtensionGiven = "Success"
)

type ExtensionParameter struct {
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
Description string `json:"description,omitempty" protobuf:"bytes,2,opt,name=description"`
EnvironmentVariableName string `json:"environmentVariableName,omitempty" protobuf:"bytes,3,opt,name=environmentVariableName"`
DefaultValue string `json:"defaultValue,omitempty" protobuf:"bytes,3,opt,name=defaultValue"`
}

type ExtensionType string

const (
ExtensionTypeBash ExtensionType = "Bash"
ExtensionTypeAny ExtensionType = "Any"
)

type ExecutableExtension struct {
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
Description string `json:"description,omitempty" protobuf:"bytes,2,opt,name=description"`
Script string `json:"script,omitempty" protobuf:"bytes,3,opt,name=script"`
EnvironmentVariables map[string]string `json:"environmentVariables,omitempty protobuf:"bytes,4,opt,name=environmentvariables"`
Given ExtensionGiven `json:"given,omitempty" protobuf:"bytes,5,opt,name=given"`
Type ExtensionType `json:"type,omitempty" protobuf:"bytes,6,opt,name=type"`
}

func (e *ExecutableExtension) Execute(verbose bool) (err error) {
scriptFile, err := ioutil.TempFile("", fmt.Sprintf("%s-*", e.Name))
if err != nil {
return err
}
script := ""
if e.Type == ExtensionTypeBash || e.Type == "" {
if !strings.HasPrefix("#!", e.Script) {
script = fmt.Sprintf("#!/bin/sh\n%s\n", e.Script)
}
} else {
script = e.Script
}
_, err = scriptFile.Write([]byte(script))
if err != nil {
return err
}
err = scriptFile.Chmod(0755)
if err != nil {
return err
}
if verbose {
log.Infof("Environment Variables:\n %s\n", e.EnvironmentVariables)
log.Infof("Script:\n %s\n", script)
}
cmd := util.Command{
Name: scriptFile.Name(),
Env: e.EnvironmentVariables,
}
log.Infof("Running Extension %s\n", util.ColorInfo(e.Name))
out, err := cmd.RunWithoutRetry()
log.Infoln(out)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Error executing script %s", e.Name))
}
return nil
}

func (e *ExtensionDetails) ToExecutable(envVarValues map[string]string) (ext ExecutableExtension, envVarsStr string, err error) {
envVars := make(map[string]string)
for _, p := range e.Parameters {
value := p.DefaultValue
if v, ok := envVarValues[p.Name]; ok {
value = v
}
// TODO Log any parameters from RepoExetensions NOT used
if value != "" {
envVarName := p.EnvironmentVariableName
if envVarName == "" {
envVarName = strings.ToUpper(strcase.SnakeCase(p.Name))
}
envVars[envVarName] = value
}
}
res := ExecutableExtension{
Name: e.Name,
Description: e.Description,
Script: e.Script,
Given: e.Given,
Type: e.Type,
EnvironmentVariables: envVars,
}
envVarsFormatted := new(bytes.Buffer)
for key, value := range envVars {
fmt.Fprintf(envVarsFormatted, "%s=%s, ", key, value)
}
return res, strings.TrimSuffix(envVarsFormatted.String(), ", "), err
}
45 changes: 26 additions & 19 deletions pkg/apis/jenkins.io/v1/types_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,27 @@ type PipelineActivity struct {

// PipelineActivitySpec is the specification of the pipeline activity
type PipelineActivitySpec struct {
Pipeline string `json:"pipeline,omitempty" protobuf:"bytes,1,opt,name=pipeline"`
Build string `json:"build,omitempty" protobuf:"bytes,2,opt,name=build"`
Version string `json:"version,omitempty" protobuf:"bytes,3,opt,name=version"`
Status ActivityStatusType `json:"status,omitempty" protobuf:"bytes,4,opt,name=status"`
StartedTimestamp *metav1.Time `json:"startedTimestamp,omitempty" protobuf:"bytes,5,opt,name=startedTimestamp"`
CompletedTimestamp *metav1.Time `json:"completedTimestamp,omitempty" protobuf:"bytes,6,opt,name=completedTimestamp"`
Steps []PipelineActivityStep `json:"steps,omitempty" protobuf:"bytes,7,opt,name=steps"`
BuildURL string `json:"buildUrl,omitempty" protobuf:"bytes,8,opt,name=buildUrl"`
BuildLogsURL string `json:"buildLogsUrl,omitempty" protobuf:"bytes,9,opt,name=buildLogsUrl"`
GitURL string `json:"gitUrl,omitempty" protobuf:"bytes,10,opt,name=gitUrl"`
GitRepository string `json:"gitRepository,omitempty" protobuf:"bytes,10,opt,name=gitRepository"`
GitOwner string `json:"gitOwner,omitempty" protobuf:"bytes,10,opt,name=gitOwner"`
ReleaseNotesURL string `json:"releaseNotesURL,omitempty" protobuf:"bytes,11,opt,name=releaseNotesURL"`
LastCommitSHA string `json:"lastCommitSHA,omitempty" protobuf:"bytes,12,opt,name=lastCommitSHA"`
LastCommitMessage string `json:"lastCommitMessage,omitempty" protobuf:"bytes,13,opt,name=lastCommitMessage"`
LastCommitURL string `json:"lastCommitURL,omitempty" protobuf:"bytes,14,opt,name=lastCommitURL"`
Workflow string `json:"workflow,omitempty" protobuf:"bytes,15,opt,name=workflow"`
WorkflowStatus ActivityStatusType `json:"workflowStatus,omitempty" protobuf:"bytes,16,opt,name=workflowStatus"`
WorkflowMessage string `json:"workflowMessage,omitempty" protobuf:"bytes,17,opt,name=workflowMessage"`
Pipeline string `json:"pipeline,omitempty" protobuf:"bytes,1,opt,name=pipeline"`
Build string `json:"build,omitempty" protobuf:"bytes,2,opt,name=build"`
Version string `json:"version,omitempty" protobuf:"bytes,3,opt,name=version"`
Status ActivityStatusType `json:"status,omitempty" protobuf:"bytes,4,opt,name=status"`
StartedTimestamp *metav1.Time `json:"startedTimestamp,omitempty" protobuf:"bytes,5,opt,name=startedTimestamp"`
CompletedTimestamp *metav1.Time `json:"completedTimestamp,omitempty" protobuf:"bytes,6,opt,name=completedTimestamp"`
Steps []PipelineActivityStep `json:"steps,omitempty" protobuf:"bytes,7,opt,name=steps"`
BuildURL string `json:"buildUrl,omitempty" protobuf:"bytes,8,opt,name=buildUrl"`
BuildLogsURL string `json:"buildLogsUrl,omitempty" protobuf:"bytes,9,opt,name=buildLogsUrl"`
GitURL string `json:"gitUrl,omitempty" protobuf:"bytes,10,opt,name=gitUrl"`
GitRepository string `json:"gitRepository,omitempty" protobuf:"bytes,10,opt,name=gitRepository"`
GitOwner string `json:"gitOwner,omitempty" protobuf:"bytes,10,opt,name=gitOwner"`
ReleaseNotesURL string `json:"releaseNotesURL,omitempty" protobuf:"bytes,11,opt,name=releaseNotesURL"`
LastCommitSHA string `json:"lastCommitSHA,omitempty" protobuf:"bytes,12,opt,name=lastCommitSHA"`
LastCommitMessage string `json:"lastCommitMessage,omitempty" protobuf:"bytes,13,opt,name=lastCommitMessage"`
LastCommitURL string `json:"lastCommitURL,omitempty" protobuf:"bytes,14,opt,name=lastCommitURL"`
Workflow string `json:"workflow,omitempty" protobuf:"bytes,15,opt,name=workflow"`
WorkflowStatus ActivityStatusType `json:"workflowStatus,omitempty" protobuf:"bytes,16,opt,name=workflowStatus"`
WorkflowMessage string `json:"workflowMessage,omitempty" protobuf:"bytes,17,opt,name=workflowMessage"`
PostExtensions map[string]ExecutableExtension `json:"postExtensions,omitempty" protobuf: "bytes,18,opt,name=postExtensions"`
Attachments []Attachment `json:"attachments,omitempty" protobuf: "bytes,19,opt,name=attachments"`
}

// PipelineActivityStep represents a step in a pipeline activity
Expand Down Expand Up @@ -162,6 +164,11 @@ const (
ActivityStatusTypeAborted ActivityStatusType = "Aborted"
)

type Attachment struct {
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
URLs []string `json:"urls,omitempty" protobuf:"bytes,2,opt,name=urls"`
}

// IsTerminated returns true if this activity has stopped executing
func (s ActivityStatusType) IsTerminated() bool {
return s == ActivityStatusTypeSucceeded || s == ActivityStatusTypeFailed || s == ActivityStatusTypeError || s == ActivityStatusTypeAborted
Expand Down
Loading

0 comments on commit de4d3ea

Please sign in to comment.