아파치 서브버전은 Git과 함께 형상관리의 중요한 툴로 자리 잡고 있다. 보통 레거시 프로젝트들은 svn을 사용한다.
리눅스에 svn 서버를 설치하기도 하는데 회사엔 window server에 설치되어있어 작업해보도록 하겠다.
cmd로도 작업할 수 있지만 윈도우의 강력한 cli인 powershell을 하나 켜주자.
1. svn 설치 및 확인
PS D:\> svn help
사용법: svn <subcommand> [options] [args]
Subversion 명령행 클라이언트 버전 1.6.0.
'svn help <subcommand>'를 사용하여 특정 명령에 대하여 도움말을 얻으십시오.
'svn --version'를 사용하여 버전과 원격접속 모듈에 대한 정보를 얻으십시오.
또는 'svn --version --quiet'를 사용하여 버전 정보만 얻으십시오.
대부분의 하위 명령들은 재귀적으로 수행하면서 파일이나 디렉토리를 인자로 취합니다.
명령들에 인자가 주어지지 않으면 현재 디렉토리를 포함하여 재귀적으로 수행하게
됩니다.
가능한 명령:
add
blame (praise, annotate, ann)
cat
changelist (cl)
checkout (co)
cleanup
commit (ci)
copy (cp)
delete (del, remove, rm)
diff (di)
export
help (?, h)
import
info
list (ls)
lock
log
merge
mergeinfo
mkdir
move (mv, rename, ren)
propdel (pdel, pd)
propedit (pedit, pe)
propget (pget, pg)
proplist (plist, pl)
propset (pset, ps)
resolve
resolved
revert
status (stat, st)
switch (sw)
unlock
update (up)
Subversion은 형상관리를 위한 도구입니다.
더 상세한 정보를 위해서는 http://subversion.tigris.org/ 를 방문하세요.
PS D:\>
svn 이 설치되어 있어야한다.
2. svnadmin create
PS D:\> svnadmin help
일반적인 사용법: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]
특정 부속 명령의 사용법을 위해서는 'svnadmin help <subcommand>' 를 참조하십시오.
'svnlook --version'으로 버전과 파일시스템 모듈을 볼 수가 있습니다.
가능한 하위 명령 목록:
crashtest
create
deltify
dump
help (?, h)
hotcopy
list-dblogs
list-unused-dblogs
load
lslocks
lstxns
pack
recover
rmlocks
rmtxns
setlog
setrevprop
setuuid
upgrade
verify
#svnserve
### This file controls the configuration of the svnserve daemon, if you
### use it to allow access to this repository. (If you only allow
### access through http: and/or file: URLs, then this file is
### irrelevant.)
### Visit http://subversion.tigris.org/ for more information.
[general]
### These options control access to the repository for unauthenticated
### and authenticated users. Valid values are "write", "read",
### and "none". The sample settings below are the defaults.
#anon-access = read
# auth-access = write
### The password-db option controls the location of the password
### database file. Unless you specify a path starting with a /,
### the file's location is relative to the directory containing
### this configuration file.
### If SASL is enabled (see below), this file will NOT be used.
### Uncomment the line below to use the default password file.
# password-db = passwd
### The authz-db option controls the location of the authorization
### rules for path-based access control. Unless you specify a path
### starting with a /, the file's location is relative to the the
### directory containing this file. If you don't specify an
### authz-db, no path-based access control is done.
### Uncomment the line below to use the default authorization file.
# authz-db = authz
### This option specifies the authentication realm of the repository.
### If two repositories have the same authentication realm, they should
### have the same password database, and vice versa. The default realm
### is repository's uuid.
# realm = My First Repository
[sasl]
### This option specifies whether you want to use the Cyrus SASL
### library for authentication. Default is false.
### This section will be ignored if svnserve is not built with Cyrus
### SASL support; to check, run 'svnserve --version' and look for a line
### reading 'Cyrus SASL authentication is available.'
# use-sasl = true
### These options specify the desired strength of the security layer
### that you want SASL to provide. 0 means no encryption, 1 means
### integrity-checking only, values larger than 1 are correlated
### to the effective key length for encryption (e.g. 128 means 128-bit
### encryption). The values below are the defaults.
# min-encryption = 0
# max-encryption = 256
원하는 환경설정을 해주면 되는데 아무나 접근할 수 없게 접근 조치만 변경한다.
...
[general]
### These options control access to the repository for unauthenticated
### and authenticated users. Valid values are "write", "read",
### and "none". The sample settings below are the defaults.
anon-access = none
auth-access = write
### The password-db option controls the location of the password
### database file. Unless you specify a path starting with a /,
### the file's location is relative to the directory containing
### this configuration file.
### If SASL is enabled (see below), this file will NOT be used.
### Uncomment the line below to use the default password file.
password-db = passwd
### The authz-db option controls the location of the authorization
...
외부 접근을 금지하고 인가된 계정은 write권한을 주었다.
4. 계정생성
conf 폴더의 passwd 파일을 열어서 계정을 추가한다.
#passwd
### This file is an example password file for svnserve.
### Its format is similar to that of svnserve.conf. As shown in the
### example below it contains one section labelled [users].
### The name and password for each user follow, one account per line.
[users]
# harry = harryssecret
# sally = sallyssecret
minggu = minggu123
Dood라는 개념이 있다. Docker Out Of Docker라는 개념인데 jenkins를 docker container로 띄우면 해당 container안에서 docker를 설치해 작업하는 Docker In Docker 방식이 있고 Docker Out Of Docker 방식이 있는데 DIND는 docker에서도 지양해야 하는 방법이라 Dood로 진행하려고 한다.
이를 위해 jenkins를 build할 때 host os의 docker.sock 소켓 파일을 jenkins container 내부와 volume 설정으로 연결해야 한다.
저번에 jenkins를 설치하면서 sock 연결은 했지만 jenkins container 내부에도 여러 파일들을 install 해야 한다.
저번에 만들어 둔 wildfly 밑에 새로 jenkins라는 이름의 service를 만들었다. 간단간단
5. docker compose up을 통한 build
#특정 service 명 입력
$ docker compose up jenkins
[+] Building 4.0s (6/6) FINISHED
=> [internal] load build definition from jenkins.Dockerfile 0.4s
=> => transferring dockerfile: 144B 0.0s
=> [internal] load .dockerignore 0.6s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/jenkins/jenkins:centos7 2.2s
=> [auth] jenkins/jenkins:pull token for registry-1.docker.io 0.0s
=> CACHED [1/1] FROM docker.io/jenkins/jenkins:centos7@sha256:84290087666555a858d5540a46875e59a0ac1ea9e18683b51972d8bf27f05e78 0.2s
=> => resolve docker.io/jenkins/jenkins:centos7@sha256:84290087666555a858d5540a46875e59a0ac1ea9e18683b51972d8bf27f05e78 0.2s
=> exporting to image 0.8s
=> => exporting layers 0.0s
=> => writing image sha256:72aeccf15a60e47a9bfec4986b18762bd255f5da9bec60562579acc5349c3f53 0.0s
=> => naming to docker.io/library/docker_jenkins 0.1s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 1/1
⠿ Container jenkinsC Created 0.5s
Attaching to jenkinsC
jenkinsC | Running from: /usr/share/jenkins/jenkins.war
jenkinsC | webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
jenkinsC | 2022-04-04 06:18:58.532+0000 [id=1] INFO org.eclipse.jetty.util.log.Log#initialized: Logging initialized @1443ms to org.eclipse.jetty.util.log.JavaUtilLog
jenkinsC | 2022-04-04 06:18:58.783+0000 [id=1] INFO winstone.Logger#logInternal: Beginning extraction from war file
jenkinsC | 2022-04-04 06:19:02.344+0000 [id=1] WARNING o.e.j.s.handler.ContextHandler#setContextPath: Empty contextPath
jenkinsC | 2022-04-04 06:19:02.500+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: jetty-9.4.45.v20220203; built: 2022-02-03T09:14:34.105Z; git: 4a0c91c0be53805e3fcffdcdcc9587d5301863db; jvm 11.0.14.1+1
jenkinsC | 2022-04-04 06:19:03.180+0000 [id=1] INFO o.e.j.w.StandardDescriptorProcessor#visitServlet: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
jenkinsC | 2022-04-04 06:19:03.276+0000 [id=1] INFO o.e.j.s.s.DefaultSessionIdManager#doStart: DefaultSessionIdManager workerName=node0
jenkinsC | 2022-04-04 06:19:03.277+0000 [id=1] INFO o.e.j.s.s.DefaultSessionIdManager#doStart: No SessionScavenger set, using defaults
jenkinsC | 2022-04-04 06:19:03.279+0000 [id=1] INFO o.e.j.server.session.HouseKeeper#startScavenging: node0 Scavenging every 660000ms
jenkinsC | 2022-04-04 06:19:04.470+0000 [id=1] INFO hudson.WebAppMain#contextInitialized: Jenkins home directory: /var/jenkins_home found at: EnvVars.masterEnvVars.get("JENKINS_HOME")
jenkinsC | 2022-04-04 06:19:05.530+0000 [id=1] INFO o.e.j.s.handler.ContextHandler#doStart: Started w.@6fca5907{Jenkins v2.341,/,file:///var/jenkins_home/war/,AVAILABLE}{/var/jenkins_home/war}
jenkinsC | 2022-04-04 06:19:05.591+0000 [id=1] INFO o.e.j.server.AbstractConnector#doStart: Started ServerConnector@2002348{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
jenkinsC | 2022-04-04 06:19:05.593+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: Started @8508ms
jenkinsC | 2022-04-04 06:19:05.596+0000 [id=24] INFO winstone.Logger#logInternal: Winstone Servlet Engine running: controlPort=disabled
jenkinsC | 2022-04-04 06:19:06.257+0000 [id=29] INFO hudson.PluginManager#loadDetachedPlugins: Upgrading Jenkins. The last running version was 2.332.1. This Jenkins is version 2.341.
jenkinsC | 2022-04-04 06:19:06.361+0000 [id=31] INFO jenkins.InitReactorRunner$1#onAttained: Started initialization
jenkinsC | 2022-04-04 06:19:06.371+0000 [id=29] INFO hudson.PluginManager#loadDetachedPlugins: Upgraded Jenkins from version 2.332.1 to version 2.341. Loaded detached plugins (and dependencies): []
jenkinsC | 2022-04-04 06:19:06.391+0000 [id=29] INFO jenkins.InitReactorRunner$1#onAttained: Listed all plugins
jenkinsC | 2022-04-04 06:19:08.524+0000 [id=36] INFO jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
jenkinsC | 2022-04-04 06:19:08.535+0000 [id=36] INFO jenkins.InitReactorRunner$1#onAttained: Started all plugins
jenkinsC | 2022-04-04 06:19:08.575+0000 [id=36] INFO jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
jenkinsC | 2022-04-04 06:19:09.174+0000 [id=37] INFO jenkins.InitReactorRunner$1#onAttained: System config loaded
jenkinsC | 2022-04-04 06:19:09.175+0000 [id=37] INFO jenkins.InitReactorRunner$1#onAttained: System config adapted
jenkinsC | 2022-04-04 06:19:09.176+0000 [id=37] INFO jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
jenkinsC | 2022-04-04 06:19:09.180+0000 [id=37] INFO jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
jenkinsC | 2022-04-04 06:19:09.217+0000 [id=51] INFO hudson.model.AsyncPeriodicWork#lambda$doRun$1: Started Download metadata
jenkinsC | 2022-04-04 06:19:09.236+0000 [id=51] INFO hudson.model.AsyncPeriodicWork#lambda$doRun$1: Finished Download metadata. 13 ms
jenkinsC | 2022-04-04 06:19:09.447+0000 [id=38] INFO jenkins.install.SetupWizard#init:
jenkinsC |
jenkinsC | *************************************************************
jenkinsC | *************************************************************
jenkinsC | *************************************************************
jenkinsC |
jenkinsC | Jenkins initial setup is required. An admin user has been created and a password generated.
jenkinsC | Please use the following password to proceed to installation:
jenkinsC |
jenkinsC | 38e626cc44dd4db89484ba29c29e8da6
jenkinsC |
jenkinsC | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
jenkinsC |
jenkinsC | *************************************************************
jenkinsC | *************************************************************
jenkinsC | *************************************************************
jenkinsC |
금방 설치되더니 하단에 generate 된 password 가 보인다.
혹시라도 background로 docker를 실행했다면 docker logs [docker name]으로 로그를 다시 찍어보면 된다.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
//구글서비스 추가
id 'com.google.gms.google-services' version '4.3.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
build.gradle(App)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services' //구글서비스 추가
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.realtime_sample"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
//뷰 바인딩추가
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Import the Firebase BoM
implementation platform('com.google.firebase:firebase-bom:29.2.1')
// Declare the dependencies for any other desired Firebase products
// For example, declare the dependencies for Firebase Authentication and Cloud Firestore
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.firebase:firebase-database-ktx'
implementation 'com.google.code.gson:gson:2.9.0'
}
import com.google.firebase.database.IgnoreExtraProperties
@IgnoreExtraProperties
data class User(val name: String? = null, val address: String? = null, val age: Int? = null) {
// Null default values create a no-argument default constructor, which is needed
// for deserialization from a DataSnapshot.
}
3.2) UserAdapter.kt
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class UserAdapter(val userList: ArrayList<User>): RecyclerView.Adapter<UserAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.name.text = userList[position].name
holder.address.text = userList[position].address
holder.age.text = userList[position].age.toString()
}
override fun getItemCount(): Int {
return userList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val name: TextView = itemView.findViewById(R.id.tv_name)
val address: TextView = itemView.findViewById(R.id.tv_address)
val age: TextView = itemView.findViewById(R.id.tv_age)
}
}
3.3) MainActivity.kt
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.realtimedatabasesample.databinding.ActivityMainBinding
import com.google.firebase.database.*
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
import com.google.gson.Gson
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
private lateinit var binding : ActivityMainBinding
private lateinit var database : DatabaseReference
private var drUser: DatabaseReference? = null
val db = FirebaseDatabase.getInstance()
val userList = arrayListOf<User>()
val adapter = UserAdapter(userList)
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.rvUserList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.rvUserList.adapter = adapter
database = Firebase.database.reference //DatabaseReference
/* 유저 읽기 */
binding.btnReload.setOnClickListener(View.OnClickListener {
database.child("users").get().addOnSuccessListener {
// 1. 데이터 1회 읽어 RecyclerView에 담기
/*
userList.clear()
for(userValue in it.children){
//jsonObject -> ModelClass by Gson
val user = Gson().fromJson(userValue.value.toString(), User::class.java)
userList.add(user)
}
adapter.notifyDataSetChanged()
Toast.makeText(this, "유저 읽기 성공", Toast.LENGTH_SHORT).show()
Log.i(TAG, "Got value ${it.value}")
*/
// 2. 데이터 변화 감지하는 ValueEventListener add
addUserListener(database)
}.addOnFailureListener {
Toast.makeText(this, "유저 읽기 실패", Toast.LENGTH_SHORT).show()
Log.w(TAG, "Error getting data! $it")
}
})
/* 유저 추가 */
binding.btnWrite.setOnClickListener(View.OnClickListener {
//동적으로 AlertDialog 생성
val builder = AlertDialog.Builder(this)
val tvName = TextView(this)
val tvAddress = TextView(this)
val tvAge = TextView(this)
tvName.text = "Name"
tvAddress.text = "Address"
tvAge.text = "Age"
val etName = EditText(this)
val etAddress = EditText(this)
val etAge = EditText(this)
etAddress.isSingleLine = true
val layout = LinearLayout(this)
layout.orientation = LinearLayout.VERTICAL
layout.setPadding(16, 16, 16, 16)
layout.addView(tvName)
layout.addView(etName)
layout.addView(tvAge)
layout.addView(etAge)
layout.addView(tvAddress)
layout.addView(etAddress)
builder.setView(layout)
.setTitle("유저 추가")
.setPositiveButton(R.string.add) { _, _ ->
val user = hashMapOf(
"name" to etName.text.toString(),
"address" to etAddress.text.toString(),
"age" to etAge.text.toString().toLong().toInt()
)
database.child("users").push().setValue(user)
.addOnSuccessListener {
Toast.makeText(this, "유저 추가 성공", Toast.LENGTH_SHORT).show()
}
.addOnFailureListener {
Toast.makeText(this, "유저 추가 실패", Toast.LENGTH_SHORT).show()
Log.w(TAG, "error! $it")
}
// binding.btnReload.callOnClick() //새로고침 버튼 클릭
}
.setNegativeButton(R.string.cancel) { _, _ -> }
.show()
})
/* 유저 삭제 */
binding.btnDelete.setOnClickListener(View.OnClickListener {
database.child("users").removeValue().addOnSuccessListener {
Toast.makeText(this, "유저 삭제 완료", Toast.LENGTH_SHORT).show()
}.addOnFailureListener {
Toast.makeText(this, "유저 삭제 실패", Toast.LENGTH_SHORT).show()
}
adapter.notifyDataSetChanged()
})
}
fun basicReadWrite() {
// [START write_message]
// Write a message to the database
val database = Firebase.database
val myRef = database.getReference("message")
myRef.setValue("Hello, World!")
// [END write_message]
// [START read_message]
// Read from the database
myRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
val value = dataSnapshot.getValue<String>()
Log.d(TAG, "Value is: $value")
}
override fun onCancelled(error: DatabaseError) {
// Failed to read value
Log.w(TAG, "Failed to read value.", error.toException())
}
})
// [END read_message]
}
private fun addUserListener(userDataReference: DatabaseReference){
val userListener = object : ValueEventListener {
@SuppressLint("NotifyDataSetChanged")
override fun onDataChange(dataSnapshot: DataSnapshot) {
if(dataSnapshot.hasChild("users")){
userList.clear()
for ( userValue in dataSnapshot.child("users").children){
val user = Gson().fromJson(userValue.value.toString(), User::class.java)
userList.add(user)
}
adapter.notifyDataSetChanged()
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadUsers:onCancelled", databaseError.toException())
}
}
userDataReference.addValueEventListener(userListener)
}
}
4.Realtime Database 조작
4.1) 새 데이터 생성
Firestore은 NoSQL 기반 Document 형식으로 되어있는데 Realtime Database는 Json형태로 되어있다.
샘플데이터를 먼저 만들어두자.
4.2) 데이터 읽기
/* 유저 읽기 */
binding.btnReload.setOnClickListener(View.OnClickListener {
database.child("users").get().addOnSuccessListener {
userList.clear()
for(json in it.children){
//jsonObject -> ModelClass by Gson
val user = Gson().fromJson(json.value.toString(), User::class.java)
userList.add(user)
}
adapter.notifyDataSetChanged()
Toast.makeText(this, "유저 읽기 성공", Toast.LENGTH_SHORT).show()
Log.i(TAG, "Got value ${it.value}")
}.addOnFailureListener {
Toast.makeText(this, "유저 읽기 실패", Toast.LENGTH_SHORT).show()
Log.w(TAG, "Error getting data! $it")
}
})
Json 데이터를 다뤄야 하다 보니까 Gson 라이브러리를 import 시켜놨다.
4.3) 데이터 쓰기
/* 유저 추가 */
binding.btnWrite.setOnClickListener(View.OnClickListener {
//동적으로 AlertDialog 생성
val builder = AlertDialog.Builder(this)
val tvName = TextView(this)
val tvAddress = TextView(this)
val tvAge = TextView(this)
tvName.text = "Name"
tvAddress.text = "Address"
tvAge.text = "Age"
val etName = EditText(this)
val etAddress = EditText(this)
val etAge = EditText(this)
etAddress.isSingleLine = true
val layout = LinearLayout(this)
layout.orientation = LinearLayout.VERTICAL
layout.setPadding(16, 16, 16, 16)
layout.addView(tvName)
layout.addView(etName)
layout.addView(tvAge)
layout.addView(etAge)
layout.addView(tvAddress)
layout.addView(etAddress)
builder.setView(layout)
.setTitle("유저 추가")
.setPositiveButton(R.string.add) { _, _ ->
val user = hashMapOf(
"name" to etName.text.toString(),
"address" to etAddress.text.toString(),
"age" to etAge.text.toString().toLong().toInt()
)
database.child("users").push().setValue(user)
.addOnSuccessListener {
Toast.makeText(this, "유저 추가 성공", Toast.LENGTH_SHORT).show()
}
.addOnFailureListener {
Toast.makeText(this, "유저 추가 실패", Toast.LENGTH_SHORT).show()
Log.w(TAG, "error! $it")
}
binding.btnReload.callOnClick() //새로고침 버튼 클릭
}
.setNegativeButton(R.string.cancel) { _, _ -> }
.show()
})
데이터를 쓰고 나서 읽어오기 위해 새로고침 버튼을 클릭하는데 Realtime Database에서는 ValueEventListener를 통해 데이터 변화를 실시간으로 감지할 수 있다.
4.4) 데이터 변화 감지
ValueEventListener를 통해 데이터의 변화를 실시간 감지할 수 있다.
전체 children도 가능하고 특정 child도 가능하다.
...
/* 유저 읽기 */
binding.btnReload.setOnClickListener(View.OnClickListener {
database.child("users").get().addOnSuccessListener {
// 1. 데이터 1회 읽어 RecyclerView에 담기
/*
userList.clear()
for(userValue in it.children){
//jsonObject -> ModelClass by Gson
val user = Gson().fromJson(userValue.value.toString(), User::class.java)
userList.add(user)
}
adapter.notifyDataSetChanged()
Toast.makeText(this, "유저 읽기 성공", Toast.LENGTH_SHORT).show()
Log.i(TAG, "Got value ${it.value}")
*/
// 2. 데이터 변화 감지하는 ValueEventListener add
addUserListener(database)
}.addOnFailureListener {
Toast.makeText(this, "유저 읽기 실패", Toast.LENGTH_SHORT).show()
Log.w(TAG, "Error getting data! $it")
}
})
...
private fun addUserListener(userDataReference: DatabaseReference){
val userListener = object : ValueEventListener {
@SuppressLint("NotifyDataSetChanged")
override fun onDataChange(dataSnapshot: DataSnapshot) {
if(dataSnapshot.hasChild("users")){
userList.clear()
for ( userValue in dataSnapshot.child("users").children){
val user = Gson().fromJson(userValue.value.toString(), User::class.java)
userList.add(user)
}
adapter.notifyDataSetChanged()
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadUsers:onCancelled", databaseError.toException())
}
}
userDataReference.addValueEventListener(userListener)
}
# docker-compose.yml
# docker-compose 버전명시
version: "3.8" # optional since v1.27.0
# 사용할 image들 즉, 생성할 컨테이너
services:
# 생성할 컨테이너 이름... 하지만 조금 다른 이름으로 생성됨
<container-name>:
# 컨테이너 생성에 사용할 이미지
image: node:10
# WORKDIR ( -w )
working_dir: /usr/src/app
# 포트포워딩 ( -p )
ports:
- 3000:3000
# docker build
build:
# 사용할 Dockerfile위치
context: ./
# 사용할 Dockerfile이름
dockerfile: Dockerfile.dev
# volume지정 ( -v )
volumes:
# node_modules는 컨테이너의 workdir에서 사용
- /usr/src/app/node_modules
# 이외에 모든 파일 참조
- ./:/usr/src/app
2.2) version
docker engine의 버전에 따라 docker-compose 버전도 달라지는데
$ docker -v
Docker version 20.10.14, build a224086
현재 docker 버전이 20대이기 때문에 3.8로 설정하면 되겠다.
근데 선택사항이니 필수로 적을 필요는 없다~!
2.3) services
각 컨테이너에서 적용될 생성 옵션을 지정한다.
서비스는 도커 컴포즈로 생성할 컨테이너 옵션을 정의한다. 이 항목에 쓰인 각 서비스는 컨테이너로 구현되며, 하나의 프로젝트로서 도커 컴포즈에 의해 관리된다. 서비스의 이름은 services의 하위 항목으로 정의하고 컨테이너의 옵션은 서비스 이름의 하위 항목에 정의한다.
image: 서비스의 컨테이너를 생성할 때 쓰일 이미지의 이름을 설정. 이미지 이름 포맷은 docker run의 [저장 소이름]/[이미지 이름]:[태그] 형태와 같으며, 존재하지 않을 경우 저장소에서 자동으로 내려받는다.
links: —link와 동일하며, 다른 서비스에 서비스명만으로 접근할 수 있도록 설정. [SERVICE:ALIAS]의 형식으로 별칭으로 서비스에 접근할 수도 있다.
environment: —env, -e 옵션과 동일하며, 컨테이너 내부에서 사용할 환경변수 설정.
command: run 명령어의 마지막에 붙는 커맨드와 같으며, 컨테이너가 실행될 때 수행할 명령어를 설정.
depends_on: 특정 컨테이너에 대한 의존 관계를 나타내며, 이 항목에 명시된 컨테이너가 먼저 실행되고 해당 컨테이너가 실행된다. cf> —no -deps: 특정 서비스의 컨테이너만 생성하고, 의존성이 없는 컨테이너 생성 ($ docker-compose up —no-deps web)
ports: -p와 같으며, 서비스의 컨테이너를 개방할 포트를 설정. 단일 호스트 환경에서 80:80과 같이 호스트의 특정 포트를 서비스의 컨테이너에 연결하면 docker-compose scale 명령어로 서비스의 컨테이너의 수를 늘릴 수 없다
build: 도커 파일에서 이미지를 빌드해 서비스의 컨테이너를 생성하도록 설정.
extends: 다른 YAML 파일이나 현재 YAML 파일에서 서비스 속성을 상속 받음.
2.4) networks
driver: 서비스의 컨테이너가 브리지 네트워크가 아닌 다른 네트워크를 사용하도록 설정할 수 있다.
ipam: IPAM(IP Address Manager)를 위해 사용할 수 있는 옵션으로 subnet, ip 범위 등을 설정할 수 있다.
external: YAML 파일을 통해 프로젝트를 생성할 때마다 네트워크를 생성하는 것이 아닌 기존의 네트워크를 사용하도록 설정할 수 있다.
네트워크 항목을 정의하지 않으면, 도커 컴포즈는 프로젝트별로 브리지 타입의 네트워크를 생성한다.
생성된 네트워크의 이름은 [프로젝트 이름]_default
생성 docker-compose up / 삭제 docker-compose down
2.5) volumes
driver: 볼륨을 생성할 때 사용될 드라이버를 설정한다. 어떠한 설정도 하지 않으면 local로 설정되며 사용하는 드라이버에 따라 변경해야 한다.
external: 프로젝트를 생성할 때마다 볼륨을 매번 생성하지 않고 기존 볼륨을 사용할 수 있도록 설정한다.
2.6) 파일 검증하기
docker-compose config 명령어를 사용하여, 오타나 파일 포맷이 적절한지 등에 대한 검사를 진행할 수 있다.
기본적으로 현재 디렉터리의 파일을 검사 (docker-compose -f [yml파일 경로] config로 변경 가능)
$ docker compose up --build
# 백그라운드에서 특정 yml 파일 실행
$ docker compose -f /opt/docker/docker-compose.yml up {myService} -d
--build: 실행할 때마다 새로 빌드하기 ( 변경사항 적용 )
-d : 백그라운드로 실행한다. 실제 서버는 -d로 띄우자
-f : docker-compose.yml 파일명이 다를 때 파일 선택 옵션
특정 형식에 맞춰서 컨테이너 이름이 자동적으로 생성됨
docker compose up --build
[+] Building 63.1s (11/11) FINISHED
=> [internal] load build definition from wildfly.Dockerfile 0.4s
=> => transferring dockerfile: 97B 0.0s
=> [internal] load .dockerignore 0.5s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/jboss/base-jdk:8 0.0s
=> [internal] load build context 8.9s
=> => transferring context: 393.77MB 8.2s
=> CACHED [1/6] FROM docker.io/jboss/base-jdk:8 0.0s
=> [2/6] RUN cd $HOME && curl -L -O https://github.com/wildfly/wildfly/releases/download/26.0.1.Final/wildfly-26.0.1.Final.tar.gz && sha1sum wildfly-26.0.1.Final.tar 35.7s
=> [3/6] RUN mkdir -p /opt/jboss/wildfly/modules/system/layers/base/org/postgresql/main && cd "$_" && curl -sLO https://repo1.maven.org/maven2/org/postgresql/postgres 3.5s
=> [4/6] ADD resources/module.xml /opt/jboss/wildfly/modules/system/layers/base/org/postgresql/main/module.xml 0.9s
=> [5/6] ADD resources/standalone.xml /opt/jboss/wildfly/standalone/configuration/standalone.xml 0.7s
=> [6/6] ADD resources/skylark-vbn.war /opt/jboss/wildfly/standalone/deployments/ 7.7s
=> exporting to image 13.6s
=> => exporting layers 13.2s
=> => writing image sha256:825c5d90f46a9f505f103df186aba7ba535eb21cbad9f010237500c128aa8ac9 0.0s
=> => naming to docker.io/library/skylark-wildfly 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 2/2
⠿ Network wildfly_default Created 0.9s
⠿ Container wildfly-wildflyC-1 Created 0.6s
Attaching to wildfly-wildflyC-1
wildfly-wildflyC-1 | =========================================================================
...
체감상 Dockerfile 돌리는 것보다 훨씬 빨랐다!!! 그리고 캐시때문인지 다시 빌드할때도 엄청나게 빠르다 ! 심지어 테스트까지 해주다니 이것 참 좋은 녀석이군
볼륨 설정한 폴더에 정상적으로 로그가 쌓인다.
5. 실행 후
백그라운드에서 실행한 후 로그를 확인하기 위해선 logs 명령어를 사용한다.
# jenkins라는 service의 log를 실시간 확인
$ docker compose logs -f jenkins
해당 컨테이너에서 명령어를 던지기 위해 run 이나 exec를 사용한다
$ docker compose run wildfly uname -a
Linux 048f0dd6a456 3.10.0-1160.49.1.el7.x86_64 #1 SMP Tue Nov 30 15:51:32 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
사용한 Dockerfile
# Use latest jboss/base-jdk:8 image as the base
FROM jboss/base-jdk:8
#metadata
LABEL name="minggu"
LABEL email="minha1002@gmail.com"
LABEL description="wildfly test"
FROM jboss/base-jdk:8
# Set the WILDFLY_VERSION env variable
ENV WILDFLY_VERSION 26.0.1.Final
ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
ENV JBOSS_HOME /opt/jboss/wildfly
USER root
# Add the WildFly distribution to /opt, and make wildfly the owner of the extracted tar content
# Make sure the distribution is available from a well-known place
RUN cd $HOME \
&& curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \
&& sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1 \
&& tar xf wildfly-$WILDFLY_VERSION.tar.gz \
&& mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \
&& rm wildfly-$WILDFLY_VERSION.tar.gz \
&& chown -R jboss:0 ${JBOSS_HOME} \
&& chmod -R g+rw ${JBOSS_HOME}
# Install postgreSQL Driver
RUN mkdir -p ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main \
&& cd "$_" \
&& curl -sLO https://repo1.maven.org/maven2/org/postgresql/postgresql/42.3.3/postgresql-42.3.3.jar
# Change resources
ADD resources/module.xml ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main/module.xml
ADD resources/standalone.xml ${JBOSS_HOME}/standalone/configuration/standalone.xml
# Add war file
# volume ADD Crush
#ADD resources/skylark-vbn.war ${JBOSS_HOME}/standalone/deployments/
# Add wildfly admin account
USER jboss
#RUN bash -c "${JBOSS_HOME}/bin/add-user.sh admin minggu"
RUN /opt/jboss/wildfly/bin/add-user.sh -u admin -p minggu #admin --silent
# Make volume directory
# docker run -v is better than VOLUME
#VOLUME /opt/wildfly_docker/logs
#VOLUME ${JBOSS_HOME}/standalone/deployments
# Ensure signals are forwarded to the JVM process correctly for graceful shutdown
ENV LAUNCH_JBOSS_IN_BACKGROUND true
# Expose the ports in which we're interested
EXPOSE 8080 9990
# Set the default command to run on boot
# This will boot WildFly in standalone mode and bind to all interfaces
CMD /opt/jboss/wildfly/bin/standalone.sh -c standalone.xml -b 0.0.0.0 -bmanagement 0.0.0.0 --debug
#CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]
[Android / Java] Cloud Firestore 연동을 통한 Data 조작(feat. RecyclerView)
Cloud Firestore는 Firebase 및 Google Cloud의 모바일, 웹, 서버 개발에 사용되는 유연하고 확장 가능한 데이터베이스입니다. Firebase 실시간 데이터베이스와 마찬가지로 실시간 리스너를 통해 클라이언트 애플리케이션 간에 데이터의 동기화를 유지하고 모바일 및 웹에 대한 오프라인 지원을 제공해 네트워크 지연 시간이나 인터넷 연결에 상관없이 원활하게 반응하는 앱을 개발할 수 있습니다. Cloud Firestore는 Cloud Functions를 비롯한 다른 Firebase 및 Google Cloud 제품과도 원활하게 통합됩니다. -구글
자자 안드로이드 할 거면 파이어 스토어는 구축해봐야겠지.
공식문서와 함께 firestore을 구축해보자. 물론 하단엔 sample github도 첨부하겠다.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
//구글 서비스 추가
id 'com.google.gms.google-services' version '4.3.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
2.2) app 수준 Gradle
plugins {
id 'com.android.application'
id 'com.google.gms.google-services' //구글서비스 추가
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.firestore_sample"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Import the BoM for the Firebase platform
implementation platform('com.google.firebase:firebase-bom:29.2.1')
// Declare the dependency for the Cloud Firestore library
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation 'com.google.firebase:firebase-firestore'
}
추가할 건 별로 없다.
3. Firestore 컬렉션 추가
Cloud Firestore는 NoSQL 문서 중심의 데이터베이스입니다. SQL 데이터베이스와 달리 테이블이나 행이 없으며, 컬렉션으로 정리되는 문서에 데이터를 저장합니다.
각 문서에는 키-값 쌍이 들어 있습니다. Cloud Firestore는 작은 문서가 많이 모인 컬렉션을 저장하는 데 최적화되어 있습니다.
모든 문서는 컬렉션에 저장되어야 합니다. 문서는 하위 컬렉션 및 중첩 객체를 포함할 수 있으며, 둘 다 문자열 같은 기본형 필드나 목록 같은 복합 객체를 포함할 수 있습니다.
컬렉션과 문서는 Cloud Firestore에서 암시적으로 생성됩니다. 사용자는 컬렉션 내의 문서에 데이터를 할당하기만 하면 됩니다. 컬렉션 또는 문서가 없으면 Cloud Firestore에서 자동으로 생성합니다.
Firestore Instance를 앱 내에 선언하고 명시적으로 Collection과 Documentation을 추가해 줄 수도 있고 FIrebase콘솔에서 생성해 줄 수도 있다고 하는데 일단 우리는 콘솔에서 새 Collection을 하나 만들어보자.
서버는 eastasia를 선택했다.
컬렉션 시작을 눌러주고
별다른 설명 없이도 너무나 쉽다..
4. Layout 그리기
단순 데이터를 조작하는 것도 좋지만 뷰를 띄워서 실제로 동작하는 것을 보면 더더욱 공부가 될 테니 간단하게나마 Recycler View의 Layout을 그려보도록 하자.
// Access a Cloud Firestore instance from your Activity
Firestore firestore = new Firestore(FirebaseFirestore.getInstance());
5.2) Data 추가
Cloud Firestore는 컬렉션에 저장되는 문서에 데이터를 저장합니다. 문서에 데이터를 처음 추가할 때 Cloud Firestore에서 암시적으로 컬렉션과 문서를 만듭니다. 컬렉션이나 문서를 명시적으로 만들 필요가 없습니다.
다음 예시 코드를 사용해 새 컬렉션과 문서를 만듭니다.
// Add a new document with a generated ID
// 새 컬렉션과 문서 추가
db.collection("users")
.add(user)
.addOnSuccessListener(documentReference -> Log.d(TAG, "DocumentSnapshot added with ID: " + documentReference.getId()))
.addOnFailureListener(e -> Log.w(TAG, "Error adding document", e));
//하위 컬렉션의 메시지에 대한 참조를 만들 수 있습니다.
DocumentReference messageRef = db
.collection("rooms").document("roomA")
.collection("messages").document("message1");
//난 다음과 같이 구현했다.
db.collection("users")
.add(user)
.addOnSuccessListener(e -> {
Toast.makeText(this, "유저 추가 성공", Toast.LENGTH_SHORT).show();
})
.addOnFailureListener(exception -> {
Toast.makeText(this, "유저 추가 실패", Toast.LENGTH_SHORT).show();
Log.w(TAG, "error! " + exception);
});
혹은 유저를 추가하는 함수를 만들 수도 있겠다.
5.3) Data 읽기
db.collection("users")
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
}
} else {
Log.w(TAG, "Error getting documents.", task.getException());
}
});
//난 이렇게 구현했다.
db.collection("users")
.get()
.addOnSuccessListener(e -> {
userList.clear();
for (QueryDocumentSnapshot document : e) {
UserModel user = new UserModel((String) document.get("firstName")
, (String) document.get("lastName")
, (Long) document.get("born")
);
userList.add(user);
}
adapter.notifyDataSetChanged();
Toast.makeText(this, "유저 데이터 로드 성공", Toast.LENGTH_SHORT).show();
})
.addOnFailureListener(exception -> {
Toast.makeText(this, "데이터 로드 실패", Toast.LENGTH_SHORT).show();
Log.w(TAG, "error! " + exception);
});
다음은 시작하는 데 사용할 수 있는 기본 규칙 세트입니다. Console의 규칙 탭에서 보안 규칙을 수정할 수 있습니다.
// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
//테스트
allow read, write: if true;
//allow read, write: if request.auth != null;
}
}
}
인증과 함께 규칙을 설정하면 database를 다루는데 보안성이 강화될 것이다.
7. 최종 Test
7.1) 앱 최초 실행
7.2) 새로고침 버튼 클릭
7.3) 유저 추가 버튼 클릭
정상적으로 들어오는 것을 확인했다.
7.4) 유저 삭제
삭제는 읽기나 추가처럼 collection만 참조해서는 안되고 document 자체를 참조해야 하는데 id값을 받아와야 구현이 가능하겠다.
# Use latest jboss/base-jdk:11 image as the base
FROM jboss/base-jdk:11
# Set the WILDFLY_VERSION env variable
ENV WILDFLY_VERSION 26.0.1.Final
ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
ENV JBOSS_HOME /opt/jboss/wildfly
USER root
# Add the WildFly distribution to /opt, and make wildfly the owner of the extracted tar content
# Make sure the distribution is available from a well-known place
RUN cd $HOME \
&& curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \
&& sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1 \
&& tar xf wildfly-$WILDFLY_VERSION.tar.gz \
&& mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \
&& rm wildfly-$WILDFLY_VERSION.tar.gz \
&& chown -R jboss:0 ${JBOSS_HOME} \
&& chmod -R g+rw ${JBOSS_HOME}
# Ensure signals are forwarded to the JVM process correctly for graceful shutdown
ENV LAUNCH_JBOSS_IN_BACKGROUND true
USER jboss
# Expose the ports in which we're interested
EXPOSE 8080
# Set the default command to run on boot
# This will boot WildFly in standalone mode and bind to all interfaces
CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]
3. docker build
#docker build -t [name]:[tag] [경로] -f [*.Dockerfile]
[minggu92@cloud dockerfiles]$ docker build -t wildfly:0.1 /dockerfiles/ -f /dockerfiles/wildfly.Dockerfile
Sending build context to Docker daemon 5.12kB
Step 1/10 : FROM jboss/base-jdk:8
8: Pulling from jboss/base-jdk
75f829a71a1c: Pull complete
7b11f246b3d3: Pull complete
b765648c2a58: Pull complete
3672c49587a4: Pull complete
Digest: sha256:4ce172c6e0874a5e1b38a90dd6eda148f3e2fa1d196cba95e5a32dc0809286c8
Status: Downloaded newer image for jboss/base-jdk:8
---> b0802b3eccf0
Step 2/10 : ENV WILDFLY_VERSION 26.0.1.Final
---> Running in c239c89152d2
Removing intermediate container c239c89152d2
---> 1f170fb475fe
Step 3/10 : ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
---> Running in ad87a7569133
Removing intermediate container ad87a7569133
---> caebe17f0f49
Step 4/10 : ENV JBOSS_HOME /opt/jboss/wildfly
---> Running in 1c32c740d2f8
Removing intermediate container 1c32c740d2f8
---> 5e1338c9a63b
Step 5/10 : USER root
---> Running in cbb71484eb19
Removing intermediate container cbb71484eb19
---> ebe0bc90a9df
Step 6/10 : RUN cd $HOME && curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz && sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1 && tar xf wildfly-$WILDFLY_VERSION.tar.gz && mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME && rm wildfly-$WILDFLY_VERSION.tar.gz && chown -R jboss:0 ${JBOSS_HOME} && chmod -R g+rw ${JBOSS_HOME}
---> Running in 8efc34f6999f
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 660 100 660 0 0 807 0 --:--:-- --:--:-- --:--:-- 808
100 207M 100 207M 0 0 368k 0 0:09:36 0:09:36 --:--:-- 1355k
08908faf9ae99e5fb6374979afbffea461aadc2c wildfly-26.0.1.Final.tar.gz
Removing intermediate container 8efc34f6999f
---> 700b6dc1974a
Step 7/10 : ENV LAUNCH_JBOSS_IN_BACKGROUND true
---> Running in 18d2acff9d47
Removing intermediate container 18d2acff9d47
---> 38b8214ce130
Step 8/10 : USER jboss
---> Running in 5306dfff81a9
Removing intermediate container 5306dfff81a9
---> e47f42fc981a
Step 9/10 : EXPOSE 8080
---> Running in caaa4e890999
Removing intermediate container caaa4e890999
---> f7ef1591a05c
Step 10/10 : CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]
---> Running in 0dccf31d8ec2
Removing intermediate container 0dccf31d8ec2
---> 6725a1d25a62
Successfully built 6725a1d25a62
Successfully tagged wildfly:0.1
4. docker run
#생성된 docker image 확인
[minggu92@cloud dockerfiles]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wildfly 0.1 6725a1d25a62 13 minutes ago 757MB
jboss/base-jdk 8 b0802b3eccf0 18 months ago 495MB
#docker container 실행
[minggu92@cloud dockerfiles]$ docker run -p 1003:8080 wildfly:0.1
=========================================================================
JBoss Bootstrap Environment
JBOSS_HOME: /opt/jboss/wildfly
JAVA: /usr/lib/jvm/java/bin/java
JAVA_OPTS: -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
=========================================================================
04:45:12,433 INFO [org.jboss.modules] (main) JBoss Modules version 2.0.0.Final
04:45:13,419 INFO [org.jboss.msc] (main) JBoss MSC version 1.4.13.Final
04:45:13,445 INFO [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final
....
....
8417ms - Started 298 of 538 services (337 services are lazy, passive or on-demand)
04:45:20,001 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
04:45:20,001 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
5. 확인
일단 서버가 기동 되는 것을 확인, 이제 상황에 맞게 커스터마이징 하면 된다.
6. Custormizing
# Use latest jboss/base-jdk:8 image as the base
FROM jboss/base-jdk:8
#metadata
LABEL name="minggu"
LABEL email="minha1002@gmail.com"
LABEL description="wildfly test"
FROM jboss/base-jdk:8
# Set the WILDFLY_VERSION env variable
ENV WILDFLY_VERSION 26.0.1.Final
ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
ENV JBOSS_HOME /opt/jboss/wildfly
USER root
# Add the WildFly distribution to /opt, and make wildfly the owner of the extracted tar content
# Make sure the distribution is available from a well-known place
RUN cd $HOME \
&& curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \
&& sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1 \
&& tar xf wildfly-$WILDFLY_VERSION.tar.gz \
&& mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \
&& rm wildfly-$WILDFLY_VERSION.tar.gz \
&& chown -R jboss:0 ${JBOSS_HOME} \
&& chmod -R g+rw ${JBOSS_HOME}
# Install postgreSQL Driver
RUN mkdir -p ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main \
&& cd "$_" \
&& curl -sLO https://repo1.maven.org/maven2/org/postgresql/postgresql/42.3.3/postgresql-42.3.3.jar
# Change resources
ADD resources/module.xml ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main/module.xml
ADD resources/standalone.xml ${JBOSS_HOME}/standalone/configuration/standalone.xml
# Add war file
# volume ADD Crush
#ADD resources/skylark-vbn.war ${JBOSS_HOME}/standalone/deployments/
# Add wildfly admin account
USER jboss
#RUN bash -c "${JBOSS_HOME}/bin/add-user.sh admin minggu"
RUN /opt/jboss/wildfly/bin/add-user.sh -u admin -p minggu #admin --silent
# Make volume directory
# docker run -v is better than VOLUME
#VOLUME /opt/wildfly_docker/logs
#VOLUME ${JBOSS_HOME}/standalone/deployments
# Ensure signals are forwarded to the JVM process correctly for graceful shutdown
ENV LAUNCH_JBOSS_IN_BACKGROUND true
# Expose the ports in which we're interested
EXPOSE 8080 9990
# Set the default command to run on boot
# This will boot WildFly in standalone mode and bind to all interfaces
CMD /opt/jboss/wildfly/bin/standalone.sh -c standalone.xml -b 0.0.0.0 -bmanagement 0.0.0.0 --debug
#CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]
미리 module을 정의한 standalone.xml을 준비해서 실행하면 된다
팁! ADD 로 war 경로를 설정했지만 volume으로 선언하면 war가 add되지 않는다.
Ctrl + Alt + s or File - Settings에서Version Control - Git
최상단 우측에 Test 버튼을 클릭하면 하단에 Git version 이 나온다. 안드 버전이 옛날 거라 2.28.0으로 나오넴..
3. GitHub 계정 추가
그 바로 밑에 GitHub에 들어가 Add Account.. 를 누르면
Use Token을 이용해 토큰으로 접속해보자.
GitHub - Settings - Developer settings - Personal access tokens
repo, admin, gist 항목을 체크해주고 발급!
3. Branch 변경
다시 Settings로 들어가서 보면
아마 default로 branch 설정이 master로 되어있을 텐데... GitHub에서 repository를 만들면 main이라는 branch가 default로 지정된다. 그 이유는 'Black Lives Matter' 운동에 발맞춰서 master, slave 이런 용어 쓰지 말자나 뭐라나..
4. GitHub로 commit
VCS-import into Version Control-Share Project on GitHub
저번에 firebase-sample-java로 만들어서 먼저 java로 작업을 해보도록 하겠다.
1. firebase console에 구글 인증 추가
Authentication 클릭
구글 클릭하고 내 메일 넣으면
짜잔.
등록을 안 하면 ApiException 12500 에러가 뜬다!
2. 구글 인증을 위한 SDK 추가
App수준 Gradle 추가
plugins {
id 'com.android.application'
id 'com.google.gms.google-services' //구글서비스 추가
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.firebase_sample"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Import the Firebase BoM
implementation platform('com.google.firebase:firebase-bom:29.2.0')
// Add the dependency for the Firebase SDK for Google Analytics
// When using the BoM, don't specify versions in Firebase dependencies
implementation 'com.google.firebase:firebase-analytics'
// Add the dependencies for any other desired Firebase products
// https://firebase.google.com/docs/android/setup#available-libraries
// Declare the dependency for the Firebase Authentication library
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation 'com.google.firebase:firebase-auth'
// Also declare the dependency for the Google Play services library and specify its version
implementation 'com.google.android.gms:play-services-auth:20.1.0'
}
근데 저 default_web_client_id는 아래 google cloud platform에서 확인할 수 있는데
내가 설정 안 해도 자동으로 firebase가 만들어주더라.....
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 파이어베이스 인증 객체 선언
mAuth = FirebaseAuth.getInstance();
// Google 로그인을 앱에 통합
// GoogleSignInOptions 개체를 구성할 때 requestIdToken을 호출
GoogleSignInOptions googleSignInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build();
mGoogleSignInClient = GoogleSignIn.getClient(this, googleSignInOptions);
btnGoogleLogin = findViewById(R.id.btn_google_sign_in);
btnGoogleLogin.setOnClickListener(view -> {
// 기존에 로그인 했던 계정을 확인한다.
gsa = GoogleSignIn.getLastSignedInAccount(MainActivity.this);
if (gsa != null) // 로그인 되있는 경우
Toast.makeText(MainActivity.this, R.string.status_login, Toast.LENGTH_SHORT).show();
else
signIn();
});
btnLogoutGoogle = findViewById(R.id.btn_logout_google);
btnLogoutGoogle.setOnClickListener(view -> {
signOut(); //로그아웃
});
}
공식문서는 간단하게 만들었지만 우리는 버튼도 만들어뒀으니 클릭이벤트도 달아주자.
기존에 로그인했더라면 이미 로그인되어있다는 토스트 메시지를 띄워줄 것이다.
5.2) 로그인 처리(onActivityResult)
로그인 처리가 정상적으로 이루어지면 GoogleSignInAccount 객체를 받아온다.
private void signIn(){
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(signInIntent, RC_SIGN_IN);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
// The Task returned from this call is always completed, no need to attach a listener.
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleSignInResult(task);
}
}
Intent와 함께 넘긴 RC_SIGN_IN 상수를 제대로 돌려받았다면 Task 객체를 가져와 handleSignInResult 함수에 태워주자.
5.3) 로그인 후처리 (계정 정보 가져오기)
/* 사용자 정보 가져오기 */
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount acct = completedTask.getResult(ApiException.class);
if (acct != null) {
firebaseAuthWithGoogle(acct.getIdToken());
String personName = acct.getDisplayName();
String personGivenName = acct.getGivenName();
String personFamilyName = acct.getFamilyName();
String personEmail = acct.getEmail();
String personId = acct.getId();
Uri personPhoto = acct.getPhotoUrl();
Log.d(TAG, "handleSignInResult:personName "+personName);
Log.d(TAG, "handleSignInResult:personGivenName "+personGivenName);
Log.d(TAG, "handleSignInResult:personEmail "+personEmail);
Log.d(TAG, "handleSignInResult:personId "+personId);
Log.d(TAG, "handleSignInResult:personFamilyName "+personFamilyName);
Log.d(TAG, "handleSignInResult:personPhoto "+personPhoto);
}
} catch (ApiException e) {
// The ApiException status code indicates the detailed failure reason.
// Please refer to the GoogleSignInStatusCodes class reference for more information.
Log.e(TAG, "signInResult:failed code=" + e.getStatusCode());
}
}
// [START auth_with_google]
private void firebaseAuthWithGoogle(String idToken) {
AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success");
Toast.makeText(MainActivity.this, R.string.success_login, Toast.LENGTH_SHORT).show();
FirebaseUser user = mAuth.getCurrentUser();
// updateUI(user);
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCredential:failure", task.getException());
Toast.makeText(MainActivity.this, R.string.failed_login, Toast.LENGTH_SHORT).show();
// updateUI(null);
}
});
}
성공적으로 로그인이 되었을 때 FirebaseAuth객체의 공유 인스턴스를 가져오고 GoogleSignInAccount객체에서 ID 토큰을 가져와서 Firebase 사용자 인증 정보로 교환한다.
원래는 블로그를 내가 자주 잊을 수 있는 안드로이드 위주로만 올리려고 하다가.. 웹 개발 쪽 이것저것 올리다 보니 소홀해졌었다.
따라서 감도 살리고 최신 기술의 동향도 살필 겸 스터디 모임을 통해 안드로이드를 다시 잡게 되었다.
그 첫번째는 firebase를 연동하는 것이다.
공식문서와 함께 작업해보자.
우리는 옵션1로 시작할 것이다.
1. 새 android project 생성
먼저 프로젝트를 생성해놔야 코드를 넣을 수 있다.
empty project를 하나 만들어주자.
2. firebase 회원가입 및 프로젝트 생성
회원가입은 편하게 구글로 하면 된다.
짓고 싶은 이름으로 하나 만들어준다.
google analytics 설정을 안 하면 바로 빌드된다.
3. Android 앱에 Firebase 추가
홈 화면에서 '앱에 Firebase를 추가하여 시작하기 - Android' 로고를 선택
3.1) package 명 입력
최상단 패키지명을 입력하면 된다.
3.2) SHA-1 입력
Android Studio 우측에 Gradle 버튼을 클릭한 후 gradle signingReport라고 입력하면
위와 같이 SHA-1 번호가 나온다.
입력 후 앱을 등록해보자.
3.3) google-services.json 파일을 App폴더에 넣어주기
해당 json 파일을 다운받아 프로젝트 내 App폴더에 드래그 앤 드롭으로 넣어주면 된다.
3.4) firebase sdk 추가
공식문서에 나온 gradle 설정이 이 방식이 아니다.. 아래와 같이 해야한다.
프로젝트 단위 gradle과 모듈단위 gradle을 각각 열어준다.
3.4.1) 모듈 수준 Gradle 설정
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
//구글 서비스 추가
id 'com.google.gms.google-services' version '4.3.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
3.4.2) 프로젝트 수준 Gradle 설정
plugins {
id 'com.android.application'
id 'com.google.gms.google-services' //구글서비스 추가
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.firebase_sample"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Import the Firebase BoM
implementation platform('com.google.firebase:firebase-bom:29.2.0')
// Add the dependency for the Firebase SDK for Google Analytics
// When using the BoM, don't specify versions in Firebase dependencies
implementation 'com.google.firebase:firebase-analytics'
// Add the dependencies for any other desired Firebase products
// https://firebase.google.com/docs/android/setup#available-libraries
// Declare the dependency for the Firebase Authentication library
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation 'com.google.firebase:firebase-auth'
}
[Docker] Failed to get D-Bus connection: Operation not permitted
dockerfile을 실행해 container를 run 하고 서비스를 실행하려고 하면 에러가 난다.
#dockerfile 실행
[minggu92@cloud dockerfiles]$ docker run -it dockerfile_test:1.0 /bin/bash
#유저변경
[root@a81f33368378 /]# su - minggu1
#sshd 서비스 상태 확인
[minggu1@a81f33368378 ~]$ systemctl status sshd
Failed to get D-Bus connection: Operation not permitted
[minggu1@a81f33368378 ~]$ sudo systemctl status sshd
Failed to get D-Bus connection: Operation not permitted
1. docker 명령어 해결
docker container는 기본적으로 Unprivileged모드로 실행되며 이상태에서는 시스템 주요자원에 접근할 수 있는 권한이 부족하기 때문에 --privileged옵션을 주어야한다.
-d옵션을 사용하면 컨테이너가 detached 모드에서 실행되며 이 모드가 없을 때 Ctrl + C로 빠져나오면 컨테이너는 종료된다.
systemctl 명령어를 사용하려면 /sbin/init을 사용한다.
대신 해당 명령어로 실행시키면 attach 대신 docker exec -it [service] bash로 접근해야 한다.
#docker 실행
[minggu92@cloud dockerfiles]$ docker run --privileged -p 1002:22 -d --name minggu1_ssh dockerfile_test:1.0 /sbin/init
d45e8fc8ba2c821eccea71d0ca00316f7f9fa621e8d4f10b433cab8def6b413c
[minggu92@skylark_dev dockerfiles]# docker exec -it minggu1_ssh /bin/bash
[root@d45e8fc8ba2c /]# su - minggu1
[minggu1@d45e8fc8ba2c ~]$ systemctl status sshd
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2022-03-22 04:16:54 UTC; 13s ago
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 92 (sshd)
CGroup: /docker/d45e8fc8ba2c821eccea71d0ca00316f7f9fa621e8d4f10b433cab8def6b413c/system.slice/sshd.service
└─92 /usr/sbin/sshd -D
‣ 92 /usr/sbin/sshd -D
[minggu1@d45e8fc8ba2c ~]$
2. Dockerfile에 ENTRYPOINT 추가
# 1. pull OS image (centos or ubuntu)
# format : FROM [image]:[tag]
FROM centos:7
# FROM ubuntu:18.04
# 2. 메타데이터 표시
LABEL "purpose"="ssh_test"
LABEL "author"="minggu"
ENV USER minggu1
# 3. 업데이트 및 네트워크 환경설정
#RUN ["[command]", "[parameter1]", "[parameter2]" ...]
# -y 명령어를 빼먹으면 도커 실행이 안된다. 반드시 yes 하자.
RUN yum -y update && yum -y install ntsysv initscripts net-tools sudo openssh-server openssh-clients openssh-askpass
RUN mkdir /var/run/sshd
# 4. minggu1유저에게 sudo 권한 생성
RUN sed -ri '20a'$USER' ALL=(ALL) NOPASSWD:ALL' /etc/sudoers
# 5. .ssh 생성 및 권한주기
RUN useradd -m $USER
RUN mkdir /home/$USER/.ssh
RUN chown $USER.$USER /home/$USER/.ssh
RUN chmod 700 /home/$USER/.ssh
#6. 패스워드 설정
RUN echo 'root:root' | chpasswd
RUN echo $USER':test' | chpasswd
# 7. Generate Keys & 포트 22번 노출 지정
RUN ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -q -N "" -t rsa -f /root/.ssh/id_rsa
RUN cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
EXPOSE 22
#ENTRYPOINT로 미리 명령어 실행할 수 있지만 도커 실행할 때 설정하는걸로
ENTRYPOINT["/sbin/init", "systemctl start sshd", "systemctl enable sshd"]
# 8. CMD : 컨테이너 생성시 시작명령어
#CMD ["[command]", "[parameter1]", "[parameter2]" ...]
#CMD ["[parameter1]", "[parameter2" ...]
#CMD <전체커맨드>
CMD ["/usr/sbin/sshd", "-D"]