github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/web/step.go (about) 1 package web 2 3 import ( 4 "context" 5 "io" 6 "net/http" 7 "path/filepath" 8 "strings" 9 10 "github.com/go-kit/kit/log" 11 "github.com/go-kit/kit/log/level" 12 "github.com/pkg/errors" 13 "github.com/replicatedhq/libyaml" 14 "github.com/replicatedhq/ship/pkg/api" 15 "github.com/replicatedhq/ship/pkg/lifecycle/render/root" 16 "github.com/replicatedhq/ship/pkg/templates" 17 "github.com/replicatedhq/ship/pkg/util" 18 "github.com/spf13/afero" 19 "github.com/spf13/viper" 20 ) 21 22 type Renderer interface { 23 Execute( 24 rootFs root.Fs, 25 asset api.WebAsset, 26 meta api.ReleaseMetadata, 27 templateContext map[string]interface{}, 28 configGroups []libyaml.ConfigGroup, 29 ) func(ctx context.Context) error 30 } 31 32 var _ Renderer = &DefaultStep{} 33 34 type DefaultStep struct { 35 Logger log.Logger 36 Fs afero.Afero 37 Viper *viper.Viper 38 BuilderBuilder *templates.BuilderBuilder 39 Client *http.Client 40 } 41 42 func NewStep( 43 logger log.Logger, 44 fs afero.Afero, 45 v *viper.Viper, 46 builderBuilder *templates.BuilderBuilder, 47 ) Renderer { 48 return &DefaultStep{ 49 Logger: logger, 50 Fs: fs, 51 Viper: v, 52 BuilderBuilder: builderBuilder, 53 Client: &http.Client{}, 54 } 55 } 56 57 type Built struct { 58 URL string 59 Dest string 60 Method string 61 Body string 62 Headers map[string][]string 63 BodyFormat string 64 } 65 66 func (p *DefaultStep) Execute( 67 rootFs root.Fs, 68 asset api.WebAsset, 69 meta api.ReleaseMetadata, 70 templateContext map[string]interface{}, 71 configGroups []libyaml.ConfigGroup, 72 ) func(ctx context.Context) error { 73 74 debug := level.Debug(log.With(p.Logger, "step.type", "render", "render.phase", "execute", 75 "asset.type", "web", "dest", asset.Dest, "description", asset.Description)) 76 77 return func(ctx context.Context) error { 78 debug.Log("event", "execute") 79 80 if p.Viper.GetBool("no-web") { 81 return errors.New("web assets are disabled when no-web is set") 82 } 83 84 built, err := p.buildAsset(asset, meta, configGroups, templateContext) 85 if err != nil { 86 debug.Log("event", "build.fail", "err", err) 87 return errors.Wrapf(err, "Build web asset") 88 } 89 90 err = util.IsLegalPath(built.Dest) 91 if err != nil { 92 return errors.Wrap(err, "write web asset") 93 } 94 95 basePath := filepath.Dir(asset.Dest) 96 debug.Log("event", "mkdirall.attempt", "root", rootFs.RootPath, "dest", built.Dest, "basePath", basePath) 97 if err := rootFs.MkdirAll(basePath, 0755); err != nil { 98 debug.Log("event", "mkdirall.fail", "err", err, "root", rootFs.RootPath, "dest", built.Dest, "basePath", basePath) 99 return errors.Wrapf(err, "Create directory path %s", basePath) 100 } 101 102 if err := p.pullWebAsset(rootFs, built); err != nil { 103 debug.Log("event", "pullWebAsset.fail", "err", err) 104 return errors.Wrapf(err, "Get web asset from %s", asset.URL) 105 } 106 107 return nil 108 } 109 } 110 111 func (p *DefaultStep) buildAsset( 112 asset api.WebAsset, 113 meta api.ReleaseMetadata, 114 configGroups []libyaml.ConfigGroup, 115 templateContext map[string]interface{}, 116 ) (*Built, error) { 117 118 builder, err := p.BuilderBuilder.FullBuilder(meta, configGroups, templateContext) 119 if err != nil { 120 return nil, errors.Wrap(err, "init builder") 121 } 122 123 builtURL, err := builder.String(asset.URL) 124 if err != nil { 125 return nil, errors.Wrap(err, "building url") 126 } 127 128 builtDest, err := builder.String(asset.Dest) 129 if err != nil { 130 return nil, errors.Wrap(err, "building dest") 131 } 132 133 builtMethod, err := builder.String(asset.Method) 134 if err != nil { 135 return nil, errors.Wrap(err, "building method") 136 } 137 138 builtBody, err := builder.String(asset.Body) 139 if err != nil { 140 return nil, errors.Wrap(err, "building body") 141 } 142 143 builtBodyFormat, err := builder.String(asset.BodyFormat) 144 if err != nil { 145 return nil, errors.Wrap(err, "building content type") 146 } 147 148 builtHeaders := make(map[string][]string) 149 for header, listOfValues := range asset.Headers { 150 for _, value := range listOfValues { 151 builtHeaderVal, err := builder.String(value) 152 if err != nil { 153 return nil, errors.Wrap(err, "building header val") 154 } 155 builtHeaders[header] = append(builtHeaders[header], builtHeaderVal) 156 } 157 } 158 return &Built{ 159 URL: builtURL, 160 Dest: builtDest, 161 Method: builtMethod, 162 Body: builtBody, 163 BodyFormat: builtBodyFormat, 164 Headers: builtHeaders, 165 }, nil 166 } 167 168 func (p *DefaultStep) pullWebAsset(rootFs root.Fs, built *Built) error { 169 req, err := http.NewRequest(built.Method, built.URL, strings.NewReader(built.Body)) 170 if err != nil { 171 return errors.Wrapf(err, "Request web asset from %s", built.URL) 172 } 173 174 if built.Method == "POST" { 175 req.Header.Set("Content-Type", built.BodyFormat) 176 } 177 178 if len(built.Headers) != 0 { 179 for header, listOfValues := range built.Headers { 180 for _, value := range listOfValues { 181 req.Header.Add(header, value) 182 } 183 } 184 } 185 186 resp, respErr := p.Client.Do(req) 187 if respErr != nil { 188 return errors.Wrapf(respErr, "%s web asset at %s", built.Method, built.URL) 189 } 190 defer resp.Body.Close() //nolint:errcheck 191 192 if resp.StatusCode > 299 { 193 return errors.Errorf("received response with status %d", resp.StatusCode) 194 } 195 196 file, err := rootFs.Create(built.Dest) 197 if err != nil { 198 return errors.Wrapf(err, "Create file %s", file) 199 } 200 201 if _, err := io.Copy(file, resp.Body); err != nil { 202 return errors.Wrapf(err, "Stream HTTP response body to %s", file.Name()) 203 } 204 return errors.Wrapf(file.Close(), "close file at %s", built.Dest) 205 }