AWS ECSを始める

ECSとはDockerを簡単に実行、停止、管理できるサービス

management console ECS

Task Definition
create task definition -> EC2 -> json

{
  "family": "myContainer",
  "containerDefinitions": [
    {
      "volumesFrom": [],
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 80
        }
      ],
      "command": null,
      "environment": [],
      "essential": true,
      "entryPoint": null,
      "links": [],
      "mountPoints": [
        {
          "containerPath": "/usr/local/apache2/htdocs",
          "sourceVolume": "my-vol",
          "readOnly": null
        }
      ],
      "memory": 300,
      "name": "simple-app",
      "cpu": 10,
      "image": "httpd:2.4"
    },
    {
      "volumesFrom": [
        {
          "readOnly": null,
          "sourceContainer": "simple-app"
        }
      ],
      "portMappings": [],
      "command": [
        "/bin/sh -c \"while true; do echo '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p>' > top; /bin/date > date ; echo '</div></body></html>' > bottom; cat top date bottom > /usr/local/apache2/htdocs/index.html ; sleep 1; done\""
      ],
      "environment": [],
      "essential": false,
      "entryPoint": [
        "sh",
        "-c"
      ],
      "links": [],
      "mountPoints": [],
      "memory": 200,
      "name": "busybox",
      "cpu": 10,
      "image": "busybox"
    }
  ],
  "volumes": [
    {
      "host": {
        "sourcePath": null
      },
      "name": "my-vol"
    }
  ]
}

create

### Cluster
ECS Cluster -> Create
Launch type: EC2

Jsonでupdateできる。
とりあえず触ってみたという感じか。

Getting startedでECSを触ってみる

さくらレンタルサーバーにPython3とPip3を入れる手順

さくらレンタルサーバーにPython3をPip3を入れて動かそうとすると、
ModuleNotFoundError: No module named ‘_ctypes’ となるので、
あらかじめlibffiをインストールする必要がある。

### libffiインストール
mkdir -p ~/work/libffi
cd work/libffi
wget ftp://sourceware.org/pub/libffi/libffi-3.2.1.tar.gz
tar xvfz libffi-3.2.1.tar.gz
cd libffi-3.2.1
./configure –prefix=$HOME/local/libffi/3_2_1
make
% mkdir -p ~/local/include
% ln -s $HOME/local/libffi/3_2_1/lib/libffi-3.2.1/include/ffi.h $HOME/local/include/
% ln -s $HOME/local/libffi/3_2_1/lib/libffi-3.2.1/include/ffitarget.h $HOME/local/include/
% mkdir -p ~/local/lib
% ln -s $HOME/local/libffi/3_2_1/lib/libffi.a $HOME/local/lib/
% ln -s $HOME/local/libffi/3_2_1/lib/libffi.la $HOME/local/lib/
% ln -s $HOME/local/libffi/3_2_1/lib/libffi.so $HOME/local/lib/
% ln -s $HOME/local/libffi/3_2_1/lib/libffi.so.6 $HOME/local/lib/
% mkdir -p ~/local/lib/pkgconfig/
% ln -s $HOME/local/libffi/3_2_1/lib/pkgconfig/libffi.pc $HOME/local/lib/pkgconfig/

% cd ~/
% vi .cshrc
// パスを最終行に追加

setenv  LD_LIBRARY_PATH $HOME/local/lib
setenv  PKG_CONFIG_PATH $HOME/local/lib/pkgconfig

% source ~/.cshrc
% rehash

### Python3インストール
% mkdir -p ~/work/python3
% cd ~/work/python3
% wget –no-check-certificate https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz
% tar zxf Python-3.9.0.tgz
% cd ./Python-3.9.0
% ./configure –prefix=$HOME/local/python/ –with-system-ffi LDFLAGS=”-L $HOME/local/lib/” CPPFLAGS=”-I $HOME/local/include/”
% make
% make install

% cd ~/
%vi .cshrc
// 最終行に以下を追加

set path = ($path $HOME/local/python/bin)

% source ~/.cshrc
% rehash

% python3 –version
Python 3.9.0
% pip3 –version
pip 20.2.3

$ pip3 install ${package}

libffiが無くて、何度もNo module named ‘_ctypes’となり、焦りました。

[Python] 画像を圧縮

まず画像を用意します。

#! /usr/bin/python3
# -*- coding: utf-8 -*-
from PIL import Image
from io import BytesIO
import os

COMPRESS_QUALITY = 30

path = "./src"

images = os.listdir(path)

for image in images:
	if image.endswith('.jpg'):
		with open("./src/" + image, 'rb') as inputfile:
			im = Image.open(inputfile)
			im_io = BytesIO()
			im.save(im_io,'JPEG', quality=COMPRESS_QUALITY)
		with open("./src/comp_" + image, mode='wb') as outputfile:
			outputfile.write(im_io.getvalue())
	if image.endswith('.jpeg'):
		with open("./src/" + image, 'rb') as inputfile:
			im = Image.open(inputfile)
			im_io = BytesIO()
			im.save(im_io,'JPEG', quality=COMPRESS_QUALITY)
		with open("./src/comp_" + image, mode='wb') as outputfile:
			outputfile.write(im_io.getvalue())
	if image.endswith('.png'):
		with open("./src/" + image, 'rb') as inputfile:
			im = Image.open(inputfile)
			im_p = im.convert('P')
			with open("./src/comp_" + image, mode='wb') as outputfile:
				im_p.save(outputfile)

我ながらよく出来ています。

[CSS] width 100%で縦横比を固定

heightを0にして、padding-topを%で指定する

Sass

.v-box {
	width: 100%;
	height: 0;
	padding-top:75%;
	background-color: gray;
}

html

<div class="columns">
				<div class="column">Ch1<br><div class="v-box"></div><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p></div>
				<div class="column">Ch2<br><div class="v-box"></div><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p></div>
				<div class="column">Ch3<br><div class="v-box"></div><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p></div>
			</div>

なるほど

bluma CSSでヘッダー&メニュー作成

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<link rel="stylesheet" href="css/style.css">
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
	<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css" integrity="sha384-vp86vTRFVJgpjF9jiIGPEEqYqlDwgyBgEF109VFjmqGmIY/Y4HV4d3Gp2irVfcrp" crossorigin="anonymous">
</head>
<body>
	<div id="app">

	<nav class="navbar" role="navigation" aria-label="main navigation">
  	<div class="navbar-brand">
	    <a class="navbar-item" href="/home" style="font-size:1.5em;font-weight:bold;">
	      <!-- <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28"> -->
	      Speech Recognition Demo
	    </a>

	    <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="">
	      <span aria-hidden="true"></span>
	      <span aria-hidden="true"></span>
	      <span aria-hidden="true"></span>
	    </a>
	</div>

  	<div id="" class="navbar-menu">
    <!-- <div class="navbar-start">
      <a class="navbar-item">
        Home
      </a>
      <a class="navbar-item">
        Documentation
      </a>
    </div> -->

	    <div class="navbar-end">
	      <div class="navbar-item">
	      	<div class="navbar-item has-dropdown is-hoverable">
	        <a class="navbar-link">
	          Language
	        </a>

	        <div class="navbar-dropdown">
	          <a class="navbar-item">
	            English
	          </a>
	          <a class="navbar-item">
	            Chinese
	          </a>
	          <a class="navbar-item">
	            Japanase
	          </a>
	        </div>
	      </div>
	        <div class="buttons">
	          <a class="button is-light">
	            Logout
	          </a>
	        </div>
	      </div>
	    </div>
	  </div>
	</nav>

	<section class="main-content columns is-fullheight">
	<aside class="column is-3 is-narrow-mobile is-fullheight section is-hidden-mobile">
    <p class="menu-label is-hidden-touch">MENU</p>
    <ul class="menu-list">
      <li>
        <a href="/home" class="">
          <span class="icon"><i class="fa fa-home"></i></span> Home
        </a>
      </li>
      <li>
        <a href="#" class="">
          <span class="icon"><i class="fa fa-table"></i></span> Channel
        </a>

        <ul>
          <li>
            <a href="#">
              <span class="icon is-small"><i class="fa fa-link"></i></span> Ch1
            </a>
          </li>
          <li>
            <a href="#">
              <span class="icon is-small"><i class="fa fa-link"></i></span> Ch2
            </a>
          </li>
          <li>
            <a href="#">
              <span class="icon is-small"><i class="fa fa-link"></i></span> Ch3
            </a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#" class="">
          <span class="icon"><i class="fa fa-id-card"></i></span> MyPage
        </a>
      </li>
      <li>
        <a href="#" class="">
          <span class="icon"><i class="fa fa-file-alt"></i></span> Document
        </a>
      </li>
      <li>
        <a href="#" class="">
          <span class="icon"><i class="fa fa-comments"></i></span> Chat room
        </a>
      </li>
    </ul>
  </aside>
	

	<div class="container column is-9">
		<div class="section">
			<div class="card">
				<div class="card-header"><p class="card-header-title">Header</p></div>
				<div class="card-content"><div class="content">Content</div></div>
			</div>
			<br>

			<div class="card">
				<div class="card-header"><p class="card-header-title">Header</p></div>
				<div class="card-content"><div class="content">Content</div></div>
			</div>
			<br>

			<div class="columns">
				<div class="column">Column1</div>
				<div class="column">Column2</div>
				<div class="column">Column3</div>
			</div>
		</div>

	</div>

	</section>
	<footer class="footer is-hidden">
		<div class="container">
			<div class="content has-text-centered">
				<p>Hello</p>
			</div>
		</div>
	</footer>
	</div>
	<script src="bundle.js"></script>
</body>
</html>

うん、いい感じです

bluma CSSでLoginページを作成

<div class="hero is-fullheight is-primary">
		<div class="hero-body">
			<div class="container has-text-centered">
				<div class="column is-8 is-offset-2">
					<h3 class="title has-text-white">Speech Recognition Demo</h3>
					<hr class="login-hr">
					<p class="subtitle has-text-white">Login</p>
					<div class="box">
						<!-- <div class="box">
							<img src="img/login.png">
						</div> -->
						<div class="title has-text-grey is-5">Please enter your email and password.</div>
						<form>
							<div class="field">
								<div class="control">
									<input class="input is-large" type="email" placeholder="Email" autofocus="">
								</div>
							</div>
							<div class="field">
								<div class="control">
									<input class="input is-large" type="password" placeholder="Password">
								</div>
							</div>
							<button class="button is-block is-danger is-large is-fullwidth">Login</button>
						</form>
						<br>
						<p class="has-text-grey">
							<a href="">Sign Up</a> &nbsp;&nbsp;
							<a href="">Forgot Password</a> &nbsp;&nbsp;
							<a href="">Need Help?</a>
						</p>
					</div>
				</div>

			</div>
		</div>
	</div>

bootstrapにはない体験ができますね。

Webpackでsassとtypescriptの環境を作る

package.json

{
  "name": "front",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --mode development --env development",
    "dev": "webpack-dev-server --mode development --env development",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "hpscript",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.12.13",
    "@babel/preset-env": "^7.12.13",
    "@babel/preset-react": "^7.12.13",
    "babel-loader": "^8.2.2",
    "css-loader": "^5.0.2",
    "mini-css-extract-plugin": "^1.3.6",
    "node-sass": "^5.0.0",
    "optimize-css-assets-webpack-plugin": "^5.0.4",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "sass-loader": "^11.0.1",
    "style-loader": "^2.0.0",
    "ts-loader": "^9.2.6",
    "typescript": "^4.4.3",
    "webpack": "^5.53.0",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.2.1"
  },
  "description": ""
}

webpack.config.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
	entry: {
		anyname: `./src/scss/style.scss`,
		bundle: './src/app.ts'
	},
	output: {
		path: path.join(__dirname,'dist'),
		filename: '[name].js'
	},
	resolve: {
		extensions:['.ts','.js']
	},
	devServer: {
		host: '192.168.34.10',
        port: '8000',
        static: "./dist",
        open: true
	},
	module: {
		rules: [
			{
				test:/\.ts$/,use:'ts-loader'
			},
			{
                test: /\.scss$/,
                use: [
                    { loader: MiniCssExtractPlugin.loader },
                    { loader: 'css-loader' },
                    { loader: 'sass-loader' },
                ],
            }
		]
	},
	plugins: [
        new MiniCssExtractPlugin({ filename: 'css/style.css'}),
    ],
    optimization: {
        minimizer: [new OptimizeCSSAssetsPlugin({})],
    },
}

tsconfig.json

環境構築まではOK^^

[Go] Session管理

manager.go

package sessions

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"io"
	"net/http"
)

type Manager struct {
	database map[string]interface{}
}

var mg Manager

func NewManager() *Manager {
	return &mg
}

func (m *Manager) NewSessionID() string {
	b := make([]byte, 64)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		return ""
	}
	return base64.URLEncoding.EncodeToString(b)
}

fun (m *Manager) New(r *http.Request, cookieName string)(*Session, error){
	cookie, err := r.Cookie(cookie.Name)
	if err == nil && m.Exists(cookie.Value){
		return nil, errors.New("sessionIDは既に発行されています")
	}

	session := NewSession(m, cookieName)
	session.ID = m.NewSessionID()
	session.request = r

	return session, nil
}

func(m *Manager) Save(r *http.Request, w http.ResponseWriter, session *Session) error {
	m.database[session.ID] = session

	c := &http.Cookie {
		Name: session.Name(),
		Value: session.ID,
		Path: "/",
	}

	http.SetCookie(session.writer, c)
	return nil
}


func(m *Manager) Exists(sessionID string) bool {
	_, r = m.database[sessionID]
	return r
}

func(m *Manager) Get(r *http.Request, cookieName string)(*Session, error){
	cookie, err := r.Cookie(cookieName)
	if err != nil {
		return nil, err
	}

	sessionID := cookie.Value
	buffer, exists := m.database[sessionID]
	if !exists {
		return nil, errors.New("無効なセッションIDです")
	}

	session := buffer.(*Session)
	session.request = r
	return session, nil
}

func(m *Manager) Destroy(sessionID string){
	delete(m.database, sessionID)
}

session.go

package sessions

import(
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/context"
)

const (
	DefaultSessionName = "default-session"
	DefaultCookieName = "default-cookie"
)

type Session struct {
	cookieName string
	ID	string
	manager *Manager
	request *http.request
	writer http.ResponseWriter
	Values map[string]interface{}
}

func NewSession(manager *Manager, cookieName string) *Session {
	return &Session {
		cookieName: cookieName,
		manager: manager,
		Values: map[string]interface{}{},
	}
}

func StartSession(sessionName, cookieName string, manager *Manager) gin.HandlerFunc {
	return func(ctx *gin.Context){
		var session *Session
		var err error
		session, err = manager.Get(ctx.Request, cookieName)
		if err != nil {
			session, err = manager.New(ctx.Request, cookieName)
			if err != nil {
				println(err.Error())
				ctx.Abort()
			}
		}
		session.writer = ctx.Writer
		ctx.Set(sessionName, session)
		defer context.Clear(ctx.Request)
		ctx.Next()
	}
}

func StartDefaultSession(manager *Manager) gin.HandlerFunc {
	return StartSession(DefaultSessionName, DefaultCookieName, manager)
}

func GetSession(c *gin.Context, sessionName string) *Session {
	return c.MustGet(sessionName).(*Session)
}

func GetDefaultSession(c *gin.Context) *Session {
	return GetSession(c, DefaultSessionName)
}

func (s *Session) Save() error {
	return s.manager.Save(s.request, s.writer, s)
}

func (s *Session) Name() string {
	return s.cookieName
}

func (s *Session) Get(key string)(interface{}, bool){
	ret, exists := s.Value[key]
	return ret, exists
}

func(s *Session) Set(key string, val interface{}){
	s.Values[key] = val
}

func(s *Session) Delete(key string){
	delete(s.Values, key)
}

func(s *Session) Terminate(){
	s.manager.Destroy(s.ID)
}

h_Login.go

package req_handler

import (
	"fmt"
	"html/template"
	"net/http"
)

func HandlerLogin(w http.ResponseWrite, req *http.Request){

	tpl := template.Must(template.ParseFiles("templates/login.gtpl"))

	values := map[string]string{}

	if err := tpl.ExecuteTemplate(w, "login.gtpl", values); err != nil {
		fmt.Println(err)
	}
}

fun HandlerExecLogin(w http.ResponseWriter, req *http.Request){

	manager := sessions.NewManager()
	sessions.StartDefaultSession(manager)

	session := manager.Get(req, sessions.DefaultCookieName)
	session.Set("account", req.FormValue("account"))
	session.Save()

	http.Redirect(w, req, "http://192.168.34.10:10443/login-top", 301)
}

h_LoginTop.go

package req_handler

import (
	"fmt"
	"html/template"
	"net/http"
)

func HandlerLoginTop(w http.ResponseWriter, r *http.Request){
	tpl := template.Must(template.ParseFiles("templates/login-top.gtpl"))

	isLoggedIn := ""
	account := ""

	session := manager.Get(req, sessions.DefaultCookieName)
	if session.Values["account"]{
		isLoggedIn = "isLoggedIn"
		account = session.Values["account"]
	}

	values := map[string] string {
		"isLoggedIn": isLoggedIn,
		"account": account,
	}

	if err := tpl.ExecuteTemplate(w, "login-top.gtpl", values); err != nil {
		fmt.Println(err)
	}
}

func HandlerExecLogout(w http.ResponseWriter, req *http.Request){

	session := manager.Get(req, sessions.DefaultCookieName)
	session.Terminate()

	http.Redirect(w, req, "https://192.168.34.10:10443/login", 301)
}

なるほど、ginね
1. usersテーブルでユーザ名とパスワードが一致するかチェックして、一致した場合進めるようにする
2. sessionで管理する

セッションの使い方は覚えていけば良さそう

[Go] RevelでCRUD

route

GET     /                     App.Index
GET     /team/:id             App.Show 
GET     /edit/:id             App.Edit 
POST	/edit/complete		  App.Update
GET		/delete/:id			  App.Delete
GET		/create				  App.Create
POST	/store				  App.Store

controller

func (c App) Index() revel.Result {

	result := []models.Baseballs{}
	DB.Find(&result)

	return c.Render(result)
}

func (c App) Show(id int) revel.Result {

	result := []models.Baseballs{}
	DB.Where("id = ?", id).First(&result)
	team := result[0]
	fmt.Println(team.Name)

	return c.Render(team)
}

func (c App) Create() revel.Result {

	return c.Render()
}

func (c App) Store() revel.Result {

	name := c.Params.Get("name")
	manager := c.Params.Get("manager")
	home := c.Params.Get("home")

	c.Validation.Required(name).Message("name is required")
	c.Validation.Required(manager).Message("manager is required")
	c.Validation.Required(home).Message("home is required")

	if c.Validation.HasErrors(){
		c.Validation.Keep()
		c.FlashParams()
		return c.Redirect(App.Create)
	}

	DB.Create(&models.Baseballs{
		Name: name,
		Manager: manager,
		Home: home,
	})

	return c.Render(name)
}

func (c App) Edit(id int) revel.Result {

	result := []models.Baseballs{}
	DB.Where("id = ?", id).First(&result)
	team := result[0]

	return c.Render(team)
}

func (c App) Update() revel.Result {

	id := c.Params.Get("id")
	name := c.Params.Get("name")
	manager := c.Params.Get("manager")
	home := c.Params.Get("home")

	DB.Model(models.Baseballs{}).Where("id = ?", id).Update(&models.Baseballs{
		Name: name,
		Manager: manager,
		Home: home,
	})

	return c.Render(name)
}

func (c App) Delete(id int) revel.Result {

	DB.Where("id = ?", id).Delete(&models.Baseballs{})

	return c.Redirect(App.Index)
}

割と簡単にできますね
migrationとかあるのかわからんが、取り敢えずログイン機能までは作りたい

[Go] RevelでMySQLを操作

conf/app.conf

db.user = fuga
db.password = hoge
db.host = localhost
db.port = 3306
db.name = test
db.protocol = tcp

app/controllers/gorm.go

package controllers

import (
	"github.com/revel/revel"
	"github.com/jinzhu/gorm"
	"strings"
	"fmt"
	_"github.com/go-sql-driver/mysql"
)

var DB *gorm.DB


func InitDB(){
	db, err := gorm.Open("mysql", getConnectionString())

	if err != nil {
		panic(err.Error())
	}

	db.DB()
	DB = db
}


func getParamString(param string, defaultValue string) string {
	p, found := revel.Config.String(param)
	if !found {
		if defaultValue == "" {
			fmt.Sprintf("Could not find parameter: " + param)
		} else {
			return defaultValue
		}
	}
	return p
}


func getConnectionString() string {
	host := getParamString("db.host", "localhost")
	port := getParamString("db.port", "3306")
	user := getParamString("db.user", "root")
	pass := getParamString("db.password", "password")
	dbname := getParamString("db.name", "test")
	protocol := getParamString("db.protocol", "tcp")
	dbargs := getParamString("dbargs", "	")
	timezone := getParamString("db.timezone", "parseTime=True")

	if strings.Trim(dbargs, " ") != ""{
		dbargs = "?" + dbargs
	} else {
		dbargs = ""
	}

	return fmt.Sprintf("%s:%s@%s([%s]:%s)/%s?%s", user, pass, protocol, host, port, dbname, timezone)
}

app/models/idols.go

package models

type Idols struct {
	ID int
	Name string `json:"name"`
	Label string `json:"label"`
	Producer string `json:"producer"`
	birth string `json:"birth"`
	Member string `json:"member"`
	Song string `json:"song"`
}

app/controllers/app.go

package controllers

import (
	"github.com/revel/revel"
	"myapp/app/models"
	_"github.com/go-sql-driver/mysql"
)

type App struct {
	*revel.Controller
}

func (c App) Index() revel.Result {

	result := []models.Idols{}
	DB.Where("id = ?", 1).First(&result)
	greeting := result[0].Name
	
	return c.Render(greeting)
}

### 更新

	result := models.Idols{}
	DB.First(&result, 5)
	result.Song = "flash"
	DB.Save(&result)

大体わかった
後はデモを作るか topicは野球あたりにしましょう